diff --git a/Dockerfile-py b/Dockerfile-py index e2aeb98..1910e9f 100644 --- a/Dockerfile-py +++ b/Dockerfile-py @@ -8,5 +8,14 @@ WORKDIR /usr/src/app COPY telegramer /usr/src/app/telegramer COPY setup.py /usr/src/app/setup.py COPY LICENSE /usr/src/app/LICENSE +COPY requirements.txt /usr/src/app/requirements.txt + +WORKDIR /usr/src/app/telegramer +RUN pip install --no-cache-dir -t ./include -r /usr/src/app/requirements.txt + +WORKDIR /usr/src/app/telegramer/include +RUN rm -rf *.dist-info __pycache__ *.egg-info setuptools + +WORKDIR /usr/src/app RUN python setup.py bdist_egg diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7d6b9c4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-telegram-bot==13.11 diff --git a/setup.py b/setup.py index 1b379d1..7f6c566 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ __plugin_name__ = "Telegramer" __author__ = "Noam" __author_email__ = "noamgit@gmail.com" -__version__ = "2.1.1.0" +__version__ = "2.1.1.1" __url__ = "https://github.com/noam09" __license__ = "GPLv3" __description__ = "Control Deluge using Telegram" @@ -55,7 +55,8 @@ Send notifications, add and view torrents on Deluge using Telegram messenger """ -__pkg_data__ = {__plugin_name__.lower(): ["data/*"]} +__pkg_data__ = {__plugin_name__.lower(): [ + "data/*", "include/certifi/cacert.pem"]} # 'certifi': ['include/certifi/cacert.pem']} packages = find_packages() setup( diff --git a/telegramer/include/BaseHTTPServer/__init__.py b/telegramer/include/BaseHTTPServer/__init__.py index 1a39485..9f6d900 100644 --- a/telegramer/include/BaseHTTPServer/__init__.py +++ b/telegramer/include/BaseHTTPServer/__init__.py @@ -327,7 +327,7 @@ def handle_one_request(self): method = getattr(self, mname) method() self.wfile.flush() #actually send the response if not already done. - except socket.timeout, e: + except socket.timeout as e: #a read or a write timed out. Discard this connection self.log_error("Request timed out: %r", e) self.close_connection = 1 @@ -595,7 +595,7 @@ def test(HandlerClass = BaseHTTPRequestHandler, httpd = ServerClass(server_address, HandlerClass) sa = httpd.socket.getsockname() - print "Serving HTTP on", sa[0], "port", sa[1], "..." + print("Serving HTTP on", sa[0], "port", sa[1], "...") httpd.serve_forever() diff --git a/telegramer/include/apscheduler/__init__.py b/telegramer/include/apscheduler/__init__.py deleted file mode 100644 index 968169a..0000000 --- a/telegramer/include/apscheduler/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from pkg_resources import get_distribution, DistributionNotFound - -try: - release = get_distribution('APScheduler').version.split('-')[0] -except DistributionNotFound: - release = '3.5.0' - -version_info = tuple(int(x) if x.isdigit() else x for x in release.split('.')) -version = __version__ = '.'.join(str(x) for x in version_info[:3]) -del get_distribution, DistributionNotFound diff --git a/telegramer/include/apscheduler/events.py b/telegramer/include/apscheduler/events.py deleted file mode 100644 index 016da03..0000000 --- a/telegramer/include/apscheduler/events.py +++ /dev/null @@ -1,94 +0,0 @@ -__all__ = ('EVENT_SCHEDULER_STARTED', 'EVENT_SCHEDULER_SHUTDOWN', 'EVENT_SCHEDULER_PAUSED', - 'EVENT_SCHEDULER_RESUMED', 'EVENT_EXECUTOR_ADDED', 'EVENT_EXECUTOR_REMOVED', - 'EVENT_JOBSTORE_ADDED', 'EVENT_JOBSTORE_REMOVED', 'EVENT_ALL_JOBS_REMOVED', - 'EVENT_JOB_ADDED', 'EVENT_JOB_REMOVED', 'EVENT_JOB_MODIFIED', 'EVENT_JOB_EXECUTED', - 'EVENT_JOB_ERROR', 'EVENT_JOB_MISSED', 'EVENT_JOB_SUBMITTED', 'EVENT_JOB_MAX_INSTANCES', - 'SchedulerEvent', 'JobEvent', 'JobExecutionEvent', 'JobSubmissionEvent') - - -EVENT_SCHEDULER_STARTED = EVENT_SCHEDULER_START = 2 ** 0 -EVENT_SCHEDULER_SHUTDOWN = 2 ** 1 -EVENT_SCHEDULER_PAUSED = 2 ** 2 -EVENT_SCHEDULER_RESUMED = 2 ** 3 -EVENT_EXECUTOR_ADDED = 2 ** 4 -EVENT_EXECUTOR_REMOVED = 2 ** 5 -EVENT_JOBSTORE_ADDED = 2 ** 6 -EVENT_JOBSTORE_REMOVED = 2 ** 7 -EVENT_ALL_JOBS_REMOVED = 2 ** 8 -EVENT_JOB_ADDED = 2 ** 9 -EVENT_JOB_REMOVED = 2 ** 10 -EVENT_JOB_MODIFIED = 2 ** 11 -EVENT_JOB_EXECUTED = 2 ** 12 -EVENT_JOB_ERROR = 2 ** 13 -EVENT_JOB_MISSED = 2 ** 14 -EVENT_JOB_SUBMITTED = 2 ** 15 -EVENT_JOB_MAX_INSTANCES = 2 ** 16 -EVENT_ALL = (EVENT_SCHEDULER_STARTED | EVENT_SCHEDULER_SHUTDOWN | EVENT_SCHEDULER_PAUSED | - EVENT_SCHEDULER_RESUMED | EVENT_EXECUTOR_ADDED | EVENT_EXECUTOR_REMOVED | - EVENT_JOBSTORE_ADDED | EVENT_JOBSTORE_REMOVED | EVENT_ALL_JOBS_REMOVED | - EVENT_JOB_ADDED | EVENT_JOB_REMOVED | EVENT_JOB_MODIFIED | EVENT_JOB_EXECUTED | - EVENT_JOB_ERROR | EVENT_JOB_MISSED | EVENT_JOB_SUBMITTED | EVENT_JOB_MAX_INSTANCES) - - -class SchedulerEvent(object): - """ - An event that concerns the scheduler itself. - - :ivar code: the type code of this event - :ivar alias: alias of the job store or executor that was added or removed (if applicable) - """ - - def __init__(self, code, alias=None): - super(SchedulerEvent, self).__init__() - self.code = code - self.alias = alias - - def __repr__(self): - return '<%s (code=%d)>' % (self.__class__.__name__, self.code) - - -class JobEvent(SchedulerEvent): - """ - An event that concerns a job. - - :ivar code: the type code of this event - :ivar job_id: identifier of the job in question - :ivar jobstore: alias of the job store containing the job in question - """ - - def __init__(self, code, job_id, jobstore): - super(JobEvent, self).__init__(code) - self.code = code - self.job_id = job_id - self.jobstore = jobstore - - -class JobSubmissionEvent(JobEvent): - """ - An event that concerns the submission of a job to its executor. - - :ivar scheduled_run_times: a list of datetimes when the job was intended to run - """ - - def __init__(self, code, job_id, jobstore, scheduled_run_times): - super(JobSubmissionEvent, self).__init__(code, job_id, jobstore) - self.scheduled_run_times = scheduled_run_times - - -class JobExecutionEvent(JobEvent): - """ - An event that concerns the running of a job within its executor. - - :ivar scheduled_run_time: the time when the job was scheduled to be run - :ivar retval: the return value of the successfully executed job - :ivar exception: the exception raised by the job - :ivar traceback: a formatted traceback for the exception - """ - - def __init__(self, code, job_id, jobstore, scheduled_run_time, retval=None, exception=None, - traceback=None): - super(JobExecutionEvent, self).__init__(code, job_id, jobstore) - self.scheduled_run_time = scheduled_run_time - self.retval = retval - self.exception = exception - self.traceback = traceback diff --git a/telegramer/include/apscheduler/executors/__init__.py b/telegramer/include/apscheduler/executors/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/apscheduler/executors/asyncio.py b/telegramer/include/apscheduler/executors/asyncio.py deleted file mode 100644 index 06fc7f9..0000000 --- a/telegramer/include/apscheduler/executors/asyncio.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import absolute_import - -import sys - -from apscheduler.executors.base import BaseExecutor, run_job -from apscheduler.util import iscoroutinefunction_partial - -try: - from apscheduler.executors.base_py3 import run_coroutine_job -except ImportError: - run_coroutine_job = None - - -class AsyncIOExecutor(BaseExecutor): - """ - Runs jobs in the default executor of the event loop. - - If the job function is a native coroutine function, it is scheduled to be run directly in the - event loop as soon as possible. All other functions are run in the event loop's default - executor which is usually a thread pool. - - Plugin alias: ``asyncio`` - """ - - def start(self, scheduler, alias): - super(AsyncIOExecutor, self).start(scheduler, alias) - self._eventloop = scheduler._eventloop - self._pending_futures = set() - - def shutdown(self, wait=True): - # There is no way to honor wait=True without converting this method into a coroutine method - for f in self._pending_futures: - if not f.done(): - f.cancel() - - self._pending_futures.clear() - - def _do_submit_job(self, job, run_times): - def callback(f): - self._pending_futures.discard(f) - try: - events = f.result() - except BaseException: - self._run_job_error(job.id, *sys.exc_info()[1:]) - else: - self._run_job_success(job.id, events) - - if iscoroutinefunction_partial(job.func): - if run_coroutine_job is not None: - coro = run_coroutine_job(job, job._jobstore_alias, run_times, self._logger.name) - f = self._eventloop.create_task(coro) - else: - raise Exception('Executing coroutine based jobs is not supported with Trollius') - else: - f = self._eventloop.run_in_executor(None, run_job, job, job._jobstore_alias, run_times, - self._logger.name) - - f.add_done_callback(callback) - self._pending_futures.add(f) diff --git a/telegramer/include/apscheduler/executors/base.py b/telegramer/include/apscheduler/executors/base.py deleted file mode 100644 index 4c09fc1..0000000 --- a/telegramer/include/apscheduler/executors/base.py +++ /dev/null @@ -1,146 +0,0 @@ -from abc import ABCMeta, abstractmethod -from collections import defaultdict -from datetime import datetime, timedelta -from traceback import format_tb -import logging -import sys - -from pytz import utc -import six - -from apscheduler.events import ( - JobExecutionEvent, EVENT_JOB_MISSED, EVENT_JOB_ERROR, EVENT_JOB_EXECUTED) - - -class MaxInstancesReachedError(Exception): - def __init__(self, job): - super(MaxInstancesReachedError, self).__init__( - 'Job "%s" has already reached its maximum number of instances (%d)' % - (job.id, job.max_instances)) - - -class BaseExecutor(six.with_metaclass(ABCMeta, object)): - """Abstract base class that defines the interface that every executor must implement.""" - - _scheduler = None - _lock = None - _logger = logging.getLogger('apscheduler.executors') - - def __init__(self): - super(BaseExecutor, self).__init__() - self._instances = defaultdict(lambda: 0) - - def start(self, scheduler, alias): - """ - Called by the scheduler when the scheduler is being started or when the executor is being - added to an already running scheduler. - - :param apscheduler.schedulers.base.BaseScheduler scheduler: the scheduler that is starting - this executor - :param str|unicode alias: alias of this executor as it was assigned to the scheduler - - """ - self._scheduler = scheduler - self._lock = scheduler._create_lock() - self._logger = logging.getLogger('apscheduler.executors.%s' % alias) - - def shutdown(self, wait=True): - """ - Shuts down this executor. - - :param bool wait: ``True`` to wait until all submitted jobs - have been executed - """ - - def submit_job(self, job, run_times): - """ - Submits job for execution. - - :param Job job: job to execute - :param list[datetime] run_times: list of datetimes specifying - when the job should have been run - :raises MaxInstancesReachedError: if the maximum number of - allowed instances for this job has been reached - - """ - assert self._lock is not None, 'This executor has not been started yet' - with self._lock: - if self._instances[job.id] >= job.max_instances: - raise MaxInstancesReachedError(job) - - self._do_submit_job(job, run_times) - self._instances[job.id] += 1 - - @abstractmethod - def _do_submit_job(self, job, run_times): - """Performs the actual task of scheduling `run_job` to be called.""" - - def _run_job_success(self, job_id, events): - """ - Called by the executor with the list of generated events when :func:`run_job` has been - successfully called. - - """ - with self._lock: - self._instances[job_id] -= 1 - if self._instances[job_id] == 0: - del self._instances[job_id] - - for event in events: - self._scheduler._dispatch_event(event) - - def _run_job_error(self, job_id, exc, traceback=None): - """Called by the executor with the exception if there is an error calling `run_job`.""" - with self._lock: - self._instances[job_id] -= 1 - if self._instances[job_id] == 0: - del self._instances[job_id] - - exc_info = (exc.__class__, exc, traceback) - self._logger.error('Error running job %s', job_id, exc_info=exc_info) - - -def run_job(job, jobstore_alias, run_times, logger_name): - """ - Called by executors to run the job. Returns a list of scheduler events to be dispatched by the - scheduler. - - """ - events = [] - logger = logging.getLogger(logger_name) - for run_time in run_times: - # See if the job missed its run time window, and handle - # possible misfires accordingly - if job.misfire_grace_time is not None: - difference = datetime.now(utc) - run_time - grace_time = timedelta(seconds=job.misfire_grace_time) - if difference > grace_time: - events.append(JobExecutionEvent(EVENT_JOB_MISSED, job.id, jobstore_alias, - run_time)) - logger.warning('Run time of job "%s" was missed by %s', job, difference) - continue - - logger.info('Running job "%s" (scheduled at %s)', job, run_time) - try: - retval = job.func(*job.args, **job.kwargs) - except BaseException: - exc, tb = sys.exc_info()[1:] - formatted_tb = ''.join(format_tb(tb)) - events.append(JobExecutionEvent(EVENT_JOB_ERROR, job.id, jobstore_alias, run_time, - exception=exc, traceback=formatted_tb)) - logger.exception('Job "%s" raised an exception', job) - - # This is to prevent cyclic references that would lead to memory leaks - if six.PY2: - sys.exc_clear() - del tb - else: - import traceback - traceback.clear_frames(tb) - del tb - else: - events.append(JobExecutionEvent(EVENT_JOB_EXECUTED, job.id, jobstore_alias, run_time, - retval=retval)) - logger.info('Job "%s" executed successfully', job) - - return events diff --git a/telegramer/include/apscheduler/executors/base_py3.py b/telegramer/include/apscheduler/executors/base_py3.py deleted file mode 100644 index 7111d2a..0000000 --- a/telegramer/include/apscheduler/executors/base_py3.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -import sys -import traceback -from datetime import datetime, timedelta -from traceback import format_tb - -from pytz import utc - -from apscheduler.events import ( - JobExecutionEvent, EVENT_JOB_MISSED, EVENT_JOB_ERROR, EVENT_JOB_EXECUTED) - - -async def run_coroutine_job(job, jobstore_alias, run_times, logger_name): - """Coroutine version of run_job().""" - events = [] - logger = logging.getLogger(logger_name) - for run_time in run_times: - # See if the job missed its run time window, and handle possible misfires accordingly - if job.misfire_grace_time is not None: - difference = datetime.now(utc) - run_time - grace_time = timedelta(seconds=job.misfire_grace_time) - if difference > grace_time: - events.append(JobExecutionEvent(EVENT_JOB_MISSED, job.id, jobstore_alias, - run_time)) - logger.warning('Run time of job "%s" was missed by %s', job, difference) - continue - - logger.info('Running job "%s" (scheduled at %s)', job, run_time) - try: - retval = await job.func(*job.args, **job.kwargs) - except BaseException: - exc, tb = sys.exc_info()[1:] - formatted_tb = ''.join(format_tb(tb)) - events.append(JobExecutionEvent(EVENT_JOB_ERROR, job.id, jobstore_alias, run_time, - exception=exc, traceback=formatted_tb)) - logger.exception('Job "%s" raised an exception', job) - traceback.clear_frames(tb) - else: - events.append(JobExecutionEvent(EVENT_JOB_EXECUTED, job.id, jobstore_alias, run_time, - retval=retval)) - logger.info('Job "%s" executed successfully', job) - - return events diff --git a/telegramer/include/apscheduler/executors/debug.py b/telegramer/include/apscheduler/executors/debug.py deleted file mode 100644 index ac739ae..0000000 --- a/telegramer/include/apscheduler/executors/debug.py +++ /dev/null @@ -1,20 +0,0 @@ -import sys - -from apscheduler.executors.base import BaseExecutor, run_job - - -class DebugExecutor(BaseExecutor): - """ - A special executor that executes the target callable directly instead of deferring it to a - thread or process. - - Plugin alias: ``debug`` - """ - - def _do_submit_job(self, job, run_times): - try: - events = run_job(job, job._jobstore_alias, run_times, self._logger.name) - except BaseException: - self._run_job_error(job.id, *sys.exc_info()[1:]) - else: - self._run_job_success(job.id, events) diff --git a/telegramer/include/apscheduler/executors/gevent.py b/telegramer/include/apscheduler/executors/gevent.py deleted file mode 100644 index 1235bb6..0000000 --- a/telegramer/include/apscheduler/executors/gevent.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import absolute_import -import sys - -from apscheduler.executors.base import BaseExecutor, run_job - - -try: - import gevent -except ImportError: # pragma: nocover - raise ImportError('GeventExecutor requires gevent installed') - - -class GeventExecutor(BaseExecutor): - """ - Runs jobs as greenlets. - - Plugin alias: ``gevent`` - """ - - def _do_submit_job(self, job, run_times): - def callback(greenlet): - try: - events = greenlet.get() - except BaseException: - self._run_job_error(job.id, *sys.exc_info()[1:]) - else: - self._run_job_success(job.id, events) - - gevent.spawn(run_job, job, job._jobstore_alias, run_times, self._logger.name).\ - link(callback) diff --git a/telegramer/include/apscheduler/executors/pool.py b/telegramer/include/apscheduler/executors/pool.py deleted file mode 100644 index c85896e..0000000 --- a/telegramer/include/apscheduler/executors/pool.py +++ /dev/null @@ -1,71 +0,0 @@ -from abc import abstractmethod -import concurrent.futures - -from apscheduler.executors.base import BaseExecutor, run_job - -try: - from concurrent.futures.process import BrokenProcessPool -except ImportError: - BrokenProcessPool = None - - -class BasePoolExecutor(BaseExecutor): - @abstractmethod - def __init__(self, pool): - super(BasePoolExecutor, self).__init__() - self._pool = pool - - def _do_submit_job(self, job, run_times): - def callback(f): - exc, tb = (f.exception_info() if hasattr(f, 'exception_info') else - (f.exception(), getattr(f.exception(), '__traceback__', None))) - if exc: - self._run_job_error(job.id, exc, tb) - else: - self._run_job_success(job.id, f.result()) - - try: - f = self._pool.submit(run_job, job, job._jobstore_alias, run_times, self._logger.name) - except BrokenProcessPool: - self._logger.warning('Process pool is broken; replacing pool with a fresh instance') - self._pool = self._pool.__class__(self._pool._max_workers) - f = self._pool.submit(run_job, job, job._jobstore_alias, run_times, self._logger.name) - - f.add_done_callback(callback) - - def shutdown(self, wait=True): - self._pool.shutdown(wait) - - -class ThreadPoolExecutor(BasePoolExecutor): - """ - An executor that runs jobs in a concurrent.futures thread pool. - - Plugin alias: ``threadpool`` - - :param max_workers: the maximum number of spawned threads. - :param pool_kwargs: dict of keyword arguments to pass to the underlying - ThreadPoolExecutor constructor - """ - - def __init__(self, max_workers=10, pool_kwargs=None): - pool_kwargs = pool_kwargs or {} - pool = concurrent.futures.ThreadPoolExecutor(int(max_workers), **pool_kwargs) - super(ThreadPoolExecutor, self).__init__(pool) - - -class ProcessPoolExecutor(BasePoolExecutor): - """ - An executor that runs jobs in a concurrent.futures process pool. - - Plugin alias: ``processpool`` - - :param max_workers: the maximum number of spawned processes. - :param pool_kwargs: dict of keyword arguments to pass to the underlying - ProcessPoolExecutor constructor - """ - - def __init__(self, max_workers=10, pool_kwargs=None): - pool_kwargs = pool_kwargs or {} - pool = concurrent.futures.ProcessPoolExecutor(int(max_workers), **pool_kwargs) - super(ProcessPoolExecutor, self).__init__(pool) diff --git a/telegramer/include/apscheduler/executors/tornado.py b/telegramer/include/apscheduler/executors/tornado.py deleted file mode 100644 index 3b97eec..0000000 --- a/telegramer/include/apscheduler/executors/tornado.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import absolute_import - -import sys -from concurrent.futures import ThreadPoolExecutor - -from tornado.gen import convert_yielded - -from apscheduler.executors.base import BaseExecutor, run_job - -try: - from apscheduler.executors.base_py3 import run_coroutine_job - from apscheduler.util import iscoroutinefunction_partial -except ImportError: - def iscoroutinefunction_partial(func): - return False - - -class TornadoExecutor(BaseExecutor): - """ - Runs jobs either in a thread pool or directly on the I/O loop. - - If the job function is a native coroutine function, it is scheduled to be run directly in the - I/O loop as soon as possible. All other functions are run in a thread pool. - - Plugin alias: ``tornado`` - - :param int max_workers: maximum number of worker threads in the thread pool - """ - - def __init__(self, max_workers=10): - super(TornadoExecutor, self).__init__() - self.executor = ThreadPoolExecutor(max_workers) - - def start(self, scheduler, alias): - super(TornadoExecutor, self).start(scheduler, alias) - self._ioloop = scheduler._ioloop - - def _do_submit_job(self, job, run_times): - def callback(f): - try: - events = f.result() - except BaseException: - self._run_job_error(job.id, *sys.exc_info()[1:]) - else: - self._run_job_success(job.id, events) - - if iscoroutinefunction_partial(job.func): - f = run_coroutine_job(job, job._jobstore_alias, run_times, self._logger.name) - else: - f = self.executor.submit(run_job, job, job._jobstore_alias, run_times, - self._logger.name) - - f = convert_yielded(f) - f.add_done_callback(callback) diff --git a/telegramer/include/apscheduler/executors/twisted.py b/telegramer/include/apscheduler/executors/twisted.py deleted file mode 100644 index c7bcf64..0000000 --- a/telegramer/include/apscheduler/executors/twisted.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import absolute_import - -from apscheduler.executors.base import BaseExecutor, run_job - - -class TwistedExecutor(BaseExecutor): - """ - Runs jobs in the reactor's thread pool. - - Plugin alias: ``twisted`` - """ - - def start(self, scheduler, alias): - super(TwistedExecutor, self).start(scheduler, alias) - self._reactor = scheduler._reactor - - def _do_submit_job(self, job, run_times): - def callback(success, result): - if success: - self._run_job_success(job.id, result) - else: - self._run_job_error(job.id, result.value, result.tb) - - self._reactor.getThreadPool().callInThreadWithCallback( - callback, run_job, job, job._jobstore_alias, run_times, self._logger.name) diff --git a/telegramer/include/apscheduler/job.py b/telegramer/include/apscheduler/job.py deleted file mode 100644 index 445d9a8..0000000 --- a/telegramer/include/apscheduler/job.py +++ /dev/null @@ -1,302 +0,0 @@ -from inspect import ismethod, isclass -from uuid import uuid4 - -import six - -from apscheduler.triggers.base import BaseTrigger -from apscheduler.util import ( - ref_to_obj, obj_to_ref, datetime_repr, repr_escape, get_callable_name, check_callable_args, - convert_to_datetime) - -try: - from collections.abc import Iterable, Mapping -except ImportError: - from collections import Iterable, Mapping - - -class Job(object): - """ - Contains the options given when scheduling callables and its current schedule and other state. - This class should never be instantiated by the user. - - :var str id: the unique identifier of this job - :var str name: the description of this job - :var func: the callable to execute - :var tuple|list args: positional arguments to the callable - :var dict kwargs: keyword arguments to the callable - :var bool coalesce: whether to only run the job once when several run times are due - :var trigger: the trigger object that controls the schedule of this job - :var str executor: the name of the executor that will run this job - :var int misfire_grace_time: the time (in seconds) how much this job's execution is allowed to - be late (``None`` means "allow the job to run no matter how late it is") - :var int max_instances: the maximum number of concurrently executing instances allowed for this - job - :var datetime.datetime next_run_time: the next scheduled run time of this job - - .. note:: - The ``misfire_grace_time`` has some non-obvious effects on job execution. See the - :ref:`missed-job-executions` section in the documentation for an in-depth explanation. - """ - - __slots__ = ('_scheduler', '_jobstore_alias', 'id', 'trigger', 'executor', 'func', 'func_ref', - 'args', 'kwargs', 'name', 'misfire_grace_time', 'coalesce', 'max_instances', - 'next_run_time', '__weakref__') - - def __init__(self, scheduler, id=None, **kwargs): - super(Job, self).__init__() - self._scheduler = scheduler - self._jobstore_alias = None - self._modify(id=id or uuid4().hex, **kwargs) - - def modify(self, **changes): - """ - Makes the given changes to this job and saves it in the associated job store. - - Accepted keyword arguments are the same as the variables on this class. - - .. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.modify_job` - - :return Job: this job instance - - """ - self._scheduler.modify_job(self.id, self._jobstore_alias, **changes) - return self - - def reschedule(self, trigger, **trigger_args): - """ - Shortcut for switching the trigger on this job. - - .. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.reschedule_job` - - :return Job: this job instance - - """ - self._scheduler.reschedule_job(self.id, self._jobstore_alias, trigger, **trigger_args) - return self - - def pause(self): - """ - Temporarily suspend the execution of this job. - - .. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.pause_job` - - :return Job: this job instance - - """ - self._scheduler.pause_job(self.id, self._jobstore_alias) - return self - - def resume(self): - """ - Resume the schedule of this job if previously paused. - - .. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.resume_job` - - :return Job: this job instance - - """ - self._scheduler.resume_job(self.id, self._jobstore_alias) - return self - - def remove(self): - """ - Unschedules this job and removes it from its associated job store. - - .. seealso:: :meth:`~apscheduler.schedulers.base.BaseScheduler.remove_job` - - """ - self._scheduler.remove_job(self.id, self._jobstore_alias) - - @property - def pending(self): - """ - Returns ``True`` if the referenced job is still waiting to be added to its designated job - store. - - """ - return self._jobstore_alias is None - - # - # Private API - # - - def _get_run_times(self, now): - """ - Computes the scheduled run times between ``next_run_time`` and ``now`` (inclusive). - - :type now: datetime.datetime - :rtype: list[datetime.datetime] - - """ - run_times = [] - next_run_time = self.next_run_time - while next_run_time and next_run_time <= now: - run_times.append(next_run_time) - next_run_time = self.trigger.get_next_fire_time(next_run_time, now) - - return run_times - - def _modify(self, **changes): - """ - Validates the changes to the Job and makes the modifications if and only if all of them - validate. - - """ - approved = {} - - if 'id' in changes: - value = changes.pop('id') - if not isinstance(value, six.string_types): - raise TypeError("id must be a nonempty string") - if hasattr(self, 'id'): - raise ValueError('The job ID may not be changed') - approved['id'] = value - - if 'func' in changes or 'args' in changes or 'kwargs' in changes: - func = changes.pop('func') if 'func' in changes else self.func - args = changes.pop('args') if 'args' in changes else self.args - kwargs = changes.pop('kwargs') if 'kwargs' in changes else self.kwargs - - if isinstance(func, six.string_types): - func_ref = func - func = ref_to_obj(func) - elif callable(func): - try: - func_ref = obj_to_ref(func) - except ValueError: - # If this happens, this Job won't be serializable - func_ref = None - else: - raise TypeError('func must be a callable or a textual reference to one') - - if not hasattr(self, 'name') and changes.get('name', None) is None: - changes['name'] = get_callable_name(func) - - if isinstance(args, six.string_types) or not isinstance(args, Iterable): - raise TypeError('args must be a non-string iterable') - if isinstance(kwargs, six.string_types) or not isinstance(kwargs, Mapping): - raise TypeError('kwargs must be a dict-like object') - - check_callable_args(func, args, kwargs) - - approved['func'] = func - approved['func_ref'] = func_ref - approved['args'] = args - approved['kwargs'] = kwargs - - if 'name' in changes: - value = changes.pop('name') - if not value or not isinstance(value, six.string_types): - raise TypeError("name must be a nonempty string") - approved['name'] = value - - if 'misfire_grace_time' in changes: - value = changes.pop('misfire_grace_time') - if value is not None and (not isinstance(value, six.integer_types) or value <= 0): - raise TypeError('misfire_grace_time must be either None or a positive integer') - approved['misfire_grace_time'] = value - - if 'coalesce' in changes: - value = bool(changes.pop('coalesce')) - approved['coalesce'] = value - - if 'max_instances' in changes: - value = changes.pop('max_instances') - if not isinstance(value, six.integer_types) or value <= 0: - raise TypeError('max_instances must be a positive integer') - approved['max_instances'] = value - - if 'trigger' in changes: - trigger = changes.pop('trigger') - if not isinstance(trigger, BaseTrigger): - raise TypeError('Expected a trigger instance, got %s instead' % - trigger.__class__.__name__) - - approved['trigger'] = trigger - - if 'executor' in changes: - value = changes.pop('executor') - if not isinstance(value, six.string_types): - raise TypeError('executor must be a string') - approved['executor'] = value - - if 'next_run_time' in changes: - value = changes.pop('next_run_time') - approved['next_run_time'] = convert_to_datetime(value, self._scheduler.timezone, - 'next_run_time') - - if changes: - raise AttributeError('The following are not modifiable attributes of Job: %s' % - ', '.join(changes)) - - for key, value in six.iteritems(approved): - setattr(self, key, value) - - def __getstate__(self): - # Don't allow this Job to be serialized if the function reference could not be determined - if not self.func_ref: - raise ValueError( - 'This Job cannot be serialized since the reference to its callable (%r) could not ' - 'be determined. Consider giving a textual reference (module:function name) ' - 'instead.' % (self.func,)) - - # Instance methods cannot survive serialization as-is, so store the "self" argument - # explicitly - func = self.func - if ismethod(func) and not isclass(func.__self__) and obj_to_ref(func) == self.func_ref: - args = (func.__self__,) + tuple(self.args) - else: - args = self.args - - return { - 'version': 1, - 'id': self.id, - 'func': self.func_ref, - 'trigger': self.trigger, - 'executor': self.executor, - 'args': args, - 'kwargs': self.kwargs, - 'name': self.name, - 'misfire_grace_time': self.misfire_grace_time, - 'coalesce': self.coalesce, - 'max_instances': self.max_instances, - 'next_run_time': self.next_run_time - } - - def __setstate__(self, state): - if state.get('version', 1) > 1: - raise ValueError('Job has version %s, but only version 1 can be handled' % - state['version']) - - self.id = state['id'] - self.func_ref = state['func'] - self.func = ref_to_obj(self.func_ref) - self.trigger = state['trigger'] - self.executor = state['executor'] - self.args = state['args'] - self.kwargs = state['kwargs'] - self.name = state['name'] - self.misfire_grace_time = state['misfire_grace_time'] - self.coalesce = state['coalesce'] - self.max_instances = state['max_instances'] - self.next_run_time = state['next_run_time'] - - def __eq__(self, other): - if isinstance(other, Job): - return self.id == other.id - return NotImplemented - - def __repr__(self): - return '' % (repr_escape(self.id), repr_escape(self.name)) - - def __str__(self): - return repr_escape(self.__unicode__()) - - def __unicode__(self): - if hasattr(self, 'next_run_time'): - status = ('next run at: ' + datetime_repr(self.next_run_time) if - self.next_run_time else 'paused') - else: - status = 'pending' - - return u'%s (trigger: %s, %s)' % (self.name, self.trigger, status) diff --git a/telegramer/include/apscheduler/jobstores/__init__.py b/telegramer/include/apscheduler/jobstores/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/apscheduler/jobstores/base.py b/telegramer/include/apscheduler/jobstores/base.py deleted file mode 100644 index 9cff66c..0000000 --- a/telegramer/include/apscheduler/jobstores/base.py +++ /dev/null @@ -1,143 +0,0 @@ -from abc import ABCMeta, abstractmethod -import logging - -import six - - -class JobLookupError(KeyError): - """Raised when the job store cannot find a job for update or removal.""" - - def __init__(self, job_id): - super(JobLookupError, self).__init__(u'No job by the id of %s was found' % job_id) - - -class ConflictingIdError(KeyError): - """Raised when the uniqueness of job IDs is being violated.""" - - def __init__(self, job_id): - super(ConflictingIdError, self).__init__( - u'Job identifier (%s) conflicts with an existing job' % job_id) - - -class TransientJobError(ValueError): - """ - Raised when an attempt to add transient (with no func_ref) job to a persistent job store is - detected. - """ - - def __init__(self, job_id): - super(TransientJobError, self).__init__( - u'Job (%s) cannot be added to this job store because a reference to the callable ' - u'could not be determined.' % job_id) - - -class BaseJobStore(six.with_metaclass(ABCMeta)): - """Abstract base class that defines the interface that every job store must implement.""" - - _scheduler = None - _alias = None - _logger = logging.getLogger('apscheduler.jobstores') - - def start(self, scheduler, alias): - """ - Called by the scheduler when the scheduler is being started or when the job store is being - added to an already running scheduler. - - :param apscheduler.schedulers.base.BaseScheduler scheduler: the scheduler that is starting - this job store - :param str|unicode alias: alias of this job store as it was assigned to the scheduler - """ - - self._scheduler = scheduler - self._alias = alias - self._logger = logging.getLogger('apscheduler.jobstores.%s' % alias) - - def shutdown(self): - """Frees any resources still bound to this job store.""" - - def _fix_paused_jobs_sorting(self, jobs): - for i, job in enumerate(jobs): - if job.next_run_time is not None: - if i > 0: - paused_jobs = jobs[:i] - del jobs[:i] - jobs.extend(paused_jobs) - break - - @abstractmethod - def lookup_job(self, job_id): - """ - Returns a specific job, or ``None`` if it isn't found.. - - The job store is responsible for setting the ``scheduler`` and ``jobstore`` attributes of - the returned job to point to the scheduler and itself, respectively. - - :param str|unicode job_id: identifier of the job - :rtype: Job - """ - - @abstractmethod - def get_due_jobs(self, now): - """ - Returns the list of jobs that have ``next_run_time`` earlier or equal to ``now``. - The returned jobs must be sorted by next run time (ascending). - - :param datetime.datetime now: the current (timezone aware) datetime - :rtype: list[Job] - """ - - @abstractmethod - def get_next_run_time(self): - """ - Returns the earliest run time of all the jobs stored in this job store, or ``None`` if - there are no active jobs. - - :rtype: datetime.datetime - """ - - @abstractmethod - def get_all_jobs(self): - """ - Returns a list of all jobs in this job store. - The returned jobs should be sorted by next run time (ascending). - Paused jobs (next_run_time == None) should be sorted last. - - The job store is responsible for setting the ``scheduler`` and ``jobstore`` attributes of - the returned jobs to point to the scheduler and itself, respectively. - - :rtype: list[Job] - """ - - @abstractmethod - def add_job(self, job): - """ - Adds the given job to this store. - - :param Job job: the job to add - :raises ConflictingIdError: if there is another job in this store with the same ID - """ - - @abstractmethod - def update_job(self, job): - """ - Replaces the job in the store with the given newer version. - - :param Job job: the job to update - :raises JobLookupError: if the job does not exist - """ - - @abstractmethod - def remove_job(self, job_id): - """ - Removes the given job from this store. - - :param str|unicode job_id: identifier of the job - :raises JobLookupError: if the job does not exist - """ - - @abstractmethod - def remove_all_jobs(self): - """Removes all jobs from this store.""" - - def __repr__(self): - return '<%s>' % self.__class__.__name__ diff --git a/telegramer/include/apscheduler/jobstores/memory.py b/telegramer/include/apscheduler/jobstores/memory.py deleted file mode 100644 index abfe7c6..0000000 --- a/telegramer/include/apscheduler/jobstores/memory.py +++ /dev/null @@ -1,108 +0,0 @@ -from __future__ import absolute_import - -from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError -from apscheduler.util import datetime_to_utc_timestamp - - -class MemoryJobStore(BaseJobStore): - """ - Stores jobs in an array in RAM. Provides no persistence support. - - Plugin alias: ``memory`` - """ - - def __init__(self): - super(MemoryJobStore, self).__init__() - # list of (job, timestamp), sorted by next_run_time and job id (ascending) - self._jobs = [] - self._jobs_index = {} # id -> (job, timestamp) lookup table - - def lookup_job(self, job_id): - return self._jobs_index.get(job_id, (None, None))[0] - - def get_due_jobs(self, now): - now_timestamp = datetime_to_utc_timestamp(now) - pending = [] - for job, timestamp in self._jobs: - if timestamp is None or timestamp > now_timestamp: - break - pending.append(job) - - return pending - - def get_next_run_time(self): - return self._jobs[0][0].next_run_time if self._jobs else None - - def get_all_jobs(self): - return [j[0] for j in self._jobs] - - def add_job(self, job): - if job.id in self._jobs_index: - raise ConflictingIdError(job.id) - - timestamp = datetime_to_utc_timestamp(job.next_run_time) - index = self._get_job_index(timestamp, job.id) - self._jobs.insert(index, (job, timestamp)) - self._jobs_index[job.id] = (job, timestamp) - - def update_job(self, job): - old_job, old_timestamp = self._jobs_index.get(job.id, (None, None)) - if old_job is None: - raise JobLookupError(job.id) - - # If the next run time has not changed, simply replace the job in its present index. - # Otherwise, reinsert the job to the list to preserve the ordering. - old_index = self._get_job_index(old_timestamp, old_job.id) - new_timestamp = datetime_to_utc_timestamp(job.next_run_time) - if old_timestamp == new_timestamp: - self._jobs[old_index] = (job, new_timestamp) - else: - del self._jobs[old_index] - new_index = self._get_job_index(new_timestamp, job.id) - self._jobs.insert(new_index, (job, new_timestamp)) - - self._jobs_index[old_job.id] = (job, new_timestamp) - - def remove_job(self, job_id): - job, timestamp = self._jobs_index.get(job_id, (None, None)) - if job is None: - raise JobLookupError(job_id) - - index = self._get_job_index(timestamp, job_id) - del self._jobs[index] - del self._jobs_index[job.id] - - def remove_all_jobs(self): - self._jobs = [] - self._jobs_index = {} - - def shutdown(self): - self.remove_all_jobs() - - def _get_job_index(self, timestamp, job_id): - """ - Returns the index of the given job, or if it's not found, the index where the job should be - inserted based on the given timestamp. - - :type timestamp: int - :type job_id: str - - """ - lo, hi = 0, len(self._jobs) - timestamp = float('inf') if timestamp is None else timestamp - while lo < hi: - mid = (lo + hi) // 2 - mid_job, mid_timestamp = self._jobs[mid] - mid_timestamp = float('inf') if mid_timestamp is None else mid_timestamp - if mid_timestamp > timestamp: - hi = mid - elif mid_timestamp < timestamp: - lo = mid + 1 - elif mid_job.id > job_id: - hi = mid - elif mid_job.id < job_id: - lo = mid + 1 - else: - return mid - - return lo diff --git a/telegramer/include/apscheduler/jobstores/mongodb.py b/telegramer/include/apscheduler/jobstores/mongodb.py deleted file mode 100644 index 5a00f94..0000000 --- a/telegramer/include/apscheduler/jobstores/mongodb.py +++ /dev/null @@ -1,141 +0,0 @@ -from __future__ import absolute_import -import warnings - -from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError -from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime -from apscheduler.job import Job - -try: - import cPickle as pickle -except ImportError: # pragma: nocover - import pickle - -try: - from bson.binary import Binary - from pymongo.errors import DuplicateKeyError - from pymongo import MongoClient, ASCENDING -except ImportError: # pragma: nocover - raise ImportError('MongoDBJobStore requires PyMongo installed') - - -class MongoDBJobStore(BaseJobStore): - """ - Stores jobs in a MongoDB database. Any leftover keyword arguments are directly passed to - pymongo's `MongoClient - `_. - - Plugin alias: ``mongodb`` - - :param str database: database to store jobs in - :param str collection: collection to store jobs in - :param client: a :class:`~pymongo.mongo_client.MongoClient` instance to use instead of - providing connection arguments - :param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the - highest available - """ - - def __init__(self, database='apscheduler', collection='jobs', client=None, - pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args): - super(MongoDBJobStore, self).__init__() - self.pickle_protocol = pickle_protocol - - if not database: - raise ValueError('The "database" parameter must not be empty') - if not collection: - raise ValueError('The "collection" parameter must not be empty') - - if client: - self.client = maybe_ref(client) - else: - connect_args.setdefault('w', 1) - self.client = MongoClient(**connect_args) - - self.collection = self.client[database][collection] - - def start(self, scheduler, alias): - super(MongoDBJobStore, self).start(scheduler, alias) - self.collection.create_index('next_run_time', sparse=True) - - @property - def connection(self): - warnings.warn('The "connection" member is deprecated -- use "client" instead', - DeprecationWarning) - return self.client - - def lookup_job(self, job_id): - document = self.collection.find_one(job_id, ['job_state']) - return self._reconstitute_job(document['job_state']) if document else None - - def get_due_jobs(self, now): - timestamp = datetime_to_utc_timestamp(now) - return self._get_jobs({'next_run_time': {'$lte': timestamp}}) - - def get_next_run_time(self): - document = self.collection.find_one({'next_run_time': {'$ne': None}}, - projection=['next_run_time'], - sort=[('next_run_time', ASCENDING)]) - return utc_timestamp_to_datetime(document['next_run_time']) if document else None - - def get_all_jobs(self): - jobs = self._get_jobs({}) - self._fix_paused_jobs_sorting(jobs) - return jobs - - def add_job(self, job): - try: - self.collection.insert_one({ - '_id': job.id, - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': Binary(pickle.dumps(job.__getstate__(), self.pickle_protocol)) - }) - except DuplicateKeyError: - raise ConflictingIdError(job.id) - - def update_job(self, job): - changes = { - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': Binary(pickle.dumps(job.__getstate__(), self.pickle_protocol)) - } - result = self.collection.update_one({'_id': job.id}, {'$set': changes}) - if result and result.matched_count == 0: - raise JobLookupError(job.id) - - def remove_job(self, job_id): - result = self.collection.delete_one({'_id': job_id}) - if result and result.deleted_count == 0: - raise JobLookupError(job_id) - - def remove_all_jobs(self): - self.collection.delete_many({}) - - def shutdown(self): - self.client.close() - - def _reconstitute_job(self, job_state): - job_state = pickle.loads(job_state) - job = Job.__new__(Job) - job.__setstate__(job_state) - job._scheduler = self._scheduler - job._jobstore_alias = self._alias - return job - - def _get_jobs(self, conditions): - jobs = [] - failed_job_ids = [] - for document in self.collection.find(conditions, ['_id', 'job_state'], - sort=[('next_run_time', ASCENDING)]): - try: - jobs.append(self._reconstitute_job(document['job_state'])) - except BaseException: - self._logger.exception('Unable to restore job "%s" -- removing it', - document['_id']) - failed_job_ids.append(document['_id']) - - # Remove all the jobs we failed to restore - if failed_job_ids: - self.collection.delete_many({'_id': {'$in': failed_job_ids}}) - - return jobs - - def __repr__(self): - return '<%s (client=%s)>' % (self.__class__.__name__, self.client) diff --git a/telegramer/include/apscheduler/jobstores/redis.py b/telegramer/include/apscheduler/jobstores/redis.py deleted file mode 100644 index 5bb69d6..0000000 --- a/telegramer/include/apscheduler/jobstores/redis.py +++ /dev/null @@ -1,150 +0,0 @@ -from __future__ import absolute_import -from datetime import datetime - -from pytz import utc -import six - -from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError -from apscheduler.util import datetime_to_utc_timestamp, utc_timestamp_to_datetime -from apscheduler.job import Job - -try: - import cPickle as pickle -except ImportError: # pragma: nocover - import pickle - -try: - from redis import Redis -except ImportError: # pragma: nocover - raise ImportError('RedisJobStore requires redis installed') - - -class RedisJobStore(BaseJobStore): - """ - Stores jobs in a Redis database. Any leftover keyword arguments are directly passed to redis's - :class:`~redis.StrictRedis`. - - Plugin alias: ``redis`` - - :param int db: the database number to store jobs in - :param str jobs_key: key to store jobs in - :param str run_times_key: key to store the jobs' run times in - :param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the - highest available - """ - - def __init__(self, db=0, jobs_key='apscheduler.jobs', run_times_key='apscheduler.run_times', - pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args): - super(RedisJobStore, self).__init__() - - if db is None: - raise ValueError('The "db" parameter must not be empty') - if not jobs_key: - raise ValueError('The "jobs_key" parameter must not be empty') - if not run_times_key: - raise ValueError('The "run_times_key" parameter must not be empty') - - self.pickle_protocol = pickle_protocol - self.jobs_key = jobs_key - self.run_times_key = run_times_key - self.redis = Redis(db=int(db), **connect_args) - - def lookup_job(self, job_id): - job_state = self.redis.hget(self.jobs_key, job_id) - return self._reconstitute_job(job_state) if job_state else None - - def get_due_jobs(self, now): - timestamp = datetime_to_utc_timestamp(now) - job_ids = self.redis.zrangebyscore(self.run_times_key, 0, timestamp) - if job_ids: - job_states = self.redis.hmget(self.jobs_key, *job_ids) - return self._reconstitute_jobs(six.moves.zip(job_ids, job_states)) - return [] - - def get_next_run_time(self): - next_run_time = self.redis.zrange(self.run_times_key, 0, 0, withscores=True) - if next_run_time: - return utc_timestamp_to_datetime(next_run_time[0][1]) - - def get_all_jobs(self): - job_states = self.redis.hgetall(self.jobs_key) - jobs = self._reconstitute_jobs(six.iteritems(job_states)) - paused_sort_key = datetime(9999, 12, 31, tzinfo=utc) - return sorted(jobs, key=lambda job: job.next_run_time or paused_sort_key) - - def add_job(self, job): - if self.redis.hexists(self.jobs_key, job.id): - raise ConflictingIdError(job.id) - - with self.redis.pipeline() as pipe: - pipe.multi() - pipe.hset(self.jobs_key, job.id, pickle.dumps(job.__getstate__(), - self.pickle_protocol)) - if job.next_run_time: - pipe.zadd(self.run_times_key, - {job.id: datetime_to_utc_timestamp(job.next_run_time)}) - - pipe.execute() - - def update_job(self, job): - if not self.redis.hexists(self.jobs_key, job.id): - raise JobLookupError(job.id) - - with self.redis.pipeline() as pipe: - pipe.hset(self.jobs_key, job.id, pickle.dumps(job.__getstate__(), - self.pickle_protocol)) - if job.next_run_time: - pipe.zadd(self.run_times_key, - {job.id: datetime_to_utc_timestamp(job.next_run_time)}) - else: - pipe.zrem(self.run_times_key, job.id) - - pipe.execute() - - def remove_job(self, job_id): - if not self.redis.hexists(self.jobs_key, job_id): - raise JobLookupError(job_id) - - with self.redis.pipeline() as pipe: - pipe.hdel(self.jobs_key, job_id) - pipe.zrem(self.run_times_key, job_id) - pipe.execute() - - def remove_all_jobs(self): - with self.redis.pipeline() as pipe: - pipe.delete(self.jobs_key) - pipe.delete(self.run_times_key) - pipe.execute() - - def shutdown(self): - self.redis.connection_pool.disconnect() - - def _reconstitute_job(self, job_state): - job_state = pickle.loads(job_state) - job = Job.__new__(Job) - job.__setstate__(job_state) - job._scheduler = self._scheduler - job._jobstore_alias = self._alias - return job - - def _reconstitute_jobs(self, job_states): - jobs = [] - failed_job_ids = [] - for job_id, job_state in job_states: - try: - jobs.append(self._reconstitute_job(job_state)) - except BaseException: - self._logger.exception('Unable to restore job "%s" -- removing it', job_id) - failed_job_ids.append(job_id) - - # Remove all the jobs we failed to restore - if failed_job_ids: - with self.redis.pipeline() as pipe: - pipe.hdel(self.jobs_key, *failed_job_ids) - pipe.zrem(self.run_times_key, *failed_job_ids) - pipe.execute() - - return jobs - - def __repr__(self): - return '<%s>' % self.__class__.__name__ diff --git a/telegramer/include/apscheduler/jobstores/rethinkdb.py b/telegramer/include/apscheduler/jobstores/rethinkdb.py deleted file mode 100644 index d8a78cd..0000000 --- a/telegramer/include/apscheduler/jobstores/rethinkdb.py +++ /dev/null @@ -1,155 +0,0 @@ -from __future__ import absolute_import - -from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError -from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime -from apscheduler.job import Job - -try: - import cPickle as pickle -except ImportError: # pragma: nocover - import pickle - -try: - from rethinkdb import RethinkDB -except ImportError: # pragma: nocover - raise ImportError('RethinkDBJobStore requires rethinkdb installed') - - -class RethinkDBJobStore(BaseJobStore): - """ - Stores jobs in a RethinkDB database. Any leftover keyword arguments are directly passed to - rethinkdb's `RethinkdbClient `_. - - Plugin alias: ``rethinkdb`` - - :param str database: database to store jobs in - :param str collection: collection to store jobs in - :param client: a :class:`rethinkdb.net.Connection` instance to use instead of providing - connection arguments - :param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the - highest available - """ - - def __init__(self, database='apscheduler', table='jobs', client=None, - pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args): - super(RethinkDBJobStore, self).__init__() - - if not database: - raise ValueError('The "database" parameter must not be empty') - if not table: - raise ValueError('The "table" parameter must not be empty') - - self.database = database - self.table_name = table - self.table = None - self.client = client - self.pickle_protocol = pickle_protocol - self.connect_args = connect_args - self.r = RethinkDB() - self.conn = None - - def start(self, scheduler, alias): - super(RethinkDBJobStore, self).start(scheduler, alias) - - if self.client: - self.conn = maybe_ref(self.client) - else: - self.conn = self.r.connect(db=self.database, **self.connect_args) - - if self.database not in self.r.db_list().run(self.conn): - self.r.db_create(self.database).run(self.conn) - - if self.table_name not in self.r.table_list().run(self.conn): - self.r.table_create(self.table_name).run(self.conn) - - if 'next_run_time' not in self.r.table(self.table_name).index_list().run(self.conn): - self.r.table(self.table_name).index_create('next_run_time').run(self.conn) - - self.table = self.r.db(self.database).table(self.table_name) - - def lookup_job(self, job_id): - results = list(self.table.get_all(job_id).pluck('job_state').run(self.conn)) - return self._reconstitute_job(results[0]['job_state']) if results else None - - def get_due_jobs(self, now): - return self._get_jobs(self.r.row['next_run_time'] <= datetime_to_utc_timestamp(now)) - - def get_next_run_time(self): - results = list( - self.table - .filter(self.r.row['next_run_time'] != None) # noqa - .order_by(self.r.asc('next_run_time')) - .map(lambda x: x['next_run_time']) - .limit(1) - .run(self.conn) - ) - return utc_timestamp_to_datetime(results[0]) if results else None - - def get_all_jobs(self): - jobs = self._get_jobs() - self._fix_paused_jobs_sorting(jobs) - return jobs - - def add_job(self, job): - job_dict = { - 'id': job.id, - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': self.r.binary(pickle.dumps(job.__getstate__(), self.pickle_protocol)) - } - results = self.table.insert(job_dict).run(self.conn) - if results['errors'] > 0: - raise ConflictingIdError(job.id) - - def update_job(self, job): - changes = { - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': self.r.binary(pickle.dumps(job.__getstate__(), self.pickle_protocol)) - } - results = self.table.get_all(job.id).update(changes).run(self.conn) - skipped = False in map(lambda x: results[x] == 0, results.keys()) - if results['skipped'] > 0 or results['errors'] > 0 or not skipped: - raise JobLookupError(job.id) - - def remove_job(self, job_id): - results = self.table.get_all(job_id).delete().run(self.conn) - if results['deleted'] + results['skipped'] != 1: - raise JobLookupError(job_id) - - def remove_all_jobs(self): - self.table.delete().run(self.conn) - - def shutdown(self): - self.conn.close() - - def _reconstitute_job(self, job_state): - job_state = pickle.loads(job_state) - job = Job.__new__(Job) - job.__setstate__(job_state) - job._scheduler = self._scheduler - job._jobstore_alias = self._alias - return job - - def _get_jobs(self, predicate=None): - jobs = [] - failed_job_ids = [] - query = (self.table.filter(self.r.row['next_run_time'] != None).filter(predicate) # noqa - if predicate else self.table) - query = query.order_by('next_run_time', 'id').pluck('id', 'job_state') - - for document in query.run(self.conn): - try: - jobs.append(self._reconstitute_job(document['job_state'])) - except Exception: - self._logger.exception('Unable to restore job "%s" -- removing it', document['id']) - failed_job_ids.append(document['id']) - - # Remove all the jobs we failed to restore - if failed_job_ids: - self.r.expr(failed_job_ids).for_each( - lambda job_id: self.table.get_all(job_id).delete()).run(self.conn) - - return jobs - - def __repr__(self): - connection = self.conn - return '<%s (connection=%s)>' % (self.__class__.__name__, connection) diff --git a/telegramer/include/apscheduler/jobstores/sqlalchemy.py b/telegramer/include/apscheduler/jobstores/sqlalchemy.py deleted file mode 100644 index dcfd3e5..0000000 --- a/telegramer/include/apscheduler/jobstores/sqlalchemy.py +++ /dev/null @@ -1,154 +0,0 @@ -from __future__ import absolute_import - -from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError -from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime -from apscheduler.job import Job - -try: - import cPickle as pickle -except ImportError: # pragma: nocover - import pickle - -try: - from sqlalchemy import ( - create_engine, Table, Column, MetaData, Unicode, Float, LargeBinary, select, and_) - from sqlalchemy.exc import IntegrityError - from sqlalchemy.sql.expression import null -except ImportError: # pragma: nocover - raise ImportError('SQLAlchemyJobStore requires SQLAlchemy installed') - - -class SQLAlchemyJobStore(BaseJobStore): - """ - Stores jobs in a database table using SQLAlchemy. - The table will be created if it doesn't exist in the database. - - Plugin alias: ``sqlalchemy`` - - :param str url: connection string (see - :ref:`SQLAlchemy documentation ` on this) - :param engine: an SQLAlchemy :class:`~sqlalchemy.engine.Engine` to use instead of creating a - new one based on ``url`` - :param str tablename: name of the table to store jobs in - :param metadata: a :class:`~sqlalchemy.schema.MetaData` instance to use instead of creating a - new one - :param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the - highest available - :param str tableschema: name of the (existing) schema in the target database where the table - should be - :param dict engine_options: keyword arguments to :func:`~sqlalchemy.create_engine` - (ignored if ``engine`` is given) - """ - - def __init__(self, url=None, engine=None, tablename='apscheduler_jobs', metadata=None, - pickle_protocol=pickle.HIGHEST_PROTOCOL, tableschema=None, engine_options=None): - super(SQLAlchemyJobStore, self).__init__() - self.pickle_protocol = pickle_protocol - metadata = maybe_ref(metadata) or MetaData() - - if engine: - self.engine = maybe_ref(engine) - elif url: - self.engine = create_engine(url, **(engine_options or {})) - else: - raise ValueError('Need either "engine" or "url" defined') - - # 191 = max key length in MySQL for InnoDB/utf8mb4 tables, - # 25 = precision that translates to an 8-byte float - self.jobs_t = Table( - tablename, metadata, - Column('id', Unicode(191, _warn_on_bytestring=False), primary_key=True), - Column('next_run_time', Float(25), index=True), - Column('job_state', LargeBinary, nullable=False), - schema=tableschema - ) - - def start(self, scheduler, alias): - super(SQLAlchemyJobStore, self).start(scheduler, alias) - self.jobs_t.create(self.engine, True) - - def lookup_job(self, job_id): - selectable = select([self.jobs_t.c.job_state]).where(self.jobs_t.c.id == job_id) - job_state = self.engine.execute(selectable).scalar() - return self._reconstitute_job(job_state) if job_state else None - - def get_due_jobs(self, now): - timestamp = datetime_to_utc_timestamp(now) - return self._get_jobs(self.jobs_t.c.next_run_time <= timestamp) - - def get_next_run_time(self): - selectable = select([self.jobs_t.c.next_run_time]).\ - where(self.jobs_t.c.next_run_time != null()).\ - order_by(self.jobs_t.c.next_run_time).limit(1) - next_run_time = self.engine.execute(selectable).scalar() - return utc_timestamp_to_datetime(next_run_time) - - def get_all_jobs(self): - jobs = self._get_jobs() - self._fix_paused_jobs_sorting(jobs) - return jobs - - def add_job(self, job): - insert = self.jobs_t.insert().values(**{ - 'id': job.id, - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': pickle.dumps(job.__getstate__(), self.pickle_protocol) - }) - try: - self.engine.execute(insert) - except IntegrityError: - raise ConflictingIdError(job.id) - - def update_job(self, job): - update = self.jobs_t.update().values(**{ - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': pickle.dumps(job.__getstate__(), self.pickle_protocol) - }).where(self.jobs_t.c.id == job.id) - result = self.engine.execute(update) - if result.rowcount == 0: - raise JobLookupError(job.id) - - def remove_job(self, job_id): - delete = self.jobs_t.delete().where(self.jobs_t.c.id == job_id) - result = self.engine.execute(delete) - if result.rowcount == 0: - raise JobLookupError(job_id) - - def remove_all_jobs(self): - delete = self.jobs_t.delete() - self.engine.execute(delete) - - def shutdown(self): - self.engine.dispose() - - def _reconstitute_job(self, job_state): - job_state = pickle.loads(job_state) - job_state['jobstore'] = self - job = Job.__new__(Job) - job.__setstate__(job_state) - job._scheduler = self._scheduler - job._jobstore_alias = self._alias - return job - - def _get_jobs(self, *conditions): - jobs = [] - selectable = select([self.jobs_t.c.id, self.jobs_t.c.job_state]).\ - order_by(self.jobs_t.c.next_run_time) - selectable = selectable.where(and_(*conditions)) if conditions else selectable - failed_job_ids = set() - for row in self.engine.execute(selectable): - try: - jobs.append(self._reconstitute_job(row.job_state)) - except BaseException: - self._logger.exception('Unable to restore job "%s" -- removing it', row.id) - failed_job_ids.add(row.id) - - # Remove all the jobs we failed to restore - if failed_job_ids: - delete = self.jobs_t.delete().where(self.jobs_t.c.id.in_(failed_job_ids)) - self.engine.execute(delete) - - return jobs - - def __repr__(self): - return '<%s (url=%s)>' % (self.__class__.__name__, self.engine.url) diff --git a/telegramer/include/apscheduler/jobstores/zookeeper.py b/telegramer/include/apscheduler/jobstores/zookeeper.py deleted file mode 100644 index 5253069..0000000 --- a/telegramer/include/apscheduler/jobstores/zookeeper.py +++ /dev/null @@ -1,178 +0,0 @@ -from __future__ import absolute_import - -from datetime import datetime - -from pytz import utc -from kazoo.exceptions import NoNodeError, NodeExistsError - -from apscheduler.jobstores.base import BaseJobStore, JobLookupError, ConflictingIdError -from apscheduler.util import maybe_ref, datetime_to_utc_timestamp, utc_timestamp_to_datetime -from apscheduler.job import Job - -try: - import cPickle as pickle -except ImportError: # pragma: nocover - import pickle - -try: - from kazoo.client import KazooClient -except ImportError: # pragma: nocover - raise ImportError('ZooKeeperJobStore requires Kazoo installed') - - -class ZooKeeperJobStore(BaseJobStore): - """ - Stores jobs in a ZooKeeper tree. Any leftover keyword arguments are directly passed to - kazoo's `KazooClient - `_. - - Plugin alias: ``zookeeper`` - - :param str path: path to store jobs in - :param client: a :class:`~kazoo.client.KazooClient` instance to use instead of - providing connection arguments - :param int pickle_protocol: pickle protocol level to use (for serialization), defaults to the - highest available - """ - - def __init__(self, path='/apscheduler', client=None, close_connection_on_exit=False, - pickle_protocol=pickle.HIGHEST_PROTOCOL, **connect_args): - super(ZooKeeperJobStore, self).__init__() - self.pickle_protocol = pickle_protocol - self.close_connection_on_exit = close_connection_on_exit - - if not path: - raise ValueError('The "path" parameter must not be empty') - - self.path = path - - if client: - self.client = maybe_ref(client) - else: - self.client = KazooClient(**connect_args) - self._ensured_path = False - - def _ensure_paths(self): - if not self._ensured_path: - self.client.ensure_path(self.path) - self._ensured_path = True - - def start(self, scheduler, alias): - super(ZooKeeperJobStore, self).start(scheduler, alias) - if not self.client.connected: - self.client.start() - - def lookup_job(self, job_id): - self._ensure_paths() - node_path = self.path + "/" + str(job_id) - try: - content, _ = self.client.get(node_path) - doc = pickle.loads(content) - job = self._reconstitute_job(doc['job_state']) - return job - except BaseException: - return None - - def get_due_jobs(self, now): - timestamp = datetime_to_utc_timestamp(now) - jobs = [job_def['job'] for job_def in self._get_jobs() - if job_def['next_run_time'] is not None and job_def['next_run_time'] <= timestamp] - return jobs - - def get_next_run_time(self): - next_runs = [job_def['next_run_time'] for job_def in self._get_jobs() - if job_def['next_run_time'] is not None] - return utc_timestamp_to_datetime(min(next_runs)) if len(next_runs) > 0 else None - - def get_all_jobs(self): - jobs = [job_def['job'] for job_def in self._get_jobs()] - self._fix_paused_jobs_sorting(jobs) - return jobs - - def add_job(self, job): - self._ensure_paths() - node_path = self.path + "/" + str(job.id) - value = { - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': job.__getstate__() - } - data = pickle.dumps(value, self.pickle_protocol) - try: - self.client.create(node_path, value=data) - except NodeExistsError: - raise ConflictingIdError(job.id) - - def update_job(self, job): - self._ensure_paths() - node_path = self.path + "/" + str(job.id) - changes = { - 'next_run_time': datetime_to_utc_timestamp(job.next_run_time), - 'job_state': job.__getstate__() - } - data = pickle.dumps(changes, self.pickle_protocol) - try: - self.client.set(node_path, value=data) - except NoNodeError: - raise JobLookupError(job.id) - - def remove_job(self, job_id): - self._ensure_paths() - node_path = self.path + "/" + str(job_id) - try: - self.client.delete(node_path) - except NoNodeError: - raise JobLookupError(job_id) - - def remove_all_jobs(self): - try: - self.client.delete(self.path, recursive=True) - except NoNodeError: - pass - self._ensured_path = False - - def shutdown(self): - if self.close_connection_on_exit: - self.client.stop() - self.client.close() - - def _reconstitute_job(self, job_state): - job_state = job_state - job = Job.__new__(Job) - job.__setstate__(job_state) - job._scheduler = self._scheduler - job._jobstore_alias = self._alias - return job - - def _get_jobs(self): - self._ensure_paths() - jobs = [] - failed_job_ids = [] - all_ids = self.client.get_children(self.path) - for node_name in all_ids: - try: - node_path = self.path + "/" + node_name - content, _ = self.client.get(node_path) - doc = pickle.loads(content) - job_def = { - 'job_id': node_name, - 'next_run_time': doc['next_run_time'] if doc['next_run_time'] else None, - 'job_state': doc['job_state'], - 'job': self._reconstitute_job(doc['job_state']), - 'creation_time': _.ctime - } - jobs.append(job_def) - except BaseException: - self._logger.exception('Unable to restore job "%s" -- removing it' % node_name) - failed_job_ids.append(node_name) - - # Remove all the jobs we failed to restore - if failed_job_ids: - for failed_id in failed_job_ids: - self.remove_job(failed_id) - paused_sort_key = datetime(9999, 12, 31, tzinfo=utc) - return sorted(jobs, key=lambda job_def: (job_def['job'].next_run_time or paused_sort_key, - job_def['creation_time'])) - - def __repr__(self): - self._logger.exception('<%s (client=%s)>' % (self.__class__.__name__, self.client)) - return '<%s (client=%s)>' % (self.__class__.__name__, self.client) diff --git a/telegramer/include/apscheduler/schedulers/__init__.py b/telegramer/include/apscheduler/schedulers/__init__.py deleted file mode 100644 index bd8a790..0000000 --- a/telegramer/include/apscheduler/schedulers/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -class SchedulerAlreadyRunningError(Exception): - """Raised when attempting to start or configure the scheduler when it's already running.""" - - def __str__(self): - return 'Scheduler is already running' - - -class SchedulerNotRunningError(Exception): - """Raised when attempting to shutdown the scheduler when it's not running.""" - - def __str__(self): - return 'Scheduler is not running' diff --git a/telegramer/include/apscheduler/schedulers/asyncio.py b/telegramer/include/apscheduler/schedulers/asyncio.py deleted file mode 100644 index 70ebede..0000000 --- a/telegramer/include/apscheduler/schedulers/asyncio.py +++ /dev/null @@ -1,74 +0,0 @@ -from __future__ import absolute_import -from functools import wraps, partial - -from apscheduler.schedulers.base import BaseScheduler -from apscheduler.util import maybe_ref - -try: - import asyncio -except ImportError: # pragma: nocover - try: - import trollius as asyncio - except ImportError: - raise ImportError( - 'AsyncIOScheduler requires either Python 3.4 or the asyncio package installed') - - -def run_in_event_loop(func): - @wraps(func) - def wrapper(self, *args, **kwargs): - wrapped = partial(func, self, *args, **kwargs) - self._eventloop.call_soon_threadsafe(wrapped) - return wrapper - - -class AsyncIOScheduler(BaseScheduler): - """ - A scheduler that runs on an asyncio (:pep:`3156`) event loop. - - The default executor can run jobs based on native coroutines (``async def``). - - Extra options: - - ============== ============================================================= - ``event_loop`` AsyncIO event loop to use (defaults to the global event loop) - ============== ============================================================= - """ - - _eventloop = None - _timeout = None - - def start(self, paused=False): - if not self._eventloop: - self._eventloop = asyncio.get_event_loop() - - super(AsyncIOScheduler, self).start(paused) - - @run_in_event_loop - def shutdown(self, wait=True): - super(AsyncIOScheduler, self).shutdown(wait) - self._stop_timer() - - def _configure(self, config): - self._eventloop = maybe_ref(config.pop('event_loop', None)) - super(AsyncIOScheduler, self)._configure(config) - - def _start_timer(self, wait_seconds): - self._stop_timer() - if wait_seconds is not None: - self._timeout = self._eventloop.call_later(wait_seconds, self.wakeup) - - def _stop_timer(self): - if self._timeout: - self._timeout.cancel() - del self._timeout - - @run_in_event_loop - def wakeup(self): - self._stop_timer() - wait_seconds = self._process_jobs() - self._start_timer(wait_seconds) - - def _create_default_executor(self): - from apscheduler.executors.asyncio import AsyncIOExecutor - return AsyncIOExecutor() diff --git a/telegramer/include/apscheduler/schedulers/background.py b/telegramer/include/apscheduler/schedulers/background.py deleted file mode 100644 index bb8f77d..0000000 --- a/telegramer/include/apscheduler/schedulers/background.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import absolute_import - -from threading import Thread, Event - -from apscheduler.schedulers.base import BaseScheduler -from apscheduler.schedulers.blocking import BlockingScheduler -from apscheduler.util import asbool - - -class BackgroundScheduler(BlockingScheduler): - """ - A scheduler that runs in the background using a separate thread - (:meth:`~apscheduler.schedulers.base.BaseScheduler.start` will return immediately). - - Extra options: - - ========== ============================================================================= - ``daemon`` Set the ``daemon`` option in the background thread (defaults to ``True``, see - `the documentation - `_ - for further details) - ========== ============================================================================= - """ - - _thread = None - - def _configure(self, config): - self._daemon = asbool(config.pop('daemon', True)) - super(BackgroundScheduler, self)._configure(config) - - def start(self, *args, **kwargs): - if self._event is None or self._event.is_set(): - self._event = Event() - - BaseScheduler.start(self, *args, **kwargs) - self._thread = Thread(target=self._main_loop, name='APScheduler') - self._thread.daemon = self._daemon - self._thread.start() - - def shutdown(self, *args, **kwargs): - super(BackgroundScheduler, self).shutdown(*args, **kwargs) - self._thread.join() - del self._thread diff --git a/telegramer/include/apscheduler/schedulers/base.py b/telegramer/include/apscheduler/schedulers/base.py deleted file mode 100644 index 444de8e..0000000 --- a/telegramer/include/apscheduler/schedulers/base.py +++ /dev/null @@ -1,1026 +0,0 @@ -from __future__ import print_function - -from abc import ABCMeta, abstractmethod -from threading import RLock -from datetime import datetime, timedelta -from logging import getLogger -import warnings -import sys - -from pkg_resources import iter_entry_points -from tzlocal import get_localzone -import six - -from apscheduler.schedulers import SchedulerAlreadyRunningError, SchedulerNotRunningError -from apscheduler.executors.base import MaxInstancesReachedError, BaseExecutor -from apscheduler.executors.pool import ThreadPoolExecutor -from apscheduler.jobstores.base import ConflictingIdError, JobLookupError, BaseJobStore -from apscheduler.jobstores.memory import MemoryJobStore -from apscheduler.job import Job -from apscheduler.triggers.base import BaseTrigger -from apscheduler.util import ( - asbool, asint, astimezone, maybe_ref, timedelta_seconds, undefined, TIMEOUT_MAX) -from apscheduler.events import ( - SchedulerEvent, JobEvent, JobSubmissionEvent, EVENT_SCHEDULER_START, EVENT_SCHEDULER_SHUTDOWN, - EVENT_JOBSTORE_ADDED, EVENT_JOBSTORE_REMOVED, EVENT_ALL, EVENT_JOB_MODIFIED, EVENT_JOB_REMOVED, - EVENT_JOB_ADDED, EVENT_EXECUTOR_ADDED, EVENT_EXECUTOR_REMOVED, EVENT_ALL_JOBS_REMOVED, - EVENT_JOB_SUBMITTED, EVENT_JOB_MAX_INSTANCES, EVENT_SCHEDULER_RESUMED, EVENT_SCHEDULER_PAUSED) - -try: - from collections.abc import MutableMapping -except ImportError: - from collections import MutableMapping - -#: constant indicating a scheduler's stopped state -STATE_STOPPED = 0 -#: constant indicating a scheduler's running state (started and processing jobs) -STATE_RUNNING = 1 -#: constant indicating a scheduler's paused state (started but not processing jobs) -STATE_PAUSED = 2 - - -class BaseScheduler(six.with_metaclass(ABCMeta)): - """ - Abstract base class for all schedulers. - - Takes the following keyword arguments: - - :param str|logging.Logger logger: logger to use for the scheduler's logging (defaults to - apscheduler.scheduler) - :param str|datetime.tzinfo timezone: the default time zone (defaults to the local timezone) - :param int|float jobstore_retry_interval: the minimum number of seconds to wait between - retries in the scheduler's main loop if the job store raises an exception when getting - the list of due jobs - :param dict job_defaults: default values for newly added jobs - :param dict jobstores: a dictionary of job store alias -> job store instance or configuration - dict - :param dict executors: a dictionary of executor alias -> executor instance or configuration - dict - - :ivar int state: current running state of the scheduler (one of the following constants from - ``apscheduler.schedulers.base``: ``STATE_STOPPED``, ``STATE_RUNNING``, ``STATE_PAUSED``) - - .. seealso:: :ref:`scheduler-config` - """ - - _trigger_plugins = dict((ep.name, ep) for ep in iter_entry_points('apscheduler.triggers')) - _trigger_classes = {} - _executor_plugins = dict((ep.name, ep) for ep in iter_entry_points('apscheduler.executors')) - _executor_classes = {} - _jobstore_plugins = dict((ep.name, ep) for ep in iter_entry_points('apscheduler.jobstores')) - _jobstore_classes = {} - - # - # Public API - # - - def __init__(self, gconfig={}, **options): - super(BaseScheduler, self).__init__() - self._executors = {} - self._executors_lock = self._create_lock() - self._jobstores = {} - self._jobstores_lock = self._create_lock() - self._listeners = [] - self._listeners_lock = self._create_lock() - self._pending_jobs = [] - self.state = STATE_STOPPED - self.configure(gconfig, **options) - - def __getstate__(self): - raise TypeError("Schedulers cannot be serialized. Ensure that you are not passing a " - "scheduler instance as an argument to a job, or scheduling an instance " - "method where the instance contains a scheduler as an attribute.") - - def configure(self, gconfig={}, prefix='apscheduler.', **options): - """ - Reconfigures the scheduler with the given options. - - Can only be done when the scheduler isn't running. - - :param dict gconfig: a "global" configuration dictionary whose values can be overridden by - keyword arguments to this method - :param str|unicode prefix: pick only those keys from ``gconfig`` that are prefixed with - this string (pass an empty string or ``None`` to use all keys) - :raises SchedulerAlreadyRunningError: if the scheduler is already running - - """ - if self.state != STATE_STOPPED: - raise SchedulerAlreadyRunningError - - # If a non-empty prefix was given, strip it from the keys in the - # global configuration dict - if prefix: - prefixlen = len(prefix) - gconfig = dict((key[prefixlen:], value) for key, value in six.iteritems(gconfig) - if key.startswith(prefix)) - - # Create a structure from the dotted options - # (e.g. "a.b.c = d" -> {'a': {'b': {'c': 'd'}}}) - config = {} - for key, value in six.iteritems(gconfig): - parts = key.split('.') - parent = config - key = parts.pop(0) - while parts: - parent = parent.setdefault(key, {}) - key = parts.pop(0) - parent[key] = value - - # Override any options with explicit keyword arguments - config.update(options) - self._configure(config) - - def start(self, paused=False): - """ - Start the configured executors and job stores and begin processing scheduled jobs. - - :param bool paused: if ``True``, don't start job processing until :meth:`resume` is called - :raises SchedulerAlreadyRunningError: if the scheduler is already running - :raises RuntimeError: if running under uWSGI with threads disabled - - """ - if self.state != STATE_STOPPED: - raise SchedulerAlreadyRunningError - - self._check_uwsgi() - - with self._executors_lock: - # Create a default executor if nothing else is configured - if 'default' not in self._executors: - self.add_executor(self._create_default_executor(), 'default') - - # Start all the executors - for alias, executor in six.iteritems(self._executors): - executor.start(self, alias) - - with self._jobstores_lock: - # Create a default job store if nothing else is configured - if 'default' not in self._jobstores: - self.add_jobstore(self._create_default_jobstore(), 'default') - - # Start all the job stores - for alias, store in six.iteritems(self._jobstores): - store.start(self, alias) - - # Schedule all pending jobs - for job, jobstore_alias, replace_existing in self._pending_jobs: - self._real_add_job(job, jobstore_alias, replace_existing) - del self._pending_jobs[:] - - self.state = STATE_PAUSED if paused else STATE_RUNNING - self._logger.info('Scheduler started') - self._dispatch_event(SchedulerEvent(EVENT_SCHEDULER_START)) - - if not paused: - self.wakeup() - - @abstractmethod - def shutdown(self, wait=True): - """ - Shuts down the scheduler, along with its executors and job stores. - - Does not interrupt any currently running jobs. - - :param bool wait: ``True`` to wait until all currently executing jobs have finished - :raises SchedulerNotRunningError: if the scheduler has not been started yet - - """ - if self.state == STATE_STOPPED: - raise SchedulerNotRunningError - - self.state = STATE_STOPPED - - # Shut down all executors - with self._executors_lock, self._jobstores_lock: - for executor in six.itervalues(self._executors): - executor.shutdown(wait) - - # Shut down all job stores - for jobstore in six.itervalues(self._jobstores): - jobstore.shutdown() - - self._logger.info('Scheduler has been shut down') - self._dispatch_event(SchedulerEvent(EVENT_SCHEDULER_SHUTDOWN)) - - def pause(self): - """ - Pause job processing in the scheduler. - - This will prevent the scheduler from waking up to do job processing until :meth:`resume` - is called. It will not however stop any already running job processing. - - """ - if self.state == STATE_STOPPED: - raise SchedulerNotRunningError - elif self.state == STATE_RUNNING: - self.state = STATE_PAUSED - self._logger.info('Paused scheduler job processing') - self._dispatch_event(SchedulerEvent(EVENT_SCHEDULER_PAUSED)) - - def resume(self): - """Resume job processing in the scheduler.""" - if self.state == STATE_STOPPED: - raise SchedulerNotRunningError - elif self.state == STATE_PAUSED: - self.state = STATE_RUNNING - self._logger.info('Resumed scheduler job processing') - self._dispatch_event(SchedulerEvent(EVENT_SCHEDULER_RESUMED)) - self.wakeup() - - @property - def running(self): - """ - Return ``True`` if the scheduler has been started. - - This is a shortcut for ``scheduler.state != STATE_STOPPED``. - - """ - return self.state != STATE_STOPPED - - def add_executor(self, executor, alias='default', **executor_opts): - """ - Adds an executor to this scheduler. - - Any extra keyword arguments will be passed to the executor plugin's constructor, assuming - that the first argument is the name of an executor plugin. - - :param str|unicode|apscheduler.executors.base.BaseExecutor executor: either an executor - instance or the name of an executor plugin - :param str|unicode alias: alias for the scheduler - :raises ValueError: if there is already an executor by the given alias - - """ - with self._executors_lock: - if alias in self._executors: - raise ValueError('This scheduler already has an executor by the alias of "%s"' % - alias) - - if isinstance(executor, BaseExecutor): - self._executors[alias] = executor - elif isinstance(executor, six.string_types): - self._executors[alias] = executor = self._create_plugin_instance( - 'executor', executor, executor_opts) - else: - raise TypeError('Expected an executor instance or a string, got %s instead' % - executor.__class__.__name__) - - # Start the executor right away if the scheduler is running - if self.state != STATE_STOPPED: - executor.start(self, alias) - - self._dispatch_event(SchedulerEvent(EVENT_EXECUTOR_ADDED, alias)) - - def remove_executor(self, alias, shutdown=True): - """ - Removes the executor by the given alias from this scheduler. - - :param str|unicode alias: alias of the executor - :param bool shutdown: ``True`` to shut down the executor after - removing it - - """ - with self._executors_lock: - executor = self._lookup_executor(alias) - del self._executors[alias] - - if shutdown: - executor.shutdown() - - self._dispatch_event(SchedulerEvent(EVENT_EXECUTOR_REMOVED, alias)) - - def add_jobstore(self, jobstore, alias='default', **jobstore_opts): - """ - Adds a job store to this scheduler. - - Any extra keyword arguments will be passed to the job store plugin's constructor, assuming - that the first argument is the name of a job store plugin. - - :param str|unicode|apscheduler.jobstores.base.BaseJobStore jobstore: job store to be added - :param str|unicode alias: alias for the job store - :raises ValueError: if there is already a job store by the given alias - - """ - with self._jobstores_lock: - if alias in self._jobstores: - raise ValueError('This scheduler already has a job store by the alias of "%s"' % - alias) - - if isinstance(jobstore, BaseJobStore): - self._jobstores[alias] = jobstore - elif isinstance(jobstore, six.string_types): - self._jobstores[alias] = jobstore = self._create_plugin_instance( - 'jobstore', jobstore, jobstore_opts) - else: - raise TypeError('Expected a job store instance or a string, got %s instead' % - jobstore.__class__.__name__) - - # Start the job store right away if the scheduler isn't stopped - if self.state != STATE_STOPPED: - jobstore.start(self, alias) - - # Notify listeners that a new job store has been added - self._dispatch_event(SchedulerEvent(EVENT_JOBSTORE_ADDED, alias)) - - # Notify the scheduler so it can scan the new job store for jobs - if self.state != STATE_STOPPED: - self.wakeup() - - def remove_jobstore(self, alias, shutdown=True): - """ - Removes the job store by the given alias from this scheduler. - - :param str|unicode alias: alias of the job store - :param bool shutdown: ``True`` to shut down the job store after removing it - - """ - with self._jobstores_lock: - jobstore = self._lookup_jobstore(alias) - del self._jobstores[alias] - - if shutdown: - jobstore.shutdown() - - self._dispatch_event(SchedulerEvent(EVENT_JOBSTORE_REMOVED, alias)) - - def add_listener(self, callback, mask=EVENT_ALL): - """ - add_listener(callback, mask=EVENT_ALL) - - Adds a listener for scheduler events. - - When a matching event occurs, ``callback`` is executed with the event object as its - sole argument. If the ``mask`` parameter is not provided, the callback will receive events - of all types. - - :param callback: any callable that takes one argument - :param int mask: bitmask that indicates which events should be - listened to - - .. seealso:: :mod:`apscheduler.events` - .. seealso:: :ref:`scheduler-events` - - """ - with self._listeners_lock: - self._listeners.append((callback, mask)) - - def remove_listener(self, callback): - """Removes a previously added event listener.""" - - with self._listeners_lock: - for i, (cb, _) in enumerate(self._listeners): - if callback == cb: - del self._listeners[i] - - def add_job(self, func, trigger=None, args=None, kwargs=None, id=None, name=None, - misfire_grace_time=undefined, coalesce=undefined, max_instances=undefined, - next_run_time=undefined, jobstore='default', executor='default', - replace_existing=False, **trigger_args): - """ - add_job(func, trigger=None, args=None, kwargs=None, id=None, \ - name=None, misfire_grace_time=undefined, coalesce=undefined, \ - max_instances=undefined, next_run_time=undefined, \ - jobstore='default', executor='default', \ - replace_existing=False, **trigger_args) - - Adds the given job to the job list and wakes up the scheduler if it's already running. - - Any option that defaults to ``undefined`` will be replaced with the corresponding default - value when the job is scheduled (which happens when the scheduler is started, or - immediately if the scheduler is already running). - - The ``func`` argument can be given either as a callable object or a textual reference in - the ``package.module:some.object`` format, where the first half (separated by ``:``) is an - importable module and the second half is a reference to the callable object, relative to - the module. - - The ``trigger`` argument can either be: - #. the alias name of the trigger (e.g. ``date``, ``interval`` or ``cron``), in which case - any extra keyword arguments to this method are passed on to the trigger's constructor - #. an instance of a trigger class - - :param func: callable (or a textual reference to one) to run at the given time - :param str|apscheduler.triggers.base.BaseTrigger trigger: trigger that determines when - ``func`` is called - :param list|tuple args: list of positional arguments to call func with - :param dict kwargs: dict of keyword arguments to call func with - :param str|unicode id: explicit identifier for the job (for modifying it later) - :param str|unicode name: textual description of the job - :param int misfire_grace_time: seconds after the designated runtime that the job is still - allowed to be run (or ``None`` to allow the job to run no matter how late it is) - :param bool coalesce: run once instead of many times if the scheduler determines that the - job should be run more than once in succession - :param int max_instances: maximum number of concurrently running instances allowed for this - job - :param datetime next_run_time: when to first run the job, regardless of the trigger (pass - ``None`` to add the job as paused) - :param str|unicode jobstore: alias of the job store to store the job in - :param str|unicode executor: alias of the executor to run the job with - :param bool replace_existing: ``True`` to replace an existing job with the same ``id`` - (but retain the number of runs from the existing one) - :rtype: Job - - """ - job_kwargs = { - 'trigger': self._create_trigger(trigger, trigger_args), - 'executor': executor, - 'func': func, - 'args': tuple(args) if args is not None else (), - 'kwargs': dict(kwargs) if kwargs is not None else {}, - 'id': id, - 'name': name, - 'misfire_grace_time': misfire_grace_time, - 'coalesce': coalesce, - 'max_instances': max_instances, - 'next_run_time': next_run_time - } - job_kwargs = dict((key, value) for key, value in six.iteritems(job_kwargs) if - value is not undefined) - job = Job(self, **job_kwargs) - - # Don't really add jobs to job stores before the scheduler is up and running - with self._jobstores_lock: - if self.state == STATE_STOPPED: - self._pending_jobs.append((job, jobstore, replace_existing)) - self._logger.info('Adding job tentatively -- it will be properly scheduled when ' - 'the scheduler starts') - else: - self._real_add_job(job, jobstore, replace_existing) - - return job - - def scheduled_job(self, trigger, args=None, kwargs=None, id=None, name=None, - misfire_grace_time=undefined, coalesce=undefined, max_instances=undefined, - next_run_time=undefined, jobstore='default', executor='default', - **trigger_args): - """ - scheduled_job(trigger, args=None, kwargs=None, id=None, \ - name=None, misfire_grace_time=undefined, \ - coalesce=undefined, max_instances=undefined, \ - next_run_time=undefined, jobstore='default', \ - executor='default',**trigger_args) - - A decorator version of :meth:`add_job`, except that ``replace_existing`` is always - ``True``. - - .. important:: The ``id`` argument must be given if scheduling a job in a persistent job - store. The scheduler cannot, however, enforce this requirement. - - """ - def inner(func): - self.add_job(func, trigger, args, kwargs, id, name, misfire_grace_time, coalesce, - max_instances, next_run_time, jobstore, executor, True, **trigger_args) - return func - return inner - - def modify_job(self, job_id, jobstore=None, **changes): - """ - Modifies the properties of a single job. - - Modifications are passed to this method as extra keyword arguments. - - :param str|unicode job_id: the identifier of the job - :param str|unicode jobstore: alias of the job store that contains the job - :return Job: the relevant job instance - - """ - with self._jobstores_lock: - job, jobstore = self._lookup_job(job_id, jobstore) - job._modify(**changes) - if jobstore: - self._lookup_jobstore(jobstore).update_job(job) - - self._dispatch_event(JobEvent(EVENT_JOB_MODIFIED, job_id, jobstore)) - - # Wake up the scheduler since the job's next run time may have been changed - if self.state == STATE_RUNNING: - self.wakeup() - - return job - - def reschedule_job(self, job_id, jobstore=None, trigger=None, **trigger_args): - """ - Constructs a new trigger for a job and updates its next run time. - - Extra keyword arguments are passed directly to the trigger's constructor. - - :param str|unicode job_id: the identifier of the job - :param str|unicode jobstore: alias of the job store that contains the job - :param trigger: alias of the trigger type or a trigger instance - :return Job: the relevant job instance - - """ - trigger = self._create_trigger(trigger, trigger_args) - now = datetime.now(self.timezone) - next_run_time = trigger.get_next_fire_time(None, now) - return self.modify_job(job_id, jobstore, trigger=trigger, next_run_time=next_run_time) - - def pause_job(self, job_id, jobstore=None): - """ - Causes the given job not to be executed until it is explicitly resumed. - - :param str|unicode job_id: the identifier of the job - :param str|unicode jobstore: alias of the job store that contains the job - :return Job: the relevant job instance - - """ - return self.modify_job(job_id, jobstore, next_run_time=None) - - def resume_job(self, job_id, jobstore=None): - """ - Resumes the schedule of the given job, or removes the job if its schedule is finished. - - :param str|unicode job_id: the identifier of the job - :param str|unicode jobstore: alias of the job store that contains the job - :return Job|None: the relevant job instance if the job was rescheduled, or ``None`` if no - next run time could be calculated and the job was removed - - """ - with self._jobstores_lock: - job, jobstore = self._lookup_job(job_id, jobstore) - now = datetime.now(self.timezone) - next_run_time = job.trigger.get_next_fire_time(None, now) - if next_run_time: - return self.modify_job(job_id, jobstore, next_run_time=next_run_time) - else: - self.remove_job(job.id, jobstore) - - def get_jobs(self, jobstore=None, pending=None): - """ - Returns a list of pending jobs (if the scheduler hasn't been started yet) and scheduled - jobs, either from a specific job store or from all of them. - - If the scheduler has not been started yet, only pending jobs can be returned because the - job stores haven't been started yet either. - - :param str|unicode jobstore: alias of the job store - :param bool pending: **DEPRECATED** - :rtype: list[Job] - - """ - if pending is not None: - warnings.warn('The "pending" option is deprecated -- get_jobs() always returns ' - 'scheduled jobs if the scheduler has been started and pending jobs ' - 'otherwise', DeprecationWarning) - - with self._jobstores_lock: - jobs = [] - if self.state == STATE_STOPPED: - for job, alias, replace_existing in self._pending_jobs: - if jobstore is None or alias == jobstore: - jobs.append(job) - else: - for alias, store in six.iteritems(self._jobstores): - if jobstore is None or alias == jobstore: - jobs.extend(store.get_all_jobs()) - - return jobs - - def get_job(self, job_id, jobstore=None): - """ - Returns the Job that matches the given ``job_id``. - - :param str|unicode job_id: the identifier of the job - :param str|unicode jobstore: alias of the job store that most likely contains the job - :return: the Job by the given ID, or ``None`` if it wasn't found - :rtype: Job - - """ - with self._jobstores_lock: - try: - return self._lookup_job(job_id, jobstore)[0] - except JobLookupError: - return - - def remove_job(self, job_id, jobstore=None): - """ - Removes a job, preventing it from being run any more. - - :param str|unicode job_id: the identifier of the job - :param str|unicode jobstore: alias of the job store that contains the job - :raises JobLookupError: if the job was not found - - """ - jobstore_alias = None - with self._jobstores_lock: - # Check if the job is among the pending jobs - if self.state == STATE_STOPPED: - for i, (job, alias, replace_existing) in enumerate(self._pending_jobs): - if job.id == job_id and jobstore in (None, alias): - del self._pending_jobs[i] - jobstore_alias = alias - break - else: - # Otherwise, try to remove it from each store until it succeeds or we run out of - # stores to check - for alias, store in six.iteritems(self._jobstores): - if jobstore in (None, alias): - try: - store.remove_job(job_id) - jobstore_alias = alias - break - except JobLookupError: - continue - - if jobstore_alias is None: - raise JobLookupError(job_id) - - # Notify listeners that a job has been removed - event = JobEvent(EVENT_JOB_REMOVED, job_id, jobstore_alias) - self._dispatch_event(event) - - self._logger.info('Removed job %s', job_id) - - def remove_all_jobs(self, jobstore=None): - """ - Removes all jobs from the specified job store, or all job stores if none is given. - - :param str|unicode jobstore: alias of the job store - - """ - with self._jobstores_lock: - if self.state == STATE_STOPPED: - if jobstore: - self._pending_jobs = [pending for pending in self._pending_jobs if - pending[1] != jobstore] - else: - self._pending_jobs = [] - else: - for alias, store in six.iteritems(self._jobstores): - if jobstore in (None, alias): - store.remove_all_jobs() - - self._dispatch_event(SchedulerEvent(EVENT_ALL_JOBS_REMOVED, jobstore)) - - def print_jobs(self, jobstore=None, out=None): - """ - print_jobs(jobstore=None, out=sys.stdout) - - Prints out a textual listing of all jobs currently scheduled on either all job stores or - just a specific one. - - :param str|unicode jobstore: alias of the job store, ``None`` to list jobs from all stores - :param file out: a file-like object to print to (defaults to **sys.stdout** if nothing is - given) - - """ - out = out or sys.stdout - with self._jobstores_lock: - if self.state == STATE_STOPPED: - print(u'Pending jobs:', file=out) - if self._pending_jobs: - for job, jobstore_alias, replace_existing in self._pending_jobs: - if jobstore in (None, jobstore_alias): - print(u' %s' % job, file=out) - else: - print(u' No pending jobs', file=out) - else: - for alias, store in sorted(six.iteritems(self._jobstores)): - if jobstore in (None, alias): - print(u'Jobstore %s:' % alias, file=out) - jobs = store.get_all_jobs() - if jobs: - for job in jobs: - print(u' %s' % job, file=out) - else: - print(u' No scheduled jobs', file=out) - - @abstractmethod - def wakeup(self): - """ - Notifies the scheduler that there may be jobs due for execution. - Triggers :meth:`_process_jobs` to be run in an implementation specific manner. - """ - - # - # Private API - # - - def _configure(self, config): - # Set general options - self._logger = maybe_ref(config.pop('logger', None)) or getLogger('apscheduler.scheduler') - self.timezone = astimezone(config.pop('timezone', None)) or get_localzone() - self.jobstore_retry_interval = float(config.pop('jobstore_retry_interval', 10)) - - # Set the job defaults - job_defaults = config.get('job_defaults', {}) - self._job_defaults = { - 'misfire_grace_time': asint(job_defaults.get('misfire_grace_time', 1)), - 'coalesce': asbool(job_defaults.get('coalesce', True)), - 'max_instances': asint(job_defaults.get('max_instances', 1)) - } - - # Configure executors - self._executors.clear() - for alias, value in six.iteritems(config.get('executors', {})): - if isinstance(value, BaseExecutor): - self.add_executor(value, alias) - elif isinstance(value, MutableMapping): - executor_class = value.pop('class', None) - plugin = value.pop('type', None) - if plugin: - executor = self._create_plugin_instance('executor', plugin, value) - elif executor_class: - cls = maybe_ref(executor_class) - executor = cls(**value) - else: - raise ValueError( - 'Cannot create executor "%s" -- either "type" or "class" must be defined' % - alias) - - self.add_executor(executor, alias) - else: - raise TypeError( - "Expected executor instance or dict for executors['%s'], got %s instead" % - (alias, value.__class__.__name__)) - - # Configure job stores - self._jobstores.clear() - for alias, value in six.iteritems(config.get('jobstores', {})): - if isinstance(value, BaseJobStore): - self.add_jobstore(value, alias) - elif isinstance(value, MutableMapping): - jobstore_class = value.pop('class', None) - plugin = value.pop('type', None) - if plugin: - jobstore = self._create_plugin_instance('jobstore', plugin, value) - elif jobstore_class: - cls = maybe_ref(jobstore_class) - jobstore = cls(**value) - else: - raise ValueError( - 'Cannot create job store "%s" -- either "type" or "class" must be ' - 'defined' % alias) - - self.add_jobstore(jobstore, alias) - else: - raise TypeError( - "Expected job store instance or dict for jobstores['%s'], got %s instead" % - (alias, value.__class__.__name__)) - - def _create_default_executor(self): - """Creates a default executor store, specific to the particular scheduler type.""" - return ThreadPoolExecutor() - - def _create_default_jobstore(self): - """Creates a default job store, specific to the particular scheduler type.""" - return MemoryJobStore() - - def _lookup_executor(self, alias): - """ - Returns the executor instance by the given name from the list of executors that were added - to this scheduler. - - :type alias: str - :raises KeyError: if no executor by the given alias is not found - - """ - try: - return self._executors[alias] - except KeyError: - raise KeyError('No such executor: %s' % alias) - - def _lookup_jobstore(self, alias): - """ - Returns the job store instance by the given name from the list of job stores that were - added to this scheduler. - - :type alias: str - :raises KeyError: if no job store by the given alias is not found - - """ - try: - return self._jobstores[alias] - except KeyError: - raise KeyError('No such job store: %s' % alias) - - def _lookup_job(self, job_id, jobstore_alias): - """ - Finds a job by its ID. - - :type job_id: str - :param str jobstore_alias: alias of a job store to look in - :return tuple[Job, str]: a tuple of job, jobstore alias (jobstore alias is None in case of - a pending job) - :raises JobLookupError: if no job by the given ID is found. - - """ - if self.state == STATE_STOPPED: - # Check if the job is among the pending jobs - for job, alias, replace_existing in self._pending_jobs: - if job.id == job_id: - return job, None - else: - # Look in all job stores - for alias, store in six.iteritems(self._jobstores): - if jobstore_alias in (None, alias): - job = store.lookup_job(job_id) - if job is not None: - return job, alias - - raise JobLookupError(job_id) - - def _dispatch_event(self, event): - """ - Dispatches the given event to interested listeners. - - :param SchedulerEvent event: the event to send - - """ - with self._listeners_lock: - listeners = tuple(self._listeners) - - for cb, mask in listeners: - if event.code & mask: - try: - cb(event) - except BaseException: - self._logger.exception('Error notifying listener') - - def _check_uwsgi(self): - """Check if we're running under uWSGI with threads disabled.""" - uwsgi_module = sys.modules.get('uwsgi') - if not getattr(uwsgi_module, 'has_threads', True): - raise RuntimeError('The scheduler seems to be running under uWSGI, but threads have ' - 'been disabled. You must run uWSGI with the --enable-threads ' - 'option for the scheduler to work.') - - def _real_add_job(self, job, jobstore_alias, replace_existing): - """ - :param Job job: the job to add - :param bool replace_existing: ``True`` to use update_job() in case the job already exists - in the store - - """ - # Fill in undefined values with defaults - replacements = {} - for key, value in six.iteritems(self._job_defaults): - if not hasattr(job, key): - replacements[key] = value - - # Calculate the next run time if there is none defined - if not hasattr(job, 'next_run_time'): - now = datetime.now(self.timezone) - replacements['next_run_time'] = job.trigger.get_next_fire_time(None, now) - - # Apply any replacements - job._modify(**replacements) - - # Add the job to the given job store - store = self._lookup_jobstore(jobstore_alias) - try: - store.add_job(job) - except ConflictingIdError: - if replace_existing: - store.update_job(job) - else: - raise - - # Mark the job as no longer pending - job._jobstore_alias = jobstore_alias - - # Notify listeners that a new job has been added - event = JobEvent(EVENT_JOB_ADDED, job.id, jobstore_alias) - self._dispatch_event(event) - - self._logger.info('Added job "%s" to job store "%s"', job.name, jobstore_alias) - - # Notify the scheduler about the new job - if self.state == STATE_RUNNING: - self.wakeup() - - def _create_plugin_instance(self, type_, alias, constructor_kwargs): - """Creates an instance of the given plugin type, loading the plugin first if necessary.""" - plugin_container, class_container, base_class = { - 'trigger': (self._trigger_plugins, self._trigger_classes, BaseTrigger), - 'jobstore': (self._jobstore_plugins, self._jobstore_classes, BaseJobStore), - 'executor': (self._executor_plugins, self._executor_classes, BaseExecutor) - }[type_] - - try: - plugin_cls = class_container[alias] - except KeyError: - if alias in plugin_container: - plugin_cls = class_container[alias] = plugin_container[alias].load() - if not issubclass(plugin_cls, base_class): - raise TypeError('The {0} entry point does not point to a {0} class'. - format(type_)) - else: - raise LookupError('No {0} by the name "{1}" was found'.format(type_, alias)) - - return plugin_cls(**constructor_kwargs) - - def _create_trigger(self, trigger, trigger_args): - if isinstance(trigger, BaseTrigger): - return trigger - elif trigger is None: - trigger = 'date' - elif not isinstance(trigger, six.string_types): - raise TypeError('Expected a trigger instance or string, got %s instead' % - trigger.__class__.__name__) - - # Use the scheduler's time zone if nothing else is specified - trigger_args.setdefault('timezone', self.timezone) - - # Instantiate the trigger class - return self._create_plugin_instance('trigger', trigger, trigger_args) - - def _create_lock(self): - """Creates a reentrant lock object.""" - return RLock() - - def _process_jobs(self): - """ - Iterates through jobs in every jobstore, starts jobs that are due and figures out how long - to wait for the next round. - - If the ``get_due_jobs()`` call raises an exception, a new wakeup is scheduled in at least - ``jobstore_retry_interval`` seconds. - - """ - if self.state == STATE_PAUSED: - self._logger.debug('Scheduler is paused -- not processing jobs') - return None - - self._logger.debug('Looking for jobs to run') - now = datetime.now(self.timezone) - next_wakeup_time = None - events = [] - - with self._jobstores_lock: - for jobstore_alias, jobstore in six.iteritems(self._jobstores): - try: - due_jobs = jobstore.get_due_jobs(now) - except Exception as e: - # Schedule a wakeup at least in jobstore_retry_interval seconds - self._logger.warning('Error getting due jobs from job store %r: %s', - jobstore_alias, e) - retry_wakeup_time = now + timedelta(seconds=self.jobstore_retry_interval) - if not next_wakeup_time or next_wakeup_time > retry_wakeup_time: - next_wakeup_time = retry_wakeup_time - - continue - - for job in due_jobs: - # Look up the job's executor - try: - executor = self._lookup_executor(job.executor) - except BaseException: - self._logger.error( - 'Executor lookup ("%s") failed for job "%s" -- removing it from the ' - 'job store', job.executor, job) - self.remove_job(job.id, jobstore_alias) - continue - - run_times = job._get_run_times(now) - run_times = run_times[-1:] if run_times and job.coalesce else run_times - if run_times: - try: - executor.submit_job(job, run_times) - except MaxInstancesReachedError: - self._logger.warning( - 'Execution of job "%s" skipped: maximum number of running ' - 'instances reached (%d)', job, job.max_instances) - event = JobSubmissionEvent(EVENT_JOB_MAX_INSTANCES, job.id, - jobstore_alias, run_times) - events.append(event) - except BaseException: - self._logger.exception('Error submitting job "%s" to executor "%s"', - job, job.executor) - else: - event = JobSubmissionEvent(EVENT_JOB_SUBMITTED, job.id, jobstore_alias, - run_times) - events.append(event) - - # Update the job if it has a next execution time. - # Otherwise remove it from the job store. - job_next_run = job.trigger.get_next_fire_time(run_times[-1], now) - if job_next_run: - job._modify(next_run_time=job_next_run) - jobstore.update_job(job) - else: - self.remove_job(job.id, jobstore_alias) - - # Set a new next wakeup time if there isn't one yet or - # the jobstore has an even earlier one - jobstore_next_run_time = jobstore.get_next_run_time() - if jobstore_next_run_time and (next_wakeup_time is None or - jobstore_next_run_time < next_wakeup_time): - next_wakeup_time = jobstore_next_run_time.astimezone(self.timezone) - - # Dispatch collected events - for event in events: - self._dispatch_event(event) - - # Determine the delay until this method should be called again - if self.state == STATE_PAUSED: - wait_seconds = None - self._logger.debug('Scheduler is paused; waiting until resume() is called') - elif next_wakeup_time is None: - wait_seconds = None - self._logger.debug('No jobs; waiting until a job is added') - else: - wait_seconds = min(max(timedelta_seconds(next_wakeup_time - now), 0), TIMEOUT_MAX) - self._logger.debug('Next wakeup is due at %s (in %f seconds)', next_wakeup_time, - wait_seconds) - - return wait_seconds diff --git a/telegramer/include/apscheduler/schedulers/blocking.py b/telegramer/include/apscheduler/schedulers/blocking.py deleted file mode 100644 index 4ecc9f6..0000000 --- a/telegramer/include/apscheduler/schedulers/blocking.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import absolute_import - -from threading import Event - -from apscheduler.schedulers.base import BaseScheduler, STATE_STOPPED -from apscheduler.util import TIMEOUT_MAX - - -class BlockingScheduler(BaseScheduler): - """ - A scheduler that runs in the foreground - (:meth:`~apscheduler.schedulers.base.BaseScheduler.start` will block). - """ - _event = None - - def start(self, *args, **kwargs): - if self._event is None or self._event.is_set(): - self._event = Event() - - super(BlockingScheduler, self).start(*args, **kwargs) - self._main_loop() - - def shutdown(self, wait=True): - super(BlockingScheduler, self).shutdown(wait) - self._event.set() - - def _main_loop(self): - wait_seconds = TIMEOUT_MAX - while self.state != STATE_STOPPED: - self._event.wait(wait_seconds) - self._event.clear() - wait_seconds = self._process_jobs() - - def wakeup(self): - self._event.set() diff --git a/telegramer/include/apscheduler/schedulers/gevent.py b/telegramer/include/apscheduler/schedulers/gevent.py deleted file mode 100644 index d48ed74..0000000 --- a/telegramer/include/apscheduler/schedulers/gevent.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import absolute_import - -from apscheduler.schedulers.blocking import BlockingScheduler -from apscheduler.schedulers.base import BaseScheduler - -try: - from gevent.event import Event - from gevent.lock import RLock - import gevent -except ImportError: # pragma: nocover - raise ImportError('GeventScheduler requires gevent installed') - - -class GeventScheduler(BlockingScheduler): - """A scheduler that runs as a Gevent greenlet.""" - - _greenlet = None - - def start(self, *args, **kwargs): - self._event = Event() - BaseScheduler.start(self, *args, **kwargs) - self._greenlet = gevent.spawn(self._main_loop) - return self._greenlet - - def shutdown(self, *args, **kwargs): - super(GeventScheduler, self).shutdown(*args, **kwargs) - self._greenlet.join() - del self._greenlet - - def _create_lock(self): - return RLock() - - def _create_default_executor(self): - from apscheduler.executors.gevent import GeventExecutor - return GeventExecutor() diff --git a/telegramer/include/apscheduler/schedulers/qt.py b/telegramer/include/apscheduler/schedulers/qt.py deleted file mode 100644 index 600f6e6..0000000 --- a/telegramer/include/apscheduler/schedulers/qt.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import absolute_import - -from apscheduler.schedulers.base import BaseScheduler - -try: - from PyQt5.QtCore import QObject, QTimer -except (ImportError, RuntimeError): # pragma: nocover - try: - from PyQt4.QtCore import QObject, QTimer - except ImportError: - try: - from PySide6.QtCore import QObject, QTimer # noqa - except ImportError: - try: - from PySide2.QtCore import QObject, QTimer # noqa - except ImportError: - try: - from PySide.QtCore import QObject, QTimer # noqa - except ImportError: - raise ImportError('QtScheduler requires either PyQt5, PyQt4, PySide6, PySide2 ' - 'or PySide installed') - - -class QtScheduler(BaseScheduler): - """A scheduler that runs in a Qt event loop.""" - - _timer = None - - def shutdown(self, *args, **kwargs): - super(QtScheduler, self).shutdown(*args, **kwargs) - self._stop_timer() - - def _start_timer(self, wait_seconds): - self._stop_timer() - if wait_seconds is not None: - wait_time = min(wait_seconds * 1000, 2147483647) - self._timer = QTimer.singleShot(wait_time, self._process_jobs) - - def _stop_timer(self): - if self._timer: - if self._timer.isActive(): - self._timer.stop() - del self._timer - - def wakeup(self): - self._start_timer(0) - - def _process_jobs(self): - wait_seconds = super(QtScheduler, self)._process_jobs() - self._start_timer(wait_seconds) diff --git a/telegramer/include/apscheduler/schedulers/tornado.py b/telegramer/include/apscheduler/schedulers/tornado.py deleted file mode 100644 index 0a9171f..0000000 --- a/telegramer/include/apscheduler/schedulers/tornado.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import absolute_import - -from datetime import timedelta -from functools import wraps - -from apscheduler.schedulers.base import BaseScheduler -from apscheduler.util import maybe_ref - -try: - from tornado.ioloop import IOLoop -except ImportError: # pragma: nocover - raise ImportError('TornadoScheduler requires tornado installed') - - -def run_in_ioloop(func): - @wraps(func) - def wrapper(self, *args, **kwargs): - self._ioloop.add_callback(func, self, *args, **kwargs) - return wrapper - - -class TornadoScheduler(BaseScheduler): - """ - A scheduler that runs on a Tornado IOLoop. - - The default executor can run jobs based on native coroutines (``async def``). - - =========== =============================================================== - ``io_loop`` Tornado IOLoop instance to use (defaults to the global IO loop) - =========== =============================================================== - """ - - _ioloop = None - _timeout = None - - @run_in_ioloop - def shutdown(self, wait=True): - super(TornadoScheduler, self).shutdown(wait) - self._stop_timer() - - def _configure(self, config): - self._ioloop = maybe_ref(config.pop('io_loop', None)) or IOLoop.current() - super(TornadoScheduler, self)._configure(config) - - def _start_timer(self, wait_seconds): - self._stop_timer() - if wait_seconds is not None: - self._timeout = self._ioloop.add_timeout(timedelta(seconds=wait_seconds), self.wakeup) - - def _stop_timer(self): - if self._timeout: - self._ioloop.remove_timeout(self._timeout) - del self._timeout - - def _create_default_executor(self): - from apscheduler.executors.tornado import TornadoExecutor - return TornadoExecutor() - - @run_in_ioloop - def wakeup(self): - self._stop_timer() - wait_seconds = self._process_jobs() - self._start_timer(wait_seconds) diff --git a/telegramer/include/apscheduler/schedulers/twisted.py b/telegramer/include/apscheduler/schedulers/twisted.py deleted file mode 100644 index 6b43a84..0000000 --- a/telegramer/include/apscheduler/schedulers/twisted.py +++ /dev/null @@ -1,62 +0,0 @@ -from __future__ import absolute_import - -from functools import wraps - -from apscheduler.schedulers.base import BaseScheduler -from apscheduler.util import maybe_ref - -try: - from twisted.internet import reactor as default_reactor -except ImportError: # pragma: nocover - raise ImportError('TwistedScheduler requires Twisted installed') - - -def run_in_reactor(func): - @wraps(func) - def wrapper(self, *args, **kwargs): - self._reactor.callFromThread(func, self, *args, **kwargs) - return wrapper - - -class TwistedScheduler(BaseScheduler): - """ - A scheduler that runs on a Twisted reactor. - - Extra options: - - =========== ======================================================== - ``reactor`` Reactor instance to use (defaults to the global reactor) - =========== ======================================================== - """ - - _reactor = None - _delayedcall = None - - def _configure(self, config): - self._reactor = maybe_ref(config.pop('reactor', default_reactor)) - super(TwistedScheduler, self)._configure(config) - - @run_in_reactor - def shutdown(self, wait=True): - super(TwistedScheduler, self).shutdown(wait) - self._stop_timer() - - def _start_timer(self, wait_seconds): - self._stop_timer() - if wait_seconds is not None: - self._delayedcall = self._reactor.callLater(wait_seconds, self.wakeup) - - def _stop_timer(self): - if self._delayedcall and self._delayedcall.active(): - self._delayedcall.cancel() - del self._delayedcall - - @run_in_reactor - def wakeup(self): - self._stop_timer() - wait_seconds = self._process_jobs() - self._start_timer(wait_seconds) - - def _create_default_executor(self): - from apscheduler.executors.twisted import TwistedExecutor - return TwistedExecutor() diff --git a/telegramer/include/apscheduler/triggers/__init__.py b/telegramer/include/apscheduler/triggers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/apscheduler/triggers/base.py b/telegramer/include/apscheduler/triggers/base.py deleted file mode 100644 index 55d010d..0000000 --- a/telegramer/include/apscheduler/triggers/base.py +++ /dev/null @@ -1,37 +0,0 @@ -from abc import ABCMeta, abstractmethod -from datetime import timedelta -import random - -import six - - -class BaseTrigger(six.with_metaclass(ABCMeta)): - """Abstract base class that defines the interface that every trigger must implement.""" - - __slots__ = () - - @abstractmethod - def get_next_fire_time(self, previous_fire_time, now): - """ - Returns the next datetime to fire on, If no such datetime can be calculated, returns - ``None``. - - :param datetime.datetime previous_fire_time: the previous time the trigger was fired - :param datetime.datetime now: current datetime - """ - - def _apply_jitter(self, next_fire_time, jitter, now): - """ - Randomize ``next_fire_time`` by adding a random value (the jitter). - - :param datetime.datetime|None next_fire_time: next fire time without jitter applied. If - ``None``, returns ``None``. - :param int|None jitter: maximum number of seconds to add to ``next_fire_time`` - (if ``None`` or ``0``, returns ``next_fire_time``) - :param datetime.datetime now: current datetime - :return datetime.datetime|None: next fire time with a jitter. - """ - if next_fire_time is None or not jitter: - return next_fire_time - - return next_fire_time + timedelta(seconds=random.uniform(0, jitter)) diff --git a/telegramer/include/apscheduler/triggers/combining.py b/telegramer/include/apscheduler/triggers/combining.py deleted file mode 100644 index bb90006..0000000 --- a/telegramer/include/apscheduler/triggers/combining.py +++ /dev/null @@ -1,95 +0,0 @@ -from apscheduler.triggers.base import BaseTrigger -from apscheduler.util import obj_to_ref, ref_to_obj - - -class BaseCombiningTrigger(BaseTrigger): - __slots__ = ('triggers', 'jitter') - - def __init__(self, triggers, jitter=None): - self.triggers = triggers - self.jitter = jitter - - def __getstate__(self): - return { - 'version': 1, - 'triggers': [(obj_to_ref(trigger.__class__), trigger.__getstate__()) - for trigger in self.triggers], - 'jitter': self.jitter - } - - def __setstate__(self, state): - if state.get('version', 1) > 1: - raise ValueError( - 'Got serialized data for version %s of %s, but only versions up to 1 can be ' - 'handled' % (state['version'], self.__class__.__name__)) - - self.jitter = state['jitter'] - self.triggers = [] - for clsref, state in state['triggers']: - cls = ref_to_obj(clsref) - trigger = cls.__new__(cls) - trigger.__setstate__(state) - self.triggers.append(trigger) - - def __repr__(self): - return '<{}({}{})>'.format(self.__class__.__name__, self.triggers, - ', jitter={}'.format(self.jitter) if self.jitter else '') - - -class AndTrigger(BaseCombiningTrigger): - """ - Always returns the earliest next fire time that all the given triggers can agree on. - The trigger is considered to be finished when any of the given triggers has finished its - schedule. - - Trigger alias: ``and`` - - :param list triggers: triggers to combine - :param int|None jitter: delay the job execution by ``jitter`` seconds at most - """ - - __slots__ = () - - def get_next_fire_time(self, previous_fire_time, now): - while True: - fire_times = [trigger.get_next_fire_time(previous_fire_time, now) - for trigger in self.triggers] - if None in fire_times: - return None - elif min(fire_times) == max(fire_times): - return self._apply_jitter(fire_times[0], self.jitter, now) - else: - now = max(fire_times) - - def __str__(self): - return 'and[{}]'.format(', '.join(str(trigger) for trigger in self.triggers)) - - -class OrTrigger(BaseCombiningTrigger): - """ - Always returns the earliest next fire time produced by any of the given triggers. - The trigger is considered finished when all the given triggers have finished their schedules. - - Trigger alias: ``or`` - - :param list triggers: triggers to combine - :param int|None jitter: delay the job execution by ``jitter`` seconds at most - - .. note:: Triggers that depends on the previous fire time, such as the interval trigger, may - seem to behave strangely since they are always passed the previous fire time produced by - any of the given triggers. - """ - - __slots__ = () - - def get_next_fire_time(self, previous_fire_time, now): - fire_times = [trigger.get_next_fire_time(previous_fire_time, now) - for trigger in self.triggers] - fire_times = [fire_time for fire_time in fire_times if fire_time is not None] - if fire_times: - return self._apply_jitter(min(fire_times), self.jitter, now) - else: - return None - - def __str__(self): - return 'or[{}]'.format(', '.join(str(trigger) for trigger in self.triggers)) diff --git a/telegramer/include/apscheduler/triggers/cron/__init__.py b/telegramer/include/apscheduler/triggers/cron/__init__.py deleted file mode 100644 index b5389dd..0000000 --- a/telegramer/include/apscheduler/triggers/cron/__init__.py +++ /dev/null @@ -1,239 +0,0 @@ -from datetime import datetime, timedelta - -from tzlocal import get_localzone -import six - -from apscheduler.triggers.base import BaseTrigger -from apscheduler.triggers.cron.fields import ( - BaseField, MonthField, WeekField, DayOfMonthField, DayOfWeekField, DEFAULT_VALUES) -from apscheduler.util import ( - datetime_ceil, convert_to_datetime, datetime_repr, astimezone, localize, normalize) - - -class CronTrigger(BaseTrigger): - """ - Triggers when current time matches all specified time constraints, - similarly to how the UNIX cron scheduler works. - - :param int|str year: 4-digit year - :param int|str month: month (1-12) - :param int|str day: day of month (1-31) - :param int|str week: ISO week (1-53) - :param int|str day_of_week: number or name of weekday (0-6 or mon,tue,wed,thu,fri,sat,sun) - :param int|str hour: hour (0-23) - :param int|str minute: minute (0-59) - :param int|str second: second (0-59) - :param datetime|str start_date: earliest possible date/time to trigger on (inclusive) - :param datetime|str end_date: latest possible date/time to trigger on (inclusive) - :param datetime.tzinfo|str timezone: time zone to use for the date/time calculations (defaults - to scheduler timezone) - :param int|None jitter: delay the job execution by ``jitter`` seconds at most - - .. note:: The first weekday is always **monday**. - """ - - FIELD_NAMES = ('year', 'month', 'day', 'week', 'day_of_week', 'hour', 'minute', 'second') - FIELDS_MAP = { - 'year': BaseField, - 'month': MonthField, - 'week': WeekField, - 'day': DayOfMonthField, - 'day_of_week': DayOfWeekField, - 'hour': BaseField, - 'minute': BaseField, - 'second': BaseField - } - - __slots__ = 'timezone', 'start_date', 'end_date', 'fields', 'jitter' - - def __init__(self, year=None, month=None, day=None, week=None, day_of_week=None, hour=None, - minute=None, second=None, start_date=None, end_date=None, timezone=None, - jitter=None): - if timezone: - self.timezone = astimezone(timezone) - elif isinstance(start_date, datetime) and start_date.tzinfo: - self.timezone = start_date.tzinfo - elif isinstance(end_date, datetime) and end_date.tzinfo: - self.timezone = end_date.tzinfo - else: - self.timezone = get_localzone() - - self.start_date = convert_to_datetime(start_date, self.timezone, 'start_date') - self.end_date = convert_to_datetime(end_date, self.timezone, 'end_date') - - self.jitter = jitter - - values = dict((key, value) for (key, value) in six.iteritems(locals()) - if key in self.FIELD_NAMES and value is not None) - self.fields = [] - assign_defaults = False - for field_name in self.FIELD_NAMES: - if field_name in values: - exprs = values.pop(field_name) - is_default = False - assign_defaults = not values - elif assign_defaults: - exprs = DEFAULT_VALUES[field_name] - is_default = True - else: - exprs = '*' - is_default = True - - field_class = self.FIELDS_MAP[field_name] - field = field_class(field_name, exprs, is_default) - self.fields.append(field) - - @classmethod - def from_crontab(cls, expr, timezone=None): - """ - Create a :class:`~CronTrigger` from a standard crontab expression. - - See https://en.wikipedia.org/wiki/Cron for more information on the format accepted here. - - :param expr: minute, hour, day of month, month, day of week - :param datetime.tzinfo|str timezone: time zone to use for the date/time calculations ( - defaults to scheduler timezone) - :return: a :class:`~CronTrigger` instance - - """ - values = expr.split() - if len(values) != 5: - raise ValueError('Wrong number of fields; got {}, expected 5'.format(len(values))) - - return cls(minute=values[0], hour=values[1], day=values[2], month=values[3], - day_of_week=values[4], timezone=timezone) - - def _increment_field_value(self, dateval, fieldnum): - """ - Increments the designated field and resets all less significant fields to their minimum - values. - - :type dateval: datetime - :type fieldnum: int - :return: a tuple containing the new date, and the number of the field that was actually - incremented - :rtype: tuple - """ - - values = {} - i = 0 - while i < len(self.fields): - field = self.fields[i] - if not field.REAL: - if i == fieldnum: - fieldnum -= 1 - i -= 1 - else: - i += 1 - continue - - if i < fieldnum: - values[field.name] = field.get_value(dateval) - i += 1 - elif i > fieldnum: - values[field.name] = field.get_min(dateval) - i += 1 - else: - value = field.get_value(dateval) - maxval = field.get_max(dateval) - if value == maxval: - fieldnum -= 1 - i -= 1 - else: - values[field.name] = value + 1 - i += 1 - - difference = datetime(**values) - dateval.replace(tzinfo=None) - return normalize(dateval + difference), fieldnum - - def _set_field_value(self, dateval, fieldnum, new_value): - values = {} - for i, field in enumerate(self.fields): - if field.REAL: - if i < fieldnum: - values[field.name] = field.get_value(dateval) - elif i > fieldnum: - values[field.name] = field.get_min(dateval) - else: - values[field.name] = new_value - - return localize(datetime(**values), self.timezone) - - def get_next_fire_time(self, previous_fire_time, now): - if previous_fire_time: - start_date = min(now, previous_fire_time + timedelta(microseconds=1)) - if start_date == previous_fire_time: - start_date += timedelta(microseconds=1) - else: - start_date = max(now, self.start_date) if self.start_date else now - - fieldnum = 0 - next_date = datetime_ceil(start_date).astimezone(self.timezone) - while 0 <= fieldnum < len(self.fields): - field = self.fields[fieldnum] - curr_value = field.get_value(next_date) - next_value = field.get_next_value(next_date) - - if next_value is None: - # No valid value was found - next_date, fieldnum = self._increment_field_value(next_date, fieldnum - 1) - elif next_value > curr_value: - # A valid, but higher than the starting value, was found - if field.REAL: - next_date = self._set_field_value(next_date, fieldnum, next_value) - fieldnum += 1 - else: - next_date, fieldnum = self._increment_field_value(next_date, fieldnum) - else: - # A valid value was found, no changes necessary - fieldnum += 1 - - # Return if the date has rolled past the end date - if self.end_date and next_date > self.end_date: - return None - - if fieldnum >= 0: - next_date = self._apply_jitter(next_date, self.jitter, now) - return min(next_date, self.end_date) if self.end_date else next_date - - def __getstate__(self): - return { - 'version': 2, - 'timezone': self.timezone, - 'start_date': self.start_date, - 'end_date': self.end_date, - 'fields': self.fields, - 'jitter': self.jitter, - } - - def __setstate__(self, state): - # This is for compatibility with APScheduler 3.0.x - if isinstance(state, tuple): - state = state[1] - - if state.get('version', 1) > 2: - raise ValueError( - 'Got serialized data for version %s of %s, but only versions up to 2 can be ' - 'handled' % (state['version'], self.__class__.__name__)) - - self.timezone = state['timezone'] - self.start_date = state['start_date'] - self.end_date = state['end_date'] - self.fields = state['fields'] - self.jitter = state.get('jitter') - - def __str__(self): - options = ["%s='%s'" % (f.name, f) for f in self.fields if not f.is_default] - return 'cron[%s]' % (', '.join(options)) - - def __repr__(self): - options = ["%s='%s'" % (f.name, f) for f in self.fields if not f.is_default] - if self.start_date: - options.append("start_date=%r" % datetime_repr(self.start_date)) - if self.end_date: - options.append("end_date=%r" % datetime_repr(self.end_date)) - if self.jitter: - options.append('jitter=%s' % self.jitter) - - return "<%s (%s, timezone='%s')>" % ( - self.__class__.__name__, ', '.join(options), self.timezone) diff --git a/telegramer/include/apscheduler/triggers/cron/expressions.py b/telegramer/include/apscheduler/triggers/cron/expressions.py deleted file mode 100644 index 55a3716..0000000 --- a/telegramer/include/apscheduler/triggers/cron/expressions.py +++ /dev/null @@ -1,251 +0,0 @@ -"""This module contains the expressions applicable for CronTrigger's fields.""" - -from calendar import monthrange -import re - -from apscheduler.util import asint - -__all__ = ('AllExpression', 'RangeExpression', 'WeekdayRangeExpression', - 'WeekdayPositionExpression', 'LastDayOfMonthExpression') - - -WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] -MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] - - -class AllExpression(object): - value_re = re.compile(r'\*(?:/(?P\d+))?$') - - def __init__(self, step=None): - self.step = asint(step) - if self.step == 0: - raise ValueError('Increment must be higher than 0') - - def validate_range(self, field_name): - from apscheduler.triggers.cron.fields import MIN_VALUES, MAX_VALUES - - value_range = MAX_VALUES[field_name] - MIN_VALUES[field_name] - if self.step and self.step > value_range: - raise ValueError('the step value ({}) is higher than the total range of the ' - 'expression ({})'.format(self.step, value_range)) - - def get_next_value(self, date, field): - start = field.get_value(date) - minval = field.get_min(date) - maxval = field.get_max(date) - start = max(start, minval) - - if not self.step: - next = start - else: - distance_to_next = (self.step - (start - minval)) % self.step - next = start + distance_to_next - - if next <= maxval: - return next - - def __eq__(self, other): - return isinstance(other, self.__class__) and self.step == other.step - - def __str__(self): - if self.step: - return '*/%d' % self.step - return '*' - - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, self.step) - - -class RangeExpression(AllExpression): - value_re = re.compile( - r'(?P\d+)(?:-(?P\d+))?(?:/(?P\d+))?$') - - def __init__(self, first, last=None, step=None): - super(RangeExpression, self).__init__(step) - first = asint(first) - last = asint(last) - if last is None and step is None: - last = first - if last is not None and first > last: - raise ValueError('The minimum value in a range must not be higher than the maximum') - self.first = first - self.last = last - - def validate_range(self, field_name): - from apscheduler.triggers.cron.fields import MIN_VALUES, MAX_VALUES - - super(RangeExpression, self).validate_range(field_name) - if self.first < MIN_VALUES[field_name]: - raise ValueError('the first value ({}) is lower than the minimum value ({})' - .format(self.first, MIN_VALUES[field_name])) - if self.last is not None and self.last > MAX_VALUES[field_name]: - raise ValueError('the last value ({}) is higher than the maximum value ({})' - .format(self.last, MAX_VALUES[field_name])) - value_range = (self.last or MAX_VALUES[field_name]) - self.first - if self.step and self.step > value_range: - raise ValueError('the step value ({}) is higher than the total range of the ' - 'expression ({})'.format(self.step, value_range)) - - def get_next_value(self, date, field): - startval = field.get_value(date) - minval = field.get_min(date) - maxval = field.get_max(date) - - # Apply range limits - minval = max(minval, self.first) - maxval = min(maxval, self.last) if self.last is not None else maxval - nextval = max(minval, startval) - - # Apply the step if defined - if self.step: - distance_to_next = (self.step - (nextval - minval)) % self.step - nextval += distance_to_next - - return nextval if nextval <= maxval else None - - def __eq__(self, other): - return (isinstance(other, self.__class__) and self.first == other.first and - self.last == other.last) - - def __str__(self): - if self.last != self.first and self.last is not None: - range = '%d-%d' % (self.first, self.last) - else: - range = str(self.first) - - if self.step: - return '%s/%d' % (range, self.step) - return range - - def __repr__(self): - args = [str(self.first)] - if self.last != self.first and self.last is not None or self.step: - args.append(str(self.last)) - if self.step: - args.append(str(self.step)) - return "%s(%s)" % (self.__class__.__name__, ', '.join(args)) - - -class MonthRangeExpression(RangeExpression): - value_re = re.compile(r'(?P[a-z]+)(?:-(?P[a-z]+))?', re.IGNORECASE) - - def __init__(self, first, last=None): - try: - first_num = MONTHS.index(first.lower()) + 1 - except ValueError: - raise ValueError('Invalid month name "%s"' % first) - - if last: - try: - last_num = MONTHS.index(last.lower()) + 1 - except ValueError: - raise ValueError('Invalid month name "%s"' % last) - else: - last_num = None - - super(MonthRangeExpression, self).__init__(first_num, last_num) - - def __str__(self): - if self.last != self.first and self.last is not None: - return '%s-%s' % (MONTHS[self.first - 1], MONTHS[self.last - 1]) - return MONTHS[self.first - 1] - - def __repr__(self): - args = ["'%s'" % MONTHS[self.first]] - if self.last != self.first and self.last is not None: - args.append("'%s'" % MONTHS[self.last - 1]) - return "%s(%s)" % (self.__class__.__name__, ', '.join(args)) - - -class WeekdayRangeExpression(RangeExpression): - value_re = re.compile(r'(?P[a-z]+)(?:-(?P[a-z]+))?', re.IGNORECASE) - - def __init__(self, first, last=None): - try: - first_num = WEEKDAYS.index(first.lower()) - except ValueError: - raise ValueError('Invalid weekday name "%s"' % first) - - if last: - try: - last_num = WEEKDAYS.index(last.lower()) - except ValueError: - raise ValueError('Invalid weekday name "%s"' % last) - else: - last_num = None - - super(WeekdayRangeExpression, self).__init__(first_num, last_num) - - def __str__(self): - if self.last != self.first and self.last is not None: - return '%s-%s' % (WEEKDAYS[self.first], WEEKDAYS[self.last]) - return WEEKDAYS[self.first] - - def __repr__(self): - args = ["'%s'" % WEEKDAYS[self.first]] - if self.last != self.first and self.last is not None: - args.append("'%s'" % WEEKDAYS[self.last]) - return "%s(%s)" % (self.__class__.__name__, ', '.join(args)) - - -class WeekdayPositionExpression(AllExpression): - options = ['1st', '2nd', '3rd', '4th', '5th', 'last'] - value_re = re.compile(r'(?P%s) +(?P(?:\d+|\w+))' % - '|'.join(options), re.IGNORECASE) - - def __init__(self, option_name, weekday_name): - super(WeekdayPositionExpression, self).__init__(None) - try: - self.option_num = self.options.index(option_name.lower()) - except ValueError: - raise ValueError('Invalid weekday position "%s"' % option_name) - - try: - self.weekday = WEEKDAYS.index(weekday_name.lower()) - except ValueError: - raise ValueError('Invalid weekday name "%s"' % weekday_name) - - def get_next_value(self, date, field): - # Figure out the weekday of the month's first day and the number of days in that month - first_day_wday, last_day = monthrange(date.year, date.month) - - # Calculate which day of the month is the first of the target weekdays - first_hit_day = self.weekday - first_day_wday + 1 - if first_hit_day <= 0: - first_hit_day += 7 - - # Calculate what day of the month the target weekday would be - if self.option_num < 5: - target_day = first_hit_day + self.option_num * 7 - else: - target_day = first_hit_day + ((last_day - first_hit_day) // 7) * 7 - - if target_day <= last_day and target_day >= date.day: - return target_day - - def __eq__(self, other): - return (super(WeekdayPositionExpression, self).__eq__(other) and - self.option_num == other.option_num and self.weekday == other.weekday) - - def __str__(self): - return '%s %s' % (self.options[self.option_num], WEEKDAYS[self.weekday]) - - def __repr__(self): - return "%s('%s', '%s')" % (self.__class__.__name__, self.options[self.option_num], - WEEKDAYS[self.weekday]) - - -class LastDayOfMonthExpression(AllExpression): - value_re = re.compile(r'last', re.IGNORECASE) - - def __init__(self): - super(LastDayOfMonthExpression, self).__init__(None) - - def get_next_value(self, date, field): - return monthrange(date.year, date.month)[1] - - def __str__(self): - return 'last' - - def __repr__(self): - return "%s()" % self.__class__.__name__ diff --git a/telegramer/include/apscheduler/triggers/cron/fields.py b/telegramer/include/apscheduler/triggers/cron/fields.py deleted file mode 100644 index 86d620c..0000000 --- a/telegramer/include/apscheduler/triggers/cron/fields.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Fields represent CronTrigger options which map to :class:`~datetime.datetime` fields.""" - -from calendar import monthrange -import re - -import six - -from apscheduler.triggers.cron.expressions import ( - AllExpression, RangeExpression, WeekdayPositionExpression, LastDayOfMonthExpression, - WeekdayRangeExpression, MonthRangeExpression) - - -__all__ = ('MIN_VALUES', 'MAX_VALUES', 'DEFAULT_VALUES', 'BaseField', 'WeekField', - 'DayOfMonthField', 'DayOfWeekField') - - -MIN_VALUES = {'year': 1970, 'month': 1, 'day': 1, 'week': 1, 'day_of_week': 0, 'hour': 0, - 'minute': 0, 'second': 0} -MAX_VALUES = {'year': 9999, 'month': 12, 'day': 31, 'week': 53, 'day_of_week': 6, 'hour': 23, - 'minute': 59, 'second': 59} -DEFAULT_VALUES = {'year': '*', 'month': 1, 'day': 1, 'week': '*', 'day_of_week': '*', 'hour': 0, - 'minute': 0, 'second': 0} -SEPARATOR = re.compile(' *, *') - - -class BaseField(object): - REAL = True - COMPILERS = [AllExpression, RangeExpression] - - def __init__(self, name, exprs, is_default=False): - self.name = name - self.is_default = is_default - self.compile_expressions(exprs) - - def get_min(self, dateval): - return MIN_VALUES[self.name] - - def get_max(self, dateval): - return MAX_VALUES[self.name] - - def get_value(self, dateval): - return getattr(dateval, self.name) - - def get_next_value(self, dateval): - smallest = None - for expr in self.expressions: - value = expr.get_next_value(dateval, self) - if smallest is None or (value is not None and value < smallest): - smallest = value - - return smallest - - def compile_expressions(self, exprs): - self.expressions = [] - - # Split a comma-separated expression list, if any - for expr in SEPARATOR.split(str(exprs).strip()): - self.compile_expression(expr) - - def compile_expression(self, expr): - for compiler in self.COMPILERS: - match = compiler.value_re.match(expr) - if match: - compiled_expr = compiler(**match.groupdict()) - - try: - compiled_expr.validate_range(self.name) - except ValueError as e: - exc = ValueError('Error validating expression {!r}: {}'.format(expr, e)) - six.raise_from(exc, None) - - self.expressions.append(compiled_expr) - return - - raise ValueError('Unrecognized expression "%s" for field "%s"' % (expr, self.name)) - - def __eq__(self, other): - return isinstance(self, self.__class__) and self.expressions == other.expressions - - def __str__(self): - expr_strings = (str(e) for e in self.expressions) - return ','.join(expr_strings) - - def __repr__(self): - return "%s('%s', '%s')" % (self.__class__.__name__, self.name, self) - - -class WeekField(BaseField): - REAL = False - - def get_value(self, dateval): - return dateval.isocalendar()[1] - - -class DayOfMonthField(BaseField): - COMPILERS = BaseField.COMPILERS + [WeekdayPositionExpression, LastDayOfMonthExpression] - - def get_max(self, dateval): - return monthrange(dateval.year, dateval.month)[1] - - -class DayOfWeekField(BaseField): - REAL = False - COMPILERS = BaseField.COMPILERS + [WeekdayRangeExpression] - - def get_value(self, dateval): - return dateval.weekday() - - -class MonthField(BaseField): - COMPILERS = BaseField.COMPILERS + [MonthRangeExpression] diff --git a/telegramer/include/apscheduler/triggers/date.py b/telegramer/include/apscheduler/triggers/date.py deleted file mode 100644 index 0768100..0000000 --- a/telegramer/include/apscheduler/triggers/date.py +++ /dev/null @@ -1,51 +0,0 @@ -from datetime import datetime - -from tzlocal import get_localzone - -from apscheduler.triggers.base import BaseTrigger -from apscheduler.util import convert_to_datetime, datetime_repr, astimezone - - -class DateTrigger(BaseTrigger): - """ - Triggers once on the given datetime. If ``run_date`` is left empty, current time is used. - - :param datetime|str run_date: the date/time to run the job at - :param datetime.tzinfo|str timezone: time zone for ``run_date`` if it doesn't have one already - """ - - __slots__ = 'run_date' - - def __init__(self, run_date=None, timezone=None): - timezone = astimezone(timezone) or get_localzone() - if run_date is not None: - self.run_date = convert_to_datetime(run_date, timezone, 'run_date') - else: - self.run_date = datetime.now(timezone) - - def get_next_fire_time(self, previous_fire_time, now): - return self.run_date if previous_fire_time is None else None - - def __getstate__(self): - return { - 'version': 1, - 'run_date': self.run_date - } - - def __setstate__(self, state): - # This is for compatibility with APScheduler 3.0.x - if isinstance(state, tuple): - state = state[1] - - if state.get('version', 1) > 1: - raise ValueError( - 'Got serialized data for version %s of %s, but only version 1 can be handled' % - (state['version'], self.__class__.__name__)) - - self.run_date = state['run_date'] - - def __str__(self): - return 'date[%s]' % datetime_repr(self.run_date) - - def __repr__(self): - return "<%s (run_date='%s')>" % (self.__class__.__name__, datetime_repr(self.run_date)) diff --git a/telegramer/include/apscheduler/triggers/interval.py b/telegramer/include/apscheduler/triggers/interval.py deleted file mode 100644 index b0e2dbd..0000000 --- a/telegramer/include/apscheduler/triggers/interval.py +++ /dev/null @@ -1,108 +0,0 @@ -from datetime import timedelta, datetime -from math import ceil - -from tzlocal import get_localzone - -from apscheduler.triggers.base import BaseTrigger -from apscheduler.util import ( - convert_to_datetime, normalize, timedelta_seconds, datetime_repr, - astimezone) - - -class IntervalTrigger(BaseTrigger): - """ - Triggers on specified intervals, starting on ``start_date`` if specified, ``datetime.now()`` + - interval otherwise. - - :param int weeks: number of weeks to wait - :param int days: number of days to wait - :param int hours: number of hours to wait - :param int minutes: number of minutes to wait - :param int seconds: number of seconds to wait - :param datetime|str start_date: starting point for the interval calculation - :param datetime|str end_date: latest possible date/time to trigger on - :param datetime.tzinfo|str timezone: time zone to use for the date/time calculations - :param int|None jitter: delay the job execution by ``jitter`` seconds at most - """ - - __slots__ = 'timezone', 'start_date', 'end_date', 'interval', 'interval_length', 'jitter' - - def __init__(self, weeks=0, days=0, hours=0, minutes=0, seconds=0, start_date=None, - end_date=None, timezone=None, jitter=None): - self.interval = timedelta(weeks=weeks, days=days, hours=hours, minutes=minutes, - seconds=seconds) - self.interval_length = timedelta_seconds(self.interval) - if self.interval_length == 0: - self.interval = timedelta(seconds=1) - self.interval_length = 1 - - if timezone: - self.timezone = astimezone(timezone) - elif isinstance(start_date, datetime) and start_date.tzinfo: - self.timezone = start_date.tzinfo - elif isinstance(end_date, datetime) and end_date.tzinfo: - self.timezone = end_date.tzinfo - else: - self.timezone = get_localzone() - - start_date = start_date or (datetime.now(self.timezone) + self.interval) - self.start_date = convert_to_datetime(start_date, self.timezone, 'start_date') - self.end_date = convert_to_datetime(end_date, self.timezone, 'end_date') - - self.jitter = jitter - - def get_next_fire_time(self, previous_fire_time, now): - if previous_fire_time: - next_fire_time = previous_fire_time + self.interval - elif self.start_date > now: - next_fire_time = self.start_date - else: - timediff_seconds = timedelta_seconds(now - self.start_date) - next_interval_num = int(ceil(timediff_seconds / self.interval_length)) - next_fire_time = self.start_date + self.interval * next_interval_num - - if self.jitter is not None: - next_fire_time = self._apply_jitter(next_fire_time, self.jitter, now) - - if not self.end_date or next_fire_time <= self.end_date: - return normalize(next_fire_time) - - def __getstate__(self): - return { - 'version': 2, - 'timezone': self.timezone, - 'start_date': self.start_date, - 'end_date': self.end_date, - 'interval': self.interval, - 'jitter': self.jitter, - } - - def __setstate__(self, state): - # This is for compatibility with APScheduler 3.0.x - if isinstance(state, tuple): - state = state[1] - - if state.get('version', 1) > 2: - raise ValueError( - 'Got serialized data for version %s of %s, but only versions up to 2 can be ' - 'handled' % (state['version'], self.__class__.__name__)) - - self.timezone = state['timezone'] - self.start_date = state['start_date'] - self.end_date = state['end_date'] - self.interval = state['interval'] - self.interval_length = timedelta_seconds(self.interval) - self.jitter = state.get('jitter') - - def __str__(self): - return 'interval[%s]' % str(self.interval) - - def __repr__(self): - options = ['interval=%r' % self.interval, 'start_date=%r' % datetime_repr(self.start_date)] - if self.end_date: - options.append("end_date=%r" % datetime_repr(self.end_date)) - if self.jitter: - options.append('jitter=%s' % self.jitter) - - return "<%s (%s, timezone='%s')>" % ( - self.__class__.__name__, ', '.join(options), self.timezone) diff --git a/telegramer/include/apscheduler/util.py b/telegramer/include/apscheduler/util.py deleted file mode 100644 index d929a48..0000000 --- a/telegramer/include/apscheduler/util.py +++ /dev/null @@ -1,438 +0,0 @@ -"""This module contains several handy functions primarily meant for internal use.""" - -from __future__ import division - -from datetime import date, datetime, time, timedelta, tzinfo -from calendar import timegm -from functools import partial -from inspect import isclass, ismethod -import re -import sys - -from pytz import timezone, utc, FixedOffset -import six - -try: - from inspect import signature -except ImportError: # pragma: nocover - from funcsigs import signature - -try: - from threading import TIMEOUT_MAX -except ImportError: - TIMEOUT_MAX = 4294967 # Maximum value accepted by Event.wait() on Windows - -try: - from asyncio import iscoroutinefunction -except ImportError: - try: - from trollius import iscoroutinefunction - except ImportError: - def iscoroutinefunction(func): - return False - -__all__ = ('asint', 'asbool', 'astimezone', 'convert_to_datetime', 'datetime_to_utc_timestamp', - 'utc_timestamp_to_datetime', 'timedelta_seconds', 'datetime_ceil', 'get_callable_name', - 'obj_to_ref', 'ref_to_obj', 'maybe_ref', 'repr_escape', 'check_callable_args', - 'normalize', 'localize', 'TIMEOUT_MAX') - - -class _Undefined(object): - def __nonzero__(self): - return False - - def __bool__(self): - return False - - def __repr__(self): - return '' - - -undefined = _Undefined() #: a unique object that only signifies that no value is defined - - -def asint(text): - """ - Safely converts a string to an integer, returning ``None`` if the string is ``None``. - - :type text: str - :rtype: int - - """ - if text is not None: - return int(text) - - -def asbool(obj): - """ - Interprets an object as a boolean value. - - :rtype: bool - - """ - if isinstance(obj, str): - obj = obj.strip().lower() - if obj in ('true', 'yes', 'on', 'y', 't', '1'): - return True - if obj in ('false', 'no', 'off', 'n', 'f', '0'): - return False - raise ValueError('Unable to interpret value "%s" as boolean' % obj) - return bool(obj) - - -def astimezone(obj): - """ - Interprets an object as a timezone. - - :rtype: tzinfo - - """ - if isinstance(obj, six.string_types): - return timezone(obj) - if isinstance(obj, tzinfo): - if obj.tzname(None) == 'local': - raise ValueError( - 'Unable to determine the name of the local timezone -- you must explicitly ' - 'specify the name of the local timezone. Please refrain from using timezones like ' - 'EST to prevent problems with daylight saving time. Instead, use a locale based ' - 'timezone name (such as Europe/Helsinki).') - return obj - if obj is not None: - raise TypeError('Expected tzinfo, got %s instead' % obj.__class__.__name__) - - -_DATE_REGEX = re.compile( - r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' - r'(?:[ T](?P\d{1,2}):(?P\d{1,2}):(?P\d{1,2})' - r'(?:\.(?P\d{1,6}))?' - r'(?PZ|[+-]\d\d:\d\d)?)?$') - - -def convert_to_datetime(input, tz, arg_name): - """ - Converts the given object to a timezone aware datetime object. - - If a timezone aware datetime object is passed, it is returned unmodified. - If a native datetime object is passed, it is given the specified timezone. - If the input is a string, it is parsed as a datetime with the given timezone. - - Date strings are accepted in three different forms: date only (Y-m-d), date with time - (Y-m-d H:M:S) or with date+time with microseconds (Y-m-d H:M:S.micro). Additionally you can - override the time zone by giving a specific offset in the format specified by ISO 8601: - Z (UTC), +HH:MM or -HH:MM. - - :param str|datetime input: the datetime or string to convert to a timezone aware datetime - :param datetime.tzinfo tz: timezone to interpret ``input`` in - :param str arg_name: the name of the argument (used in an error message) - :rtype: datetime - - """ - if input is None: - return - elif isinstance(input, datetime): - datetime_ = input - elif isinstance(input, date): - datetime_ = datetime.combine(input, time()) - elif isinstance(input, six.string_types): - m = _DATE_REGEX.match(input) - if not m: - raise ValueError('Invalid date string') - - values = m.groupdict() - tzname = values.pop('timezone') - if tzname == 'Z': - tz = utc - elif tzname: - hours, minutes = (int(x) for x in tzname[1:].split(':')) - sign = 1 if tzname[0] == '+' else -1 - tz = FixedOffset(sign * (hours * 60 + minutes)) - - values = {k: int(v or 0) for k, v in values.items()} - datetime_ = datetime(**values) - else: - raise TypeError('Unsupported type for %s: %s' % (arg_name, input.__class__.__name__)) - - if datetime_.tzinfo is not None: - return datetime_ - if tz is None: - raise ValueError( - 'The "tz" argument must be specified if %s has no timezone information' % arg_name) - if isinstance(tz, six.string_types): - tz = timezone(tz) - - return localize(datetime_, tz) - - -def datetime_to_utc_timestamp(timeval): - """ - Converts a datetime instance to a timestamp. - - :type timeval: datetime - :rtype: float - - """ - if timeval is not None: - return timegm(timeval.utctimetuple()) + timeval.microsecond / 1000000 - - -def utc_timestamp_to_datetime(timestamp): - """ - Converts the given timestamp to a datetime instance. - - :type timestamp: float - :rtype: datetime - - """ - if timestamp is not None: - return datetime.fromtimestamp(timestamp, utc) - - -def timedelta_seconds(delta): - """ - Converts the given timedelta to seconds. - - :type delta: timedelta - :rtype: float - - """ - return delta.days * 24 * 60 * 60 + delta.seconds + \ - delta.microseconds / 1000000.0 - - -def datetime_ceil(dateval): - """ - Rounds the given datetime object upwards. - - :type dateval: datetime - - """ - if dateval.microsecond > 0: - return dateval + timedelta(seconds=1, microseconds=-dateval.microsecond) - return dateval - - -def datetime_repr(dateval): - return dateval.strftime('%Y-%m-%d %H:%M:%S %Z') if dateval else 'None' - - -def get_callable_name(func): - """ - Returns the best available display name for the given function/callable. - - :rtype: str - - """ - # the easy case (on Python 3.3+) - if hasattr(func, '__qualname__'): - return func.__qualname__ - - # class methods, bound and unbound methods - f_self = getattr(func, '__self__', None) or getattr(func, 'im_self', None) - if f_self and hasattr(func, '__name__'): - f_class = f_self if isclass(f_self) else f_self.__class__ - else: - f_class = getattr(func, 'im_class', None) - - if f_class and hasattr(func, '__name__'): - return '%s.%s' % (f_class.__name__, func.__name__) - - # class or class instance - if hasattr(func, '__call__'): - # class - if hasattr(func, '__name__'): - return func.__name__ - - # instance of a class with a __call__ method - return func.__class__.__name__ - - raise TypeError('Unable to determine a name for %r -- maybe it is not a callable?' % func) - - -def obj_to_ref(obj): - """ - Returns the path to the given callable. - - :rtype: str - :raises TypeError: if the given object is not callable - :raises ValueError: if the given object is a :class:`~functools.partial`, lambda or a nested - function - - """ - if isinstance(obj, partial): - raise ValueError('Cannot create a reference to a partial()') - - name = get_callable_name(obj) - if '' in name: - raise ValueError('Cannot create a reference to a lambda') - if '' in name: - raise ValueError('Cannot create a reference to a nested function') - - if ismethod(obj): - if hasattr(obj, 'im_self') and obj.im_self: - # bound method - module = obj.im_self.__module__ - elif hasattr(obj, 'im_class') and obj.im_class: - # unbound method - module = obj.im_class.__module__ - else: - module = obj.__module__ - else: - module = obj.__module__ - return '%s:%s' % (module, name) - - -def ref_to_obj(ref): - """ - Returns the object pointed to by ``ref``. - - :type ref: str - - """ - if not isinstance(ref, six.string_types): - raise TypeError('References must be strings') - if ':' not in ref: - raise ValueError('Invalid reference') - - modulename, rest = ref.split(':', 1) - try: - obj = __import__(modulename, fromlist=[rest]) - except ImportError: - raise LookupError('Error resolving reference %s: could not import module' % ref) - - try: - for name in rest.split('.'): - obj = getattr(obj, name) - return obj - except Exception: - raise LookupError('Error resolving reference %s: error looking up object' % ref) - - -def maybe_ref(ref): - """ - Returns the object that the given reference points to, if it is indeed a reference. - If it is not a reference, the object is returned as-is. - - """ - if not isinstance(ref, str): - return ref - return ref_to_obj(ref) - - -if six.PY2: - def repr_escape(string): - if isinstance(string, six.text_type): - return string.encode('ascii', 'backslashreplace') - return string -else: - def repr_escape(string): - return string - - -def check_callable_args(func, args, kwargs): - """ - Ensures that the given callable can be called with the given arguments. - - :type args: tuple - :type kwargs: dict - - """ - pos_kwargs_conflicts = [] # parameters that have a match in both args and kwargs - positional_only_kwargs = [] # positional-only parameters that have a match in kwargs - unsatisfied_args = [] # parameters in signature that don't have a match in args or kwargs - unsatisfied_kwargs = [] # keyword-only arguments that don't have a match in kwargs - unmatched_args = list(args) # args that didn't match any of the parameters in the signature - # kwargs that didn't match any of the parameters in the signature - unmatched_kwargs = list(kwargs) - # indicates if the signature defines *args and **kwargs respectively - has_varargs = has_var_kwargs = False - - try: - if sys.version_info >= (3, 5): - sig = signature(func, follow_wrapped=False) - else: - sig = signature(func) - except ValueError: - # signature() doesn't work against every kind of callable - return - - for param in six.itervalues(sig.parameters): - if param.kind == param.POSITIONAL_OR_KEYWORD: - if param.name in unmatched_kwargs and unmatched_args: - pos_kwargs_conflicts.append(param.name) - elif unmatched_args: - del unmatched_args[0] - elif param.name in unmatched_kwargs: - unmatched_kwargs.remove(param.name) - elif param.default is param.empty: - unsatisfied_args.append(param.name) - elif param.kind == param.POSITIONAL_ONLY: - if unmatched_args: - del unmatched_args[0] - elif param.name in unmatched_kwargs: - unmatched_kwargs.remove(param.name) - positional_only_kwargs.append(param.name) - elif param.default is param.empty: - unsatisfied_args.append(param.name) - elif param.kind == param.KEYWORD_ONLY: - if param.name in unmatched_kwargs: - unmatched_kwargs.remove(param.name) - elif param.default is param.empty: - unsatisfied_kwargs.append(param.name) - elif param.kind == param.VAR_POSITIONAL: - has_varargs = True - elif param.kind == param.VAR_KEYWORD: - has_var_kwargs = True - - # Make sure there are no conflicts between args and kwargs - if pos_kwargs_conflicts: - raise ValueError('The following arguments are supplied in both args and kwargs: %s' % - ', '.join(pos_kwargs_conflicts)) - - # Check if keyword arguments are being fed to positional-only parameters - if positional_only_kwargs: - raise ValueError('The following arguments cannot be given as keyword arguments: %s' % - ', '.join(positional_only_kwargs)) - - # Check that the number of positional arguments minus the number of matched kwargs matches the - # argspec - if unsatisfied_args: - raise ValueError('The following arguments have not been supplied: %s' % - ', '.join(unsatisfied_args)) - - # Check that all keyword-only arguments have been supplied - if unsatisfied_kwargs: - raise ValueError( - 'The following keyword-only arguments have not been supplied in kwargs: %s' % - ', '.join(unsatisfied_kwargs)) - - # Check that the callable can accept the given number of positional arguments - if not has_varargs and unmatched_args: - raise ValueError( - 'The list of positional arguments is longer than the target callable can handle ' - '(allowed: %d, given in args: %d)' % (len(args) - len(unmatched_args), len(args))) - - # Check that the callable can accept the given keyword arguments - if not has_var_kwargs and unmatched_kwargs: - raise ValueError( - 'The target callable does not accept the following keyword arguments: %s' % - ', '.join(unmatched_kwargs)) - - -def iscoroutinefunction_partial(f): - while isinstance(f, partial): - f = f.func - - # The asyncio version of iscoroutinefunction includes testing for @coroutine - # decorations vs. the inspect version which does not. - return iscoroutinefunction(f) - - -def normalize(dt): - return datetime.fromtimestamp(dt.timestamp(), dt.tzinfo) - - -def localize(dt, tzinfo): - if hasattr(tzinfo, 'localize'): - return tzinfo.localize(dt) - - return normalize(dt.replace(tzinfo=tzinfo)) diff --git a/telegramer/include/cachetools/__init__.py b/telegramer/include/cachetools/__init__.py deleted file mode 100644 index 03b6f25..0000000 --- a/telegramer/include/cachetools/__init__.py +++ /dev/null @@ -1,718 +0,0 @@ -"""Extensible memoizing collections and decorators.""" - -__all__ = ( - "Cache", - "FIFOCache", - "LFUCache", - "LRUCache", - "MRUCache", - "RRCache", - "TLRUCache", - "TTLCache", - "cached", - "cachedmethod", -) - -__version__ = "5.0.0" - -import collections -import collections.abc -import functools -import heapq -import random -import time - -from .keys import hashkey as _defaultkey - - -def _methodkey(_, *args, **kwargs): - return _defaultkey(*args, **kwargs) - - -class _DefaultSize: - - __slots__ = () - - def __getitem__(self, _): - return 1 - - def __setitem__(self, _, value): - assert value == 1 - - def pop(self, _): - return 1 - - -class Cache(collections.abc.MutableMapping): - """Mutable mapping to serve as a simple cache or cache base class.""" - - __marker = object() - - __size = _DefaultSize() - - def __init__(self, maxsize, getsizeof=None): - if getsizeof: - self.getsizeof = getsizeof - if self.getsizeof is not Cache.getsizeof: - self.__size = dict() - self.__data = dict() - self.__currsize = 0 - self.__maxsize = maxsize - - def __repr__(self): - return "%s(%s, maxsize=%r, currsize=%r)" % ( - self.__class__.__name__, - repr(self.__data), - self.__maxsize, - self.__currsize, - ) - - def __getitem__(self, key): - try: - return self.__data[key] - except KeyError: - return self.__missing__(key) - - def __setitem__(self, key, value): - maxsize = self.__maxsize - size = self.getsizeof(value) - if size > maxsize: - raise ValueError("value too large") - if key not in self.__data or self.__size[key] < size: - while self.__currsize + size > maxsize: - self.popitem() - if key in self.__data: - diffsize = size - self.__size[key] - else: - diffsize = size - self.__data[key] = value - self.__size[key] = size - self.__currsize += diffsize - - def __delitem__(self, key): - size = self.__size.pop(key) - del self.__data[key] - self.__currsize -= size - - def __contains__(self, key): - return key in self.__data - - def __missing__(self, key): - raise KeyError(key) - - def __iter__(self): - return iter(self.__data) - - def __len__(self): - return len(self.__data) - - def get(self, key, default=None): - if key in self: - return self[key] - else: - return default - - def pop(self, key, default=__marker): - if key in self: - value = self[key] - del self[key] - elif default is self.__marker: - raise KeyError(key) - else: - value = default - return value - - def setdefault(self, key, default=None): - if key in self: - value = self[key] - else: - self[key] = value = default - return value - - @property - def maxsize(self): - """The maximum size of the cache.""" - return self.__maxsize - - @property - def currsize(self): - """The current size of the cache.""" - return self.__currsize - - @staticmethod - def getsizeof(value): - """Return the size of a cache element's value.""" - return 1 - - -class FIFOCache(Cache): - """First In First Out (FIFO) cache implementation.""" - - def __init__(self, maxsize, getsizeof=None): - Cache.__init__(self, maxsize, getsizeof) - self.__order = collections.OrderedDict() - - def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): - cache_setitem(self, key, value) - try: - self.__order.move_to_end(key) - except KeyError: - self.__order[key] = None - - def __delitem__(self, key, cache_delitem=Cache.__delitem__): - cache_delitem(self, key) - del self.__order[key] - - def popitem(self): - """Remove and return the `(key, value)` pair first inserted.""" - try: - key = next(iter(self.__order)) - except StopIteration: - raise KeyError("%s is empty" % type(self).__name__) from None - else: - return (key, self.pop(key)) - - -class LFUCache(Cache): - """Least Frequently Used (LFU) cache implementation.""" - - def __init__(self, maxsize, getsizeof=None): - Cache.__init__(self, maxsize, getsizeof) - self.__counter = collections.Counter() - - def __getitem__(self, key, cache_getitem=Cache.__getitem__): - value = cache_getitem(self, key) - if key in self: # __missing__ may not store item - self.__counter[key] -= 1 - return value - - def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): - cache_setitem(self, key, value) - self.__counter[key] -= 1 - - def __delitem__(self, key, cache_delitem=Cache.__delitem__): - cache_delitem(self, key) - del self.__counter[key] - - def popitem(self): - """Remove and return the `(key, value)` pair least frequently used.""" - try: - ((key, _),) = self.__counter.most_common(1) - except ValueError: - raise KeyError("%s is empty" % type(self).__name__) from None - else: - return (key, self.pop(key)) - - -class LRUCache(Cache): - """Least Recently Used (LRU) cache implementation.""" - - def __init__(self, maxsize, getsizeof=None): - Cache.__init__(self, maxsize, getsizeof) - self.__order = collections.OrderedDict() - - def __getitem__(self, key, cache_getitem=Cache.__getitem__): - value = cache_getitem(self, key) - if key in self: # __missing__ may not store item - self.__update(key) - return value - - def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): - cache_setitem(self, key, value) - self.__update(key) - - def __delitem__(self, key, cache_delitem=Cache.__delitem__): - cache_delitem(self, key) - del self.__order[key] - - def popitem(self): - """Remove and return the `(key, value)` pair least recently used.""" - try: - key = next(iter(self.__order)) - except StopIteration: - raise KeyError("%s is empty" % type(self).__name__) from None - else: - return (key, self.pop(key)) - - def __update(self, key): - try: - self.__order.move_to_end(key) - except KeyError: - self.__order[key] = None - - -class MRUCache(Cache): - """Most Recently Used (MRU) cache implementation.""" - - def __init__(self, maxsize, getsizeof=None): - Cache.__init__(self, maxsize, getsizeof) - self.__order = collections.OrderedDict() - - def __getitem__(self, key, cache_getitem=Cache.__getitem__): - value = cache_getitem(self, key) - if key in self: # __missing__ may not store item - self.__update(key) - return value - - def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): - cache_setitem(self, key, value) - self.__update(key) - - def __delitem__(self, key, cache_delitem=Cache.__delitem__): - cache_delitem(self, key) - del self.__order[key] - - def popitem(self): - """Remove and return the `(key, value)` pair most recently used.""" - try: - key = next(iter(self.__order)) - except StopIteration: - raise KeyError("%s is empty" % type(self).__name__) from None - else: - return (key, self.pop(key)) - - def __update(self, key): - try: - self.__order.move_to_end(key, last=False) - except KeyError: - self.__order[key] = None - - -class RRCache(Cache): - """Random Replacement (RR) cache implementation.""" - - def __init__(self, maxsize, choice=random.choice, getsizeof=None): - Cache.__init__(self, maxsize, getsizeof) - self.__choice = choice - - @property - def choice(self): - """The `choice` function used by the cache.""" - return self.__choice - - def popitem(self): - """Remove and return a random `(key, value)` pair.""" - try: - key = self.__choice(list(self)) - except IndexError: - raise KeyError("%s is empty" % type(self).__name__) from None - else: - return (key, self.pop(key)) - - -class _TimedCache(Cache): - """Base class for time aware cache implementations.""" - - class _Timer: - def __init__(self, timer): - self.__timer = timer - self.__nesting = 0 - - def __call__(self): - if self.__nesting == 0: - return self.__timer() - else: - return self.__time - - def __enter__(self): - if self.__nesting == 0: - self.__time = time = self.__timer() - else: - time = self.__time - self.__nesting += 1 - return time - - def __exit__(self, *exc): - self.__nesting -= 1 - - def __reduce__(self): - return _TimedCache._Timer, (self.__timer,) - - def __getattr__(self, name): - return getattr(self.__timer, name) - - def __init__(self, maxsize, timer=time.monotonic, getsizeof=None): - Cache.__init__(self, maxsize, getsizeof) - self.__timer = _TimedCache._Timer(timer) - - def __repr__(self, cache_repr=Cache.__repr__): - with self.__timer as time: - self.expire(time) - return cache_repr(self) - - def __len__(self, cache_len=Cache.__len__): - with self.__timer as time: - self.expire(time) - return cache_len(self) - - @property - def currsize(self): - with self.__timer as time: - self.expire(time) - return super().currsize - - @property - def timer(self): - """The timer function used by the cache.""" - return self.__timer - - def clear(self): - with self.__timer as time: - self.expire(time) - Cache.clear(self) - - def get(self, *args, **kwargs): - with self.__timer: - return Cache.get(self, *args, **kwargs) - - def pop(self, *args, **kwargs): - with self.__timer: - return Cache.pop(self, *args, **kwargs) - - def setdefault(self, *args, **kwargs): - with self.__timer: - return Cache.setdefault(self, *args, **kwargs) - - -class TTLCache(_TimedCache): - """LRU Cache implementation with per-item time-to-live (TTL) value.""" - - class _Link: - - __slots__ = ("key", "expires", "next", "prev") - - def __init__(self, key=None, expires=None): - self.key = key - self.expires = expires - - def __reduce__(self): - return TTLCache._Link, (self.key, self.expires) - - def unlink(self): - next = self.next - prev = self.prev - prev.next = next - next.prev = prev - - def __init__(self, maxsize, ttl, timer=time.monotonic, getsizeof=None): - _TimedCache.__init__(self, maxsize, timer, getsizeof) - self.__root = root = TTLCache._Link() - root.prev = root.next = root - self.__links = collections.OrderedDict() - self.__ttl = ttl - - def __contains__(self, key): - try: - link = self.__links[key] # no reordering - except KeyError: - return False - else: - return self.timer() < link.expires - - def __getitem__(self, key, cache_getitem=Cache.__getitem__): - try: - link = self.__getlink(key) - except KeyError: - expired = False - else: - expired = not (self.timer() < link.expires) - if expired: - return self.__missing__(key) - else: - return cache_getitem(self, key) - - def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): - with self.timer as time: - self.expire(time) - cache_setitem(self, key, value) - try: - link = self.__getlink(key) - except KeyError: - self.__links[key] = link = TTLCache._Link(key) - else: - link.unlink() - link.expires = time + self.__ttl - link.next = root = self.__root - link.prev = prev = root.prev - prev.next = root.prev = link - - def __delitem__(self, key, cache_delitem=Cache.__delitem__): - cache_delitem(self, key) - link = self.__links.pop(key) - link.unlink() - if not (self.timer() < link.expires): - raise KeyError(key) - - def __iter__(self): - root = self.__root - curr = root.next - while curr is not root: - # "freeze" time for iterator access - with self.timer as time: - if time < curr.expires: - yield curr.key - curr = curr.next - - def __setstate__(self, state): - self.__dict__.update(state) - root = self.__root - root.prev = root.next = root - for link in sorted(self.__links.values(), key=lambda obj: obj.expires): - link.next = root - link.prev = prev = root.prev - prev.next = root.prev = link - self.expire(self.timer()) - - @property - def ttl(self): - """The time-to-live value of the cache's items.""" - return self.__ttl - - def expire(self, time=None): - """Remove expired items from the cache.""" - if time is None: - time = self.timer() - root = self.__root - curr = root.next - links = self.__links - cache_delitem = Cache.__delitem__ - while curr is not root and not (time < curr.expires): - cache_delitem(self, curr.key) - del links[curr.key] - next = curr.next - curr.unlink() - curr = next - - def popitem(self): - """Remove and return the `(key, value)` pair least recently used that - has not already expired. - - """ - with self.timer as time: - self.expire(time) - try: - key = next(iter(self.__links)) - except StopIteration: - raise KeyError("%s is empty" % type(self).__name__) from None - else: - return (key, self.pop(key)) - - def __getlink(self, key): - value = self.__links[key] - self.__links.move_to_end(key) - return value - - -class TLRUCache(_TimedCache): - """Time aware Least Recently Used (TLRU) cache implementation.""" - - @functools.total_ordering - class _Item: - - __slots__ = ("key", "expires", "removed") - - def __init__(self, key=None, expires=None): - self.key = key - self.expires = expires - self.removed = False - - def __lt__(self, other): - return self.expires < other.expires - - def __init__(self, maxsize, ttu, timer=time.monotonic, getsizeof=None): - _TimedCache.__init__(self, maxsize, timer, getsizeof) - self.__items = collections.OrderedDict() - self.__order = [] - self.__ttu = ttu - - def __contains__(self, key): - try: - item = self.__items[key] # no reordering - except KeyError: - return False - else: - return self.timer() < item.expires - - def __getitem__(self, key, cache_getitem=Cache.__getitem__): - try: - item = self.__getitem(key) - except KeyError: - expired = False - else: - expired = not (self.timer() < item.expires) - if expired: - return self.__missing__(key) - else: - return cache_getitem(self, key) - - def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): - with self.timer as time: - expires = self.__ttu(key, value, time) - if not (time < expires): - return # skip expired items - self.expire(time) - cache_setitem(self, key, value) - # removing an existing item would break the heap structure, so - # only mark it as removed for now - try: - self.__getitem(key).removed = True - except KeyError: - pass - self.__items[key] = item = TLRUCache._Item(key, expires) - heapq.heappush(self.__order, item) - - def __delitem__(self, key, cache_delitem=Cache.__delitem__): - with self.timer as time: - # no self.expire() for performance reasons, e.g. self.clear() [#67] - cache_delitem(self, key) - item = self.__items.pop(key) - item.removed = True - if not (time < item.expires): - raise KeyError(key) - - def __iter__(self): - for curr in self.__order: - # "freeze" time for iterator access - with self.timer as time: - if time < curr.expires and not curr.removed: - yield curr.key - - @property - def ttu(self): - """The local time-to-use function used by the cache.""" - return self.__ttu - - def expire(self, time=None): - """Remove expired items from the cache.""" - if time is None: - time = self.timer() - items = self.__items - order = self.__order - # clean up the heap if too many items are marked as removed - if len(order) > len(items) * 2: - self.__order = order = [item for item in order if not item.removed] - heapq.heapify(order) - cache_delitem = Cache.__delitem__ - while order and (order[0].removed or not (time < order[0].expires)): - item = heapq.heappop(order) - if not item.removed: - cache_delitem(self, item.key) - del items[item.key] - - def popitem(self): - """Remove and return the `(key, value)` pair least recently used that - has not already expired. - - """ - with self.timer as time: - self.expire(time) - try: - key = next(iter(self.__items)) - except StopIteration: - raise KeyError("%s is empty" % self.__class__.__name__) from None - else: - return (key, self.pop(key)) - - def __getitem(self, key): - value = self.__items[key] - self.__items.move_to_end(key) - return value - - -def cached(cache, key=_defaultkey, lock=None): - """Decorator to wrap a function with a memoizing callable that saves - results in a cache. - - """ - - def decorator(func): - if cache is None: - - def wrapper(*args, **kwargs): - return func(*args, **kwargs) - - elif lock is None: - - def wrapper(*args, **kwargs): - k = key(*args, **kwargs) - try: - return cache[k] - except KeyError: - pass # key not found - v = func(*args, **kwargs) - try: - cache[k] = v - except ValueError: - pass # value too large - return v - - else: - - def wrapper(*args, **kwargs): - k = key(*args, **kwargs) - try: - with lock: - return cache[k] - except KeyError: - pass # key not found - v = func(*args, **kwargs) - # in case of a race, prefer the item already in the cache - try: - with lock: - return cache.setdefault(k, v) - except ValueError: - return v # value too large - - return functools.update_wrapper(wrapper, func) - - return decorator - - -def cachedmethod(cache, key=_methodkey, lock=None): - """Decorator to wrap a class or instance method with a memoizing - callable that saves results in a cache. - - """ - - def decorator(method): - if lock is None: - - def wrapper(self, *args, **kwargs): - c = cache(self) - if c is None: - return method(self, *args, **kwargs) - k = key(self, *args, **kwargs) - try: - return c[k] - except KeyError: - pass # key not found - v = method(self, *args, **kwargs) - try: - c[k] = v - except ValueError: - pass # value too large - return v - - else: - - def wrapper(self, *args, **kwargs): - c = cache(self) - if c is None: - return method(self, *args, **kwargs) - k = key(self, *args, **kwargs) - try: - with lock(self): - return c[k] - except KeyError: - pass # key not found - v = method(self, *args, **kwargs) - # in case of a race, prefer the item already in the cache - try: - with lock(self): - return c.setdefault(k, v) - except ValueError: - return v # value too large - - return functools.update_wrapper(wrapper, method) - - return decorator diff --git a/telegramer/include/cachetools/func.py b/telegramer/include/cachetools/func.py deleted file mode 100644 index 01702c2..0000000 --- a/telegramer/include/cachetools/func.py +++ /dev/null @@ -1,171 +0,0 @@ -"""`functools.lru_cache` compatible memoizing function decorators.""" - -__all__ = ("fifo_cache", "lfu_cache", "lru_cache", "mru_cache", "rr_cache", "ttl_cache") - -import collections -import functools -import math -import random -import time - -try: - from threading import RLock -except ImportError: # pragma: no cover - from dummy_threading import RLock - -from . import FIFOCache, LFUCache, LRUCache, MRUCache, RRCache, TTLCache -from . import keys - - -_CacheInfo = collections.namedtuple( - "CacheInfo", ["hits", "misses", "maxsize", "currsize"] -) - - -class _UnboundCache(dict): - @property - def maxsize(self): - return None - - @property - def currsize(self): - return len(self) - - -class _UnboundTTLCache(TTLCache): - def __init__(self, ttl, timer): - TTLCache.__init__(self, math.inf, ttl, timer) - - @property - def maxsize(self): - return None - - -def _cache(cache, typed): - maxsize = cache.maxsize - - def decorator(func): - key = keys.typedkey if typed else keys.hashkey - lock = RLock() - stats = [0, 0] - - def wrapper(*args, **kwargs): - k = key(*args, **kwargs) - with lock: - try: - v = cache[k] - stats[0] += 1 - return v - except KeyError: - stats[1] += 1 - v = func(*args, **kwargs) - # in case of a race, prefer the item already in the cache - try: - with lock: - return cache.setdefault(k, v) - except ValueError: - return v # value too large - - def cache_info(): - with lock: - hits, misses = stats - maxsize = cache.maxsize - currsize = cache.currsize - return _CacheInfo(hits, misses, maxsize, currsize) - - def cache_clear(): - with lock: - try: - cache.clear() - finally: - stats[:] = [0, 0] - - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - wrapper.cache_parameters = lambda: {"maxsize": maxsize, "typed": typed} - functools.update_wrapper(wrapper, func) - return wrapper - - return decorator - - -def fifo_cache(maxsize=128, typed=False): - """Decorator to wrap a function with a memoizing callable that saves - up to `maxsize` results based on a First In First Out (FIFO) - algorithm. - - """ - if maxsize is None: - return _cache(_UnboundCache(), typed) - elif callable(maxsize): - return _cache(FIFOCache(128), typed)(maxsize) - else: - return _cache(FIFOCache(maxsize), typed) - - -def lfu_cache(maxsize=128, typed=False): - """Decorator to wrap a function with a memoizing callable that saves - up to `maxsize` results based on a Least Frequently Used (LFU) - algorithm. - - """ - if maxsize is None: - return _cache(_UnboundCache(), typed) - elif callable(maxsize): - return _cache(LFUCache(128), typed)(maxsize) - else: - return _cache(LFUCache(maxsize), typed) - - -def lru_cache(maxsize=128, typed=False): - """Decorator to wrap a function with a memoizing callable that saves - up to `maxsize` results based on a Least Recently Used (LRU) - algorithm. - - """ - if maxsize is None: - return _cache(_UnboundCache(), typed) - elif callable(maxsize): - return _cache(LRUCache(128), typed)(maxsize) - else: - return _cache(LRUCache(maxsize), typed) - - -def mru_cache(maxsize=128, typed=False): - """Decorator to wrap a function with a memoizing callable that saves - up to `maxsize` results based on a Most Recently Used (MRU) - algorithm. - """ - if maxsize is None: - return _cache(_UnboundCache(), typed) - elif callable(maxsize): - return _cache(MRUCache(128), typed)(maxsize) - else: - return _cache(MRUCache(maxsize), typed) - - -def rr_cache(maxsize=128, choice=random.choice, typed=False): - """Decorator to wrap a function with a memoizing callable that saves - up to `maxsize` results based on a Random Replacement (RR) - algorithm. - - """ - if maxsize is None: - return _cache(_UnboundCache(), typed) - elif callable(maxsize): - return _cache(RRCache(128, choice), typed)(maxsize) - else: - return _cache(RRCache(maxsize, choice), typed) - - -def ttl_cache(maxsize=128, ttl=600, timer=time.monotonic, typed=False): - """Decorator to wrap a function with a memoizing callable that saves - up to `maxsize` results based on a Least Recently Used (LRU) - algorithm with a per-item time-to-live (TTL) value. - """ - if maxsize is None: - return _cache(_UnboundTTLCache(ttl, timer), typed) - elif callable(maxsize): - return _cache(TTLCache(128, ttl, timer), typed)(maxsize) - else: - return _cache(TTLCache(maxsize, ttl, timer), typed) diff --git a/telegramer/include/cachetools/keys.py b/telegramer/include/cachetools/keys.py deleted file mode 100644 index 13630a4..0000000 --- a/telegramer/include/cachetools/keys.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Key functions for memoizing decorators.""" - -__all__ = ("hashkey", "typedkey") - - -class _HashedTuple(tuple): - """A tuple that ensures that hash() will be called no more than once - per element, since cache decorators will hash the key multiple - times on a cache miss. See also _HashedSeq in the standard - library functools implementation. - - """ - - __hashvalue = None - - def __hash__(self, hash=tuple.__hash__): - hashvalue = self.__hashvalue - if hashvalue is None: - self.__hashvalue = hashvalue = hash(self) - return hashvalue - - def __add__(self, other, add=tuple.__add__): - return _HashedTuple(add(self, other)) - - def __radd__(self, other, add=tuple.__add__): - return _HashedTuple(add(other, self)) - - def __getstate__(self): - return {} - - -# used for separating keyword arguments; we do not use an object -# instance here so identity is preserved when pickling/unpickling -_kwmark = (_HashedTuple,) - - -def hashkey(*args, **kwargs): - """Return a cache key for the specified hashable arguments.""" - - if kwargs: - return _HashedTuple(args + sum(sorted(kwargs.items()), _kwmark)) - else: - return _HashedTuple(args) - - -def typedkey(*args, **kwargs): - """Return a typed cache key for the specified hashable arguments.""" - - key = hashkey(*args, **kwargs) - key += tuple(type(v) for v in args) - key += tuple(type(v) for _, v in sorted(kwargs.items())) - return key diff --git a/telegramer/include/imghdr/__init__.py b/telegramer/include/imghdr/__init__.py index 1683024..fe0337e 100644 --- a/telegramer/include/imghdr/__init__.py +++ b/telegramer/include/imghdr/__init__.py @@ -144,18 +144,18 @@ def testall(list, recursive, toplevel): import os for filename in list: if os.path.isdir(filename): - print filename + '/:', + print(filename + '/:', end=' '), if recursive or toplevel: print 'recursing down:' import glob names = glob.glob(os.path.join(filename, '*')) testall(names, recursive, 0) else: - print '*** directory (use -r) ***' + print('*** directory (use -r) ***') else: - print filename + ':', + print(filename + ':', end=' ') sys.stdout.flush() try: print what(filename) except IOError: - print '*** not found ***' + print('*** not found ***') diff --git a/telegramer/include/pytz/__init__.py b/telegramer/include/pytz/__init__.py deleted file mode 100644 index 900e8ca..0000000 --- a/telegramer/include/pytz/__init__.py +++ /dev/null @@ -1,1559 +0,0 @@ -''' -datetime.tzinfo timezone definitions generated from the -Olson timezone database: - - ftp://elsie.nci.nih.gov/pub/tz*.tar.gz - -See the datetime section of the Python Library Reference for information -on how to use these modules. -''' - -import sys -import datetime -import os.path - -from pytz.exceptions import AmbiguousTimeError -from pytz.exceptions import InvalidTimeError -from pytz.exceptions import NonExistentTimeError -from pytz.exceptions import UnknownTimeZoneError -from pytz.lazy import LazyDict, LazyList, LazySet # noqa -from pytz.tzinfo import unpickler, BaseTzInfo -from pytz.tzfile import build_tzinfo - - -# The IANA (nee Olson) database is updated several times a year. -OLSON_VERSION = '2022a' -VERSION = '2022.1' # pip compatible version number. -__version__ = VERSION - -OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling - -__all__ = [ - 'timezone', 'utc', 'country_timezones', 'country_names', - 'AmbiguousTimeError', 'InvalidTimeError', - 'NonExistentTimeError', 'UnknownTimeZoneError', - 'all_timezones', 'all_timezones_set', - 'common_timezones', 'common_timezones_set', - 'BaseTzInfo', 'FixedOffset', -] - - -if sys.version_info[0] > 2: # Python 3.x - - # Python 3.x doesn't have unicode(), making writing code - # for Python 2.3 and Python 3.x a pain. - unicode = str - - def ascii(s): - r""" - >>> ascii('Hello') - 'Hello' - >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - UnicodeEncodeError: ... - """ - if type(s) == bytes: - s = s.decode('ASCII') - else: - s.encode('ASCII') # Raise an exception if not ASCII - return s # But the string - not a byte string. - -else: # Python 2.x - - def ascii(s): - r""" - >>> ascii('Hello') - 'Hello' - >>> ascii(u'Hello') - 'Hello' - >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - UnicodeEncodeError: ... - """ - return s.encode('ASCII') - - -def open_resource(name): - """Open a resource from the zoneinfo subdir for reading. - - Uses the pkg_resources module if available and no standard file - found at the calculated location. - - It is possible to specify different location for zoneinfo - subdir by using the PYTZ_TZDATADIR environment variable. - """ - name_parts = name.lstrip('/').split('/') - for part in name_parts: - if part == os.path.pardir or os.path.sep in part: - raise ValueError('Bad path segment: %r' % part) - zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None) - if zoneinfo_dir is not None: - filename = os.path.join(zoneinfo_dir, *name_parts) - else: - filename = os.path.join(os.path.dirname(__file__), - 'zoneinfo', *name_parts) - if not os.path.exists(filename): - # http://bugs.launchpad.net/bugs/383171 - we avoid using this - # unless absolutely necessary to help when a broken version of - # pkg_resources is installed. - try: - from pkg_resources import resource_stream - except ImportError: - resource_stream = None - - if resource_stream is not None: - return resource_stream(__name__, 'zoneinfo/' + name) - return open(filename, 'rb') - - -def resource_exists(name): - """Return true if the given resource exists""" - try: - if os.environ.get('PYTZ_SKIPEXISTSCHECK', ''): - # In "standard" distributions, we can assume that - # all the listed timezones are present. As an - # import-speed optimization, you can set the - # PYTZ_SKIPEXISTSCHECK flag to skip checking - # for the presence of the resource file on disk. - return True - open_resource(name).close() - return True - except IOError: - return False - - -_tzinfo_cache = {} - - -def timezone(zone): - r''' Return a datetime.tzinfo implementation for the given timezone - - >>> from datetime import datetime, timedelta - >>> utc = timezone('UTC') - >>> eastern = timezone('US/Eastern') - >>> eastern.zone - 'US/Eastern' - >>> timezone(unicode('US/Eastern')) is eastern - True - >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) - >>> loc_dt = utc_dt.astimezone(eastern) - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> loc_dt.strftime(fmt) - '2002-10-27 01:00:00 EST (-0500)' - >>> (loc_dt - timedelta(minutes=10)).strftime(fmt) - '2002-10-27 00:50:00 EST (-0500)' - >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt) - '2002-10-27 01:50:00 EDT (-0400)' - >>> (loc_dt + timedelta(minutes=10)).strftime(fmt) - '2002-10-27 01:10:00 EST (-0500)' - - Raises UnknownTimeZoneError if passed an unknown zone. - - >>> try: - ... timezone('Asia/Shangri-La') - ... except UnknownTimeZoneError: - ... print('Unknown') - Unknown - - >>> try: - ... timezone(unicode('\N{TRADE MARK SIGN}')) - ... except UnknownTimeZoneError: - ... print('Unknown') - Unknown - - ''' - if zone is None: - raise UnknownTimeZoneError(None) - - if zone.upper() == 'UTC': - return utc - - try: - zone = ascii(zone) - except UnicodeEncodeError: - # All valid timezones are ASCII - raise UnknownTimeZoneError(zone) - - zone = _case_insensitive_zone_lookup(_unmunge_zone(zone)) - if zone not in _tzinfo_cache: - if zone in all_timezones_set: # noqa - fp = open_resource(zone) - try: - _tzinfo_cache[zone] = build_tzinfo(zone, fp) - finally: - fp.close() - else: - raise UnknownTimeZoneError(zone) - - return _tzinfo_cache[zone] - - -def _unmunge_zone(zone): - """Undo the time zone name munging done by older versions of pytz.""" - return zone.replace('_plus_', '+').replace('_minus_', '-') - - -_all_timezones_lower_to_standard = None - - -def _case_insensitive_zone_lookup(zone): - """case-insensitively matching timezone, else return zone unchanged""" - global _all_timezones_lower_to_standard - if _all_timezones_lower_to_standard is None: - _all_timezones_lower_to_standard = dict((tz.lower(), tz) for tz in all_timezones) # noqa - return _all_timezones_lower_to_standard.get(zone.lower()) or zone # noqa - - -ZERO = datetime.timedelta(0) -HOUR = datetime.timedelta(hours=1) - - -class UTC(BaseTzInfo): - """UTC - - Optimized UTC implementation. It unpickles using the single module global - instance defined beneath this class declaration. - """ - zone = "UTC" - - _utcoffset = ZERO - _dst = ZERO - _tzname = zone - - def fromutc(self, dt): - if dt.tzinfo is None: - return self.localize(dt) - return super(utc.__class__, self).fromutc(dt) - - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO - - def __reduce__(self): - return _UTC, () - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - return dt.replace(tzinfo=self) - - def normalize(self, dt, is_dst=False): - '''Correct the timezone information on the given datetime''' - if dt.tzinfo is self: - return dt - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - return dt.astimezone(self) - - def __repr__(self): - return "" - - def __str__(self): - return "UTC" - - -UTC = utc = UTC() # UTC is a singleton - - -def _UTC(): - """Factory function for utc unpickling. - - Makes sure that unpickling a utc instance always returns the same - module global. - - These examples belong in the UTC class above, but it is obscured; or in - the README.rst, but we are not depending on Python 2.4 so integrating - the README.rst examples with the unit tests is not trivial. - - >>> import datetime, pickle - >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) - >>> naive = dt.replace(tzinfo=None) - >>> p = pickle.dumps(dt, 1) - >>> naive_p = pickle.dumps(naive, 1) - >>> len(p) - len(naive_p) - 17 - >>> new = pickle.loads(p) - >>> new == dt - True - >>> new is dt - False - >>> new.tzinfo is dt.tzinfo - True - >>> utc is UTC is timezone('UTC') - True - >>> utc is timezone('GMT') - False - """ - return utc - - -_UTC.__safe_for_unpickling__ = True - - -def _p(*args): - """Factory function for unpickling pytz tzinfo instances. - - Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle - by shortening the path. - """ - return unpickler(*args) - - -_p.__safe_for_unpickling__ = True - - -class _CountryTimezoneDict(LazyDict): - """Map ISO 3166 country code to a list of timezone names commonly used - in that country. - - iso3166_code is the two letter code used to identify the country. - - >>> def print_list(list_of_strings): - ... 'We use a helper so doctests work under Python 2.3 -> 3.x' - ... for s in list_of_strings: - ... print(s) - - >>> print_list(country_timezones['nz']) - Pacific/Auckland - Pacific/Chatham - >>> print_list(country_timezones['ch']) - Europe/Zurich - >>> print_list(country_timezones['CH']) - Europe/Zurich - >>> print_list(country_timezones[unicode('ch')]) - Europe/Zurich - >>> print_list(country_timezones['XXX']) - Traceback (most recent call last): - ... - KeyError: 'XXX' - - Previously, this information was exposed as a function rather than a - dictionary. This is still supported:: - - >>> print_list(country_timezones('nz')) - Pacific/Auckland - Pacific/Chatham - """ - def __call__(self, iso3166_code): - """Backwards compatibility.""" - return self[iso3166_code] - - def _fill(self): - data = {} - zone_tab = open_resource('zone.tab') - try: - for line in zone_tab: - line = line.decode('UTF-8') - if line.startswith('#'): - continue - code, coordinates, zone = line.split(None, 4)[:3] - if zone not in all_timezones_set: # noqa - continue - try: - data[code].append(zone) - except KeyError: - data[code] = [zone] - self.data = data - finally: - zone_tab.close() - - -country_timezones = _CountryTimezoneDict() - - -class _CountryNameDict(LazyDict): - '''Dictionary proving ISO3166 code -> English name. - - >>> print(country_names['au']) - Australia - ''' - def _fill(self): - data = {} - zone_tab = open_resource('iso3166.tab') - try: - for line in zone_tab.readlines(): - line = line.decode('UTF-8') - if line.startswith('#'): - continue - code, name = line.split(None, 1) - data[code] = name.strip() - self.data = data - finally: - zone_tab.close() - - -country_names = _CountryNameDict() - - -# Time-zone info based solely on fixed offsets - -class _FixedOffset(datetime.tzinfo): - - zone = None # to match the standard pytz API - - def __init__(self, minutes): - if abs(minutes) >= 1440: - raise ValueError("absolute offset is too large", minutes) - self._minutes = minutes - self._offset = datetime.timedelta(minutes=minutes) - - def utcoffset(self, dt): - return self._offset - - def __reduce__(self): - return FixedOffset, (self._minutes, ) - - def dst(self, dt): - return ZERO - - def tzname(self, dt): - return None - - def __repr__(self): - return 'pytz.FixedOffset(%d)' % self._minutes - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - return dt.replace(tzinfo=self) - - def normalize(self, dt, is_dst=False): - '''Correct the timezone information on the given datetime''' - if dt.tzinfo is self: - return dt - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - return dt.astimezone(self) - - -def FixedOffset(offset, _tzinfos={}): - """return a fixed-offset timezone based off a number of minutes. - - >>> one = FixedOffset(-330) - >>> one - pytz.FixedOffset(-330) - >>> str(one.utcoffset(datetime.datetime.now())) - '-1 day, 18:30:00' - >>> str(one.dst(datetime.datetime.now())) - '0:00:00' - - >>> two = FixedOffset(1380) - >>> two - pytz.FixedOffset(1380) - >>> str(two.utcoffset(datetime.datetime.now())) - '23:00:00' - >>> str(two.dst(datetime.datetime.now())) - '0:00:00' - - The datetime.timedelta must be between the range of -1 and 1 day, - non-inclusive. - - >>> FixedOffset(1440) - Traceback (most recent call last): - ... - ValueError: ('absolute offset is too large', 1440) - - >>> FixedOffset(-1440) - Traceback (most recent call last): - ... - ValueError: ('absolute offset is too large', -1440) - - An offset of 0 is special-cased to return UTC. - - >>> FixedOffset(0) is UTC - True - - There should always be only one instance of a FixedOffset per timedelta. - This should be true for multiple creation calls. - - >>> FixedOffset(-330) is one - True - >>> FixedOffset(1380) is two - True - - It should also be true for pickling. - - >>> import pickle - >>> pickle.loads(pickle.dumps(one)) is one - True - >>> pickle.loads(pickle.dumps(two)) is two - True - """ - if offset == 0: - return UTC - - info = _tzinfos.get(offset) - if info is None: - # We haven't seen this one before. we need to save it. - - # Use setdefault to avoid a race condition and make sure we have - # only one - info = _tzinfos.setdefault(offset, _FixedOffset(offset)) - - return info - - -FixedOffset.__safe_for_unpickling__ = True - - -def _test(): - import doctest - sys.path.insert(0, os.pardir) - import pytz - return doctest.testmod(pytz) - - -if __name__ == '__main__': - _test() -all_timezones = \ -['Africa/Abidjan', - 'Africa/Accra', - 'Africa/Addis_Ababa', - 'Africa/Algiers', - 'Africa/Asmara', - 'Africa/Asmera', - 'Africa/Bamako', - 'Africa/Bangui', - 'Africa/Banjul', - 'Africa/Bissau', - 'Africa/Blantyre', - 'Africa/Brazzaville', - 'Africa/Bujumbura', - 'Africa/Cairo', - 'Africa/Casablanca', - 'Africa/Ceuta', - 'Africa/Conakry', - 'Africa/Dakar', - 'Africa/Dar_es_Salaam', - 'Africa/Djibouti', - 'Africa/Douala', - 'Africa/El_Aaiun', - 'Africa/Freetown', - 'Africa/Gaborone', - 'Africa/Harare', - 'Africa/Johannesburg', - 'Africa/Juba', - 'Africa/Kampala', - 'Africa/Khartoum', - 'Africa/Kigali', - 'Africa/Kinshasa', - 'Africa/Lagos', - 'Africa/Libreville', - 'Africa/Lome', - 'Africa/Luanda', - 'Africa/Lubumbashi', - 'Africa/Lusaka', - 'Africa/Malabo', - 'Africa/Maputo', - 'Africa/Maseru', - 'Africa/Mbabane', - 'Africa/Mogadishu', - 'Africa/Monrovia', - 'Africa/Nairobi', - 'Africa/Ndjamena', - 'Africa/Niamey', - 'Africa/Nouakchott', - 'Africa/Ouagadougou', - 'Africa/Porto-Novo', - 'Africa/Sao_Tome', - 'Africa/Timbuktu', - 'Africa/Tripoli', - 'Africa/Tunis', - 'Africa/Windhoek', - 'America/Adak', - 'America/Anchorage', - 'America/Anguilla', - 'America/Antigua', - 'America/Araguaina', - 'America/Argentina/Buenos_Aires', - 'America/Argentina/Catamarca', - 'America/Argentina/ComodRivadavia', - 'America/Argentina/Cordoba', - 'America/Argentina/Jujuy', - 'America/Argentina/La_Rioja', - 'America/Argentina/Mendoza', - 'America/Argentina/Rio_Gallegos', - 'America/Argentina/Salta', - 'America/Argentina/San_Juan', - 'America/Argentina/San_Luis', - 'America/Argentina/Tucuman', - 'America/Argentina/Ushuaia', - 'America/Aruba', - 'America/Asuncion', - 'America/Atikokan', - 'America/Atka', - 'America/Bahia', - 'America/Bahia_Banderas', - 'America/Barbados', - 'America/Belem', - 'America/Belize', - 'America/Blanc-Sablon', - 'America/Boa_Vista', - 'America/Bogota', - 'America/Boise', - 'America/Buenos_Aires', - 'America/Cambridge_Bay', - 'America/Campo_Grande', - 'America/Cancun', - 'America/Caracas', - 'America/Catamarca', - 'America/Cayenne', - 'America/Cayman', - 'America/Chicago', - 'America/Chihuahua', - 'America/Coral_Harbour', - 'America/Cordoba', - 'America/Costa_Rica', - 'America/Creston', - 'America/Cuiaba', - 'America/Curacao', - 'America/Danmarkshavn', - 'America/Dawson', - 'America/Dawson_Creek', - 'America/Denver', - 'America/Detroit', - 'America/Dominica', - 'America/Edmonton', - 'America/Eirunepe', - 'America/El_Salvador', - 'America/Ensenada', - 'America/Fort_Nelson', - 'America/Fort_Wayne', - 'America/Fortaleza', - 'America/Glace_Bay', - 'America/Godthab', - 'America/Goose_Bay', - 'America/Grand_Turk', - 'America/Grenada', - 'America/Guadeloupe', - 'America/Guatemala', - 'America/Guayaquil', - 'America/Guyana', - 'America/Halifax', - 'America/Havana', - 'America/Hermosillo', - 'America/Indiana/Indianapolis', - 'America/Indiana/Knox', - 'America/Indiana/Marengo', - 'America/Indiana/Petersburg', - 'America/Indiana/Tell_City', - 'America/Indiana/Vevay', - 'America/Indiana/Vincennes', - 'America/Indiana/Winamac', - 'America/Indianapolis', - 'America/Inuvik', - 'America/Iqaluit', - 'America/Jamaica', - 'America/Jujuy', - 'America/Juneau', - 'America/Kentucky/Louisville', - 'America/Kentucky/Monticello', - 'America/Knox_IN', - 'America/Kralendijk', - 'America/La_Paz', - 'America/Lima', - 'America/Los_Angeles', - 'America/Louisville', - 'America/Lower_Princes', - 'America/Maceio', - 'America/Managua', - 'America/Manaus', - 'America/Marigot', - 'America/Martinique', - 'America/Matamoros', - 'America/Mazatlan', - 'America/Mendoza', - 'America/Menominee', - 'America/Merida', - 'America/Metlakatla', - 'America/Mexico_City', - 'America/Miquelon', - 'America/Moncton', - 'America/Monterrey', - 'America/Montevideo', - 'America/Montreal', - 'America/Montserrat', - 'America/Nassau', - 'America/New_York', - 'America/Nipigon', - 'America/Nome', - 'America/Noronha', - 'America/North_Dakota/Beulah', - 'America/North_Dakota/Center', - 'America/North_Dakota/New_Salem', - 'America/Nuuk', - 'America/Ojinaga', - 'America/Panama', - 'America/Pangnirtung', - 'America/Paramaribo', - 'America/Phoenix', - 'America/Port-au-Prince', - 'America/Port_of_Spain', - 'America/Porto_Acre', - 'America/Porto_Velho', - 'America/Puerto_Rico', - 'America/Punta_Arenas', - 'America/Rainy_River', - 'America/Rankin_Inlet', - 'America/Recife', - 'America/Regina', - 'America/Resolute', - 'America/Rio_Branco', - 'America/Rosario', - 'America/Santa_Isabel', - 'America/Santarem', - 'America/Santiago', - 'America/Santo_Domingo', - 'America/Sao_Paulo', - 'America/Scoresbysund', - 'America/Shiprock', - 'America/Sitka', - 'America/St_Barthelemy', - 'America/St_Johns', - 'America/St_Kitts', - 'America/St_Lucia', - 'America/St_Thomas', - 'America/St_Vincent', - 'America/Swift_Current', - 'America/Tegucigalpa', - 'America/Thule', - 'America/Thunder_Bay', - 'America/Tijuana', - 'America/Toronto', - 'America/Tortola', - 'America/Vancouver', - 'America/Virgin', - 'America/Whitehorse', - 'America/Winnipeg', - 'America/Yakutat', - 'America/Yellowknife', - 'Antarctica/Casey', - 'Antarctica/Davis', - 'Antarctica/DumontDUrville', - 'Antarctica/Macquarie', - 'Antarctica/Mawson', - 'Antarctica/McMurdo', - 'Antarctica/Palmer', - 'Antarctica/Rothera', - 'Antarctica/South_Pole', - 'Antarctica/Syowa', - 'Antarctica/Troll', - 'Antarctica/Vostok', - 'Arctic/Longyearbyen', - 'Asia/Aden', - 'Asia/Almaty', - 'Asia/Amman', - 'Asia/Anadyr', - 'Asia/Aqtau', - 'Asia/Aqtobe', - 'Asia/Ashgabat', - 'Asia/Ashkhabad', - 'Asia/Atyrau', - 'Asia/Baghdad', - 'Asia/Bahrain', - 'Asia/Baku', - 'Asia/Bangkok', - 'Asia/Barnaul', - 'Asia/Beirut', - 'Asia/Bishkek', - 'Asia/Brunei', - 'Asia/Calcutta', - 'Asia/Chita', - 'Asia/Choibalsan', - 'Asia/Chongqing', - 'Asia/Chungking', - 'Asia/Colombo', - 'Asia/Dacca', - 'Asia/Damascus', - 'Asia/Dhaka', - 'Asia/Dili', - 'Asia/Dubai', - 'Asia/Dushanbe', - 'Asia/Famagusta', - 'Asia/Gaza', - 'Asia/Harbin', - 'Asia/Hebron', - 'Asia/Ho_Chi_Minh', - 'Asia/Hong_Kong', - 'Asia/Hovd', - 'Asia/Irkutsk', - 'Asia/Istanbul', - 'Asia/Jakarta', - 'Asia/Jayapura', - 'Asia/Jerusalem', - 'Asia/Kabul', - 'Asia/Kamchatka', - 'Asia/Karachi', - 'Asia/Kashgar', - 'Asia/Kathmandu', - 'Asia/Katmandu', - 'Asia/Khandyga', - 'Asia/Kolkata', - 'Asia/Krasnoyarsk', - 'Asia/Kuala_Lumpur', - 'Asia/Kuching', - 'Asia/Kuwait', - 'Asia/Macao', - 'Asia/Macau', - 'Asia/Magadan', - 'Asia/Makassar', - 'Asia/Manila', - 'Asia/Muscat', - 'Asia/Nicosia', - 'Asia/Novokuznetsk', - 'Asia/Novosibirsk', - 'Asia/Omsk', - 'Asia/Oral', - 'Asia/Phnom_Penh', - 'Asia/Pontianak', - 'Asia/Pyongyang', - 'Asia/Qatar', - 'Asia/Qostanay', - 'Asia/Qyzylorda', - 'Asia/Rangoon', - 'Asia/Riyadh', - 'Asia/Saigon', - 'Asia/Sakhalin', - 'Asia/Samarkand', - 'Asia/Seoul', - 'Asia/Shanghai', - 'Asia/Singapore', - 'Asia/Srednekolymsk', - 'Asia/Taipei', - 'Asia/Tashkent', - 'Asia/Tbilisi', - 'Asia/Tehran', - 'Asia/Tel_Aviv', - 'Asia/Thimbu', - 'Asia/Thimphu', - 'Asia/Tokyo', - 'Asia/Tomsk', - 'Asia/Ujung_Pandang', - 'Asia/Ulaanbaatar', - 'Asia/Ulan_Bator', - 'Asia/Urumqi', - 'Asia/Ust-Nera', - 'Asia/Vientiane', - 'Asia/Vladivostok', - 'Asia/Yakutsk', - 'Asia/Yangon', - 'Asia/Yekaterinburg', - 'Asia/Yerevan', - 'Atlantic/Azores', - 'Atlantic/Bermuda', - 'Atlantic/Canary', - 'Atlantic/Cape_Verde', - 'Atlantic/Faeroe', - 'Atlantic/Faroe', - 'Atlantic/Jan_Mayen', - 'Atlantic/Madeira', - 'Atlantic/Reykjavik', - 'Atlantic/South_Georgia', - 'Atlantic/St_Helena', - 'Atlantic/Stanley', - 'Australia/ACT', - 'Australia/Adelaide', - 'Australia/Brisbane', - 'Australia/Broken_Hill', - 'Australia/Canberra', - 'Australia/Currie', - 'Australia/Darwin', - 'Australia/Eucla', - 'Australia/Hobart', - 'Australia/LHI', - 'Australia/Lindeman', - 'Australia/Lord_Howe', - 'Australia/Melbourne', - 'Australia/NSW', - 'Australia/North', - 'Australia/Perth', - 'Australia/Queensland', - 'Australia/South', - 'Australia/Sydney', - 'Australia/Tasmania', - 'Australia/Victoria', - 'Australia/West', - 'Australia/Yancowinna', - 'Brazil/Acre', - 'Brazil/DeNoronha', - 'Brazil/East', - 'Brazil/West', - 'CET', - 'CST6CDT', - 'Canada/Atlantic', - 'Canada/Central', - 'Canada/Eastern', - 'Canada/Mountain', - 'Canada/Newfoundland', - 'Canada/Pacific', - 'Canada/Saskatchewan', - 'Canada/Yukon', - 'Chile/Continental', - 'Chile/EasterIsland', - 'Cuba', - 'EET', - 'EST', - 'EST5EDT', - 'Egypt', - 'Eire', - 'Etc/GMT', - 'Etc/GMT+0', - 'Etc/GMT+1', - 'Etc/GMT+10', - 'Etc/GMT+11', - 'Etc/GMT+12', - 'Etc/GMT+2', - 'Etc/GMT+3', - 'Etc/GMT+4', - 'Etc/GMT+5', - 'Etc/GMT+6', - 'Etc/GMT+7', - 'Etc/GMT+8', - 'Etc/GMT+9', - 'Etc/GMT-0', - 'Etc/GMT-1', - 'Etc/GMT-10', - 'Etc/GMT-11', - 'Etc/GMT-12', - 'Etc/GMT-13', - 'Etc/GMT-14', - 'Etc/GMT-2', - 'Etc/GMT-3', - 'Etc/GMT-4', - 'Etc/GMT-5', - 'Etc/GMT-6', - 'Etc/GMT-7', - 'Etc/GMT-8', - 'Etc/GMT-9', - 'Etc/GMT0', - 'Etc/Greenwich', - 'Etc/UCT', - 'Etc/UTC', - 'Etc/Universal', - 'Etc/Zulu', - 'Europe/Amsterdam', - 'Europe/Andorra', - 'Europe/Astrakhan', - 'Europe/Athens', - 'Europe/Belfast', - 'Europe/Belgrade', - 'Europe/Berlin', - 'Europe/Bratislava', - 'Europe/Brussels', - 'Europe/Bucharest', - 'Europe/Budapest', - 'Europe/Busingen', - 'Europe/Chisinau', - 'Europe/Copenhagen', - 'Europe/Dublin', - 'Europe/Gibraltar', - 'Europe/Guernsey', - 'Europe/Helsinki', - 'Europe/Isle_of_Man', - 'Europe/Istanbul', - 'Europe/Jersey', - 'Europe/Kaliningrad', - 'Europe/Kiev', - 'Europe/Kirov', - 'Europe/Lisbon', - 'Europe/Ljubljana', - 'Europe/London', - 'Europe/Luxembourg', - 'Europe/Madrid', - 'Europe/Malta', - 'Europe/Mariehamn', - 'Europe/Minsk', - 'Europe/Monaco', - 'Europe/Moscow', - 'Europe/Nicosia', - 'Europe/Oslo', - 'Europe/Paris', - 'Europe/Podgorica', - 'Europe/Prague', - 'Europe/Riga', - 'Europe/Rome', - 'Europe/Samara', - 'Europe/San_Marino', - 'Europe/Sarajevo', - 'Europe/Saratov', - 'Europe/Simferopol', - 'Europe/Skopje', - 'Europe/Sofia', - 'Europe/Stockholm', - 'Europe/Tallinn', - 'Europe/Tirane', - 'Europe/Tiraspol', - 'Europe/Ulyanovsk', - 'Europe/Uzhgorod', - 'Europe/Vaduz', - 'Europe/Vatican', - 'Europe/Vienna', - 'Europe/Vilnius', - 'Europe/Volgograd', - 'Europe/Warsaw', - 'Europe/Zagreb', - 'Europe/Zaporozhye', - 'Europe/Zurich', - 'GB', - 'GB-Eire', - 'GMT', - 'GMT+0', - 'GMT-0', - 'GMT0', - 'Greenwich', - 'HST', - 'Hongkong', - 'Iceland', - 'Indian/Antananarivo', - 'Indian/Chagos', - 'Indian/Christmas', - 'Indian/Cocos', - 'Indian/Comoro', - 'Indian/Kerguelen', - 'Indian/Mahe', - 'Indian/Maldives', - 'Indian/Mauritius', - 'Indian/Mayotte', - 'Indian/Reunion', - 'Iran', - 'Israel', - 'Jamaica', - 'Japan', - 'Kwajalein', - 'Libya', - 'MET', - 'MST', - 'MST7MDT', - 'Mexico/BajaNorte', - 'Mexico/BajaSur', - 'Mexico/General', - 'NZ', - 'NZ-CHAT', - 'Navajo', - 'PRC', - 'PST8PDT', - 'Pacific/Apia', - 'Pacific/Auckland', - 'Pacific/Bougainville', - 'Pacific/Chatham', - 'Pacific/Chuuk', - 'Pacific/Easter', - 'Pacific/Efate', - 'Pacific/Enderbury', - 'Pacific/Fakaofo', - 'Pacific/Fiji', - 'Pacific/Funafuti', - 'Pacific/Galapagos', - 'Pacific/Gambier', - 'Pacific/Guadalcanal', - 'Pacific/Guam', - 'Pacific/Honolulu', - 'Pacific/Johnston', - 'Pacific/Kanton', - 'Pacific/Kiritimati', - 'Pacific/Kosrae', - 'Pacific/Kwajalein', - 'Pacific/Majuro', - 'Pacific/Marquesas', - 'Pacific/Midway', - 'Pacific/Nauru', - 'Pacific/Niue', - 'Pacific/Norfolk', - 'Pacific/Noumea', - 'Pacific/Pago_Pago', - 'Pacific/Palau', - 'Pacific/Pitcairn', - 'Pacific/Pohnpei', - 'Pacific/Ponape', - 'Pacific/Port_Moresby', - 'Pacific/Rarotonga', - 'Pacific/Saipan', - 'Pacific/Samoa', - 'Pacific/Tahiti', - 'Pacific/Tarawa', - 'Pacific/Tongatapu', - 'Pacific/Truk', - 'Pacific/Wake', - 'Pacific/Wallis', - 'Pacific/Yap', - 'Poland', - 'Portugal', - 'ROC', - 'ROK', - 'Singapore', - 'Turkey', - 'UCT', - 'US/Alaska', - 'US/Aleutian', - 'US/Arizona', - 'US/Central', - 'US/East-Indiana', - 'US/Eastern', - 'US/Hawaii', - 'US/Indiana-Starke', - 'US/Michigan', - 'US/Mountain', - 'US/Pacific', - 'US/Samoa', - 'UTC', - 'Universal', - 'W-SU', - 'WET', - 'Zulu'] -all_timezones = LazyList( - tz for tz in all_timezones if resource_exists(tz)) - -all_timezones_set = LazySet(all_timezones) -common_timezones = \ -['Africa/Abidjan', - 'Africa/Accra', - 'Africa/Addis_Ababa', - 'Africa/Algiers', - 'Africa/Asmara', - 'Africa/Bamako', - 'Africa/Bangui', - 'Africa/Banjul', - 'Africa/Bissau', - 'Africa/Blantyre', - 'Africa/Brazzaville', - 'Africa/Bujumbura', - 'Africa/Cairo', - 'Africa/Casablanca', - 'Africa/Ceuta', - 'Africa/Conakry', - 'Africa/Dakar', - 'Africa/Dar_es_Salaam', - 'Africa/Djibouti', - 'Africa/Douala', - 'Africa/El_Aaiun', - 'Africa/Freetown', - 'Africa/Gaborone', - 'Africa/Harare', - 'Africa/Johannesburg', - 'Africa/Juba', - 'Africa/Kampala', - 'Africa/Khartoum', - 'Africa/Kigali', - 'Africa/Kinshasa', - 'Africa/Lagos', - 'Africa/Libreville', - 'Africa/Lome', - 'Africa/Luanda', - 'Africa/Lubumbashi', - 'Africa/Lusaka', - 'Africa/Malabo', - 'Africa/Maputo', - 'Africa/Maseru', - 'Africa/Mbabane', - 'Africa/Mogadishu', - 'Africa/Monrovia', - 'Africa/Nairobi', - 'Africa/Ndjamena', - 'Africa/Niamey', - 'Africa/Nouakchott', - 'Africa/Ouagadougou', - 'Africa/Porto-Novo', - 'Africa/Sao_Tome', - 'Africa/Tripoli', - 'Africa/Tunis', - 'Africa/Windhoek', - 'America/Adak', - 'America/Anchorage', - 'America/Anguilla', - 'America/Antigua', - 'America/Araguaina', - 'America/Argentina/Buenos_Aires', - 'America/Argentina/Catamarca', - 'America/Argentina/Cordoba', - 'America/Argentina/Jujuy', - 'America/Argentina/La_Rioja', - 'America/Argentina/Mendoza', - 'America/Argentina/Rio_Gallegos', - 'America/Argentina/Salta', - 'America/Argentina/San_Juan', - 'America/Argentina/San_Luis', - 'America/Argentina/Tucuman', - 'America/Argentina/Ushuaia', - 'America/Aruba', - 'America/Asuncion', - 'America/Atikokan', - 'America/Bahia', - 'America/Bahia_Banderas', - 'America/Barbados', - 'America/Belem', - 'America/Belize', - 'America/Blanc-Sablon', - 'America/Boa_Vista', - 'America/Bogota', - 'America/Boise', - 'America/Cambridge_Bay', - 'America/Campo_Grande', - 'America/Cancun', - 'America/Caracas', - 'America/Cayenne', - 'America/Cayman', - 'America/Chicago', - 'America/Chihuahua', - 'America/Costa_Rica', - 'America/Creston', - 'America/Cuiaba', - 'America/Curacao', - 'America/Danmarkshavn', - 'America/Dawson', - 'America/Dawson_Creek', - 'America/Denver', - 'America/Detroit', - 'America/Dominica', - 'America/Edmonton', - 'America/Eirunepe', - 'America/El_Salvador', - 'America/Fort_Nelson', - 'America/Fortaleza', - 'America/Glace_Bay', - 'America/Goose_Bay', - 'America/Grand_Turk', - 'America/Grenada', - 'America/Guadeloupe', - 'America/Guatemala', - 'America/Guayaquil', - 'America/Guyana', - 'America/Halifax', - 'America/Havana', - 'America/Hermosillo', - 'America/Indiana/Indianapolis', - 'America/Indiana/Knox', - 'America/Indiana/Marengo', - 'America/Indiana/Petersburg', - 'America/Indiana/Tell_City', - 'America/Indiana/Vevay', - 'America/Indiana/Vincennes', - 'America/Indiana/Winamac', - 'America/Inuvik', - 'America/Iqaluit', - 'America/Jamaica', - 'America/Juneau', - 'America/Kentucky/Louisville', - 'America/Kentucky/Monticello', - 'America/Kralendijk', - 'America/La_Paz', - 'America/Lima', - 'America/Los_Angeles', - 'America/Lower_Princes', - 'America/Maceio', - 'America/Managua', - 'America/Manaus', - 'America/Marigot', - 'America/Martinique', - 'America/Matamoros', - 'America/Mazatlan', - 'America/Menominee', - 'America/Merida', - 'America/Metlakatla', - 'America/Mexico_City', - 'America/Miquelon', - 'America/Moncton', - 'America/Monterrey', - 'America/Montevideo', - 'America/Montserrat', - 'America/Nassau', - 'America/New_York', - 'America/Nipigon', - 'America/Nome', - 'America/Noronha', - 'America/North_Dakota/Beulah', - 'America/North_Dakota/Center', - 'America/North_Dakota/New_Salem', - 'America/Nuuk', - 'America/Ojinaga', - 'America/Panama', - 'America/Pangnirtung', - 'America/Paramaribo', - 'America/Phoenix', - 'America/Port-au-Prince', - 'America/Port_of_Spain', - 'America/Porto_Velho', - 'America/Puerto_Rico', - 'America/Punta_Arenas', - 'America/Rainy_River', - 'America/Rankin_Inlet', - 'America/Recife', - 'America/Regina', - 'America/Resolute', - 'America/Rio_Branco', - 'America/Santarem', - 'America/Santiago', - 'America/Santo_Domingo', - 'America/Sao_Paulo', - 'America/Scoresbysund', - 'America/Sitka', - 'America/St_Barthelemy', - 'America/St_Johns', - 'America/St_Kitts', - 'America/St_Lucia', - 'America/St_Thomas', - 'America/St_Vincent', - 'America/Swift_Current', - 'America/Tegucigalpa', - 'America/Thule', - 'America/Thunder_Bay', - 'America/Tijuana', - 'America/Toronto', - 'America/Tortola', - 'America/Vancouver', - 'America/Whitehorse', - 'America/Winnipeg', - 'America/Yakutat', - 'America/Yellowknife', - 'Antarctica/Casey', - 'Antarctica/Davis', - 'Antarctica/DumontDUrville', - 'Antarctica/Macquarie', - 'Antarctica/Mawson', - 'Antarctica/McMurdo', - 'Antarctica/Palmer', - 'Antarctica/Rothera', - 'Antarctica/Syowa', - 'Antarctica/Troll', - 'Antarctica/Vostok', - 'Arctic/Longyearbyen', - 'Asia/Aden', - 'Asia/Almaty', - 'Asia/Amman', - 'Asia/Anadyr', - 'Asia/Aqtau', - 'Asia/Aqtobe', - 'Asia/Ashgabat', - 'Asia/Atyrau', - 'Asia/Baghdad', - 'Asia/Bahrain', - 'Asia/Baku', - 'Asia/Bangkok', - 'Asia/Barnaul', - 'Asia/Beirut', - 'Asia/Bishkek', - 'Asia/Brunei', - 'Asia/Chita', - 'Asia/Choibalsan', - 'Asia/Colombo', - 'Asia/Damascus', - 'Asia/Dhaka', - 'Asia/Dili', - 'Asia/Dubai', - 'Asia/Dushanbe', - 'Asia/Famagusta', - 'Asia/Gaza', - 'Asia/Hebron', - 'Asia/Ho_Chi_Minh', - 'Asia/Hong_Kong', - 'Asia/Hovd', - 'Asia/Irkutsk', - 'Asia/Jakarta', - 'Asia/Jayapura', - 'Asia/Jerusalem', - 'Asia/Kabul', - 'Asia/Kamchatka', - 'Asia/Karachi', - 'Asia/Kathmandu', - 'Asia/Khandyga', - 'Asia/Kolkata', - 'Asia/Krasnoyarsk', - 'Asia/Kuala_Lumpur', - 'Asia/Kuching', - 'Asia/Kuwait', - 'Asia/Macau', - 'Asia/Magadan', - 'Asia/Makassar', - 'Asia/Manila', - 'Asia/Muscat', - 'Asia/Nicosia', - 'Asia/Novokuznetsk', - 'Asia/Novosibirsk', - 'Asia/Omsk', - 'Asia/Oral', - 'Asia/Phnom_Penh', - 'Asia/Pontianak', - 'Asia/Pyongyang', - 'Asia/Qatar', - 'Asia/Qostanay', - 'Asia/Qyzylorda', - 'Asia/Riyadh', - 'Asia/Sakhalin', - 'Asia/Samarkand', - 'Asia/Seoul', - 'Asia/Shanghai', - 'Asia/Singapore', - 'Asia/Srednekolymsk', - 'Asia/Taipei', - 'Asia/Tashkent', - 'Asia/Tbilisi', - 'Asia/Tehran', - 'Asia/Thimphu', - 'Asia/Tokyo', - 'Asia/Tomsk', - 'Asia/Ulaanbaatar', - 'Asia/Urumqi', - 'Asia/Ust-Nera', - 'Asia/Vientiane', - 'Asia/Vladivostok', - 'Asia/Yakutsk', - 'Asia/Yangon', - 'Asia/Yekaterinburg', - 'Asia/Yerevan', - 'Atlantic/Azores', - 'Atlantic/Bermuda', - 'Atlantic/Canary', - 'Atlantic/Cape_Verde', - 'Atlantic/Faroe', - 'Atlantic/Madeira', - 'Atlantic/Reykjavik', - 'Atlantic/South_Georgia', - 'Atlantic/St_Helena', - 'Atlantic/Stanley', - 'Australia/Adelaide', - 'Australia/Brisbane', - 'Australia/Broken_Hill', - 'Australia/Darwin', - 'Australia/Eucla', - 'Australia/Hobart', - 'Australia/Lindeman', - 'Australia/Lord_Howe', - 'Australia/Melbourne', - 'Australia/Perth', - 'Australia/Sydney', - 'Canada/Atlantic', - 'Canada/Central', - 'Canada/Eastern', - 'Canada/Mountain', - 'Canada/Newfoundland', - 'Canada/Pacific', - 'Europe/Amsterdam', - 'Europe/Andorra', - 'Europe/Astrakhan', - 'Europe/Athens', - 'Europe/Belgrade', - 'Europe/Berlin', - 'Europe/Bratislava', - 'Europe/Brussels', - 'Europe/Bucharest', - 'Europe/Budapest', - 'Europe/Busingen', - 'Europe/Chisinau', - 'Europe/Copenhagen', - 'Europe/Dublin', - 'Europe/Gibraltar', - 'Europe/Guernsey', - 'Europe/Helsinki', - 'Europe/Isle_of_Man', - 'Europe/Istanbul', - 'Europe/Jersey', - 'Europe/Kaliningrad', - 'Europe/Kiev', - 'Europe/Kirov', - 'Europe/Lisbon', - 'Europe/Ljubljana', - 'Europe/London', - 'Europe/Luxembourg', - 'Europe/Madrid', - 'Europe/Malta', - 'Europe/Mariehamn', - 'Europe/Minsk', - 'Europe/Monaco', - 'Europe/Moscow', - 'Europe/Oslo', - 'Europe/Paris', - 'Europe/Podgorica', - 'Europe/Prague', - 'Europe/Riga', - 'Europe/Rome', - 'Europe/Samara', - 'Europe/San_Marino', - 'Europe/Sarajevo', - 'Europe/Saratov', - 'Europe/Simferopol', - 'Europe/Skopje', - 'Europe/Sofia', - 'Europe/Stockholm', - 'Europe/Tallinn', - 'Europe/Tirane', - 'Europe/Ulyanovsk', - 'Europe/Uzhgorod', - 'Europe/Vaduz', - 'Europe/Vatican', - 'Europe/Vienna', - 'Europe/Vilnius', - 'Europe/Volgograd', - 'Europe/Warsaw', - 'Europe/Zagreb', - 'Europe/Zaporozhye', - 'Europe/Zurich', - 'GMT', - 'Indian/Antananarivo', - 'Indian/Chagos', - 'Indian/Christmas', - 'Indian/Cocos', - 'Indian/Comoro', - 'Indian/Kerguelen', - 'Indian/Mahe', - 'Indian/Maldives', - 'Indian/Mauritius', - 'Indian/Mayotte', - 'Indian/Reunion', - 'Pacific/Apia', - 'Pacific/Auckland', - 'Pacific/Bougainville', - 'Pacific/Chatham', - 'Pacific/Chuuk', - 'Pacific/Easter', - 'Pacific/Efate', - 'Pacific/Fakaofo', - 'Pacific/Fiji', - 'Pacific/Funafuti', - 'Pacific/Galapagos', - 'Pacific/Gambier', - 'Pacific/Guadalcanal', - 'Pacific/Guam', - 'Pacific/Honolulu', - 'Pacific/Kanton', - 'Pacific/Kiritimati', - 'Pacific/Kosrae', - 'Pacific/Kwajalein', - 'Pacific/Majuro', - 'Pacific/Marquesas', - 'Pacific/Midway', - 'Pacific/Nauru', - 'Pacific/Niue', - 'Pacific/Norfolk', - 'Pacific/Noumea', - 'Pacific/Pago_Pago', - 'Pacific/Palau', - 'Pacific/Pitcairn', - 'Pacific/Pohnpei', - 'Pacific/Port_Moresby', - 'Pacific/Rarotonga', - 'Pacific/Saipan', - 'Pacific/Tahiti', - 'Pacific/Tarawa', - 'Pacific/Tongatapu', - 'Pacific/Wake', - 'Pacific/Wallis', - 'US/Alaska', - 'US/Arizona', - 'US/Central', - 'US/Eastern', - 'US/Hawaii', - 'US/Mountain', - 'US/Pacific', - 'UTC'] -common_timezones = LazyList( - tz for tz in common_timezones if tz in all_timezones) - -common_timezones_set = LazySet(common_timezones) diff --git a/telegramer/include/pytz/exceptions.py b/telegramer/include/pytz/exceptions.py deleted file mode 100644 index 4b20bde..0000000 --- a/telegramer/include/pytz/exceptions.py +++ /dev/null @@ -1,59 +0,0 @@ -''' -Custom exceptions raised by pytz. -''' - -__all__ = [ - 'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError', - 'NonExistentTimeError', -] - - -class Error(Exception): - '''Base class for all exceptions raised by the pytz library''' - - -class UnknownTimeZoneError(KeyError, Error): - '''Exception raised when pytz is passed an unknown timezone. - - >>> isinstance(UnknownTimeZoneError(), LookupError) - True - - This class is actually a subclass of KeyError to provide backwards - compatibility with code relying on the undocumented behavior of earlier - pytz releases. - - >>> isinstance(UnknownTimeZoneError(), KeyError) - True - - And also a subclass of pytz.exceptions.Error, as are other pytz - exceptions. - - >>> isinstance(UnknownTimeZoneError(), Error) - True - - ''' - pass - - -class InvalidTimeError(Error): - '''Base class for invalid time exceptions.''' - - -class AmbiguousTimeError(InvalidTimeError): - '''Exception raised when attempting to create an ambiguous wallclock time. - - At the end of a DST transition period, a particular wallclock time will - occur twice (once before the clocks are set back, once after). Both - possibilities may be correct, unless further information is supplied. - - See DstTzInfo.normalize() for more info - ''' - - -class NonExistentTimeError(InvalidTimeError): - '''Exception raised when attempting to create a wallclock time that - cannot exist. - - At the start of a DST transition period, the wallclock time jumps forward. - The instants jumped over never occur. - ''' diff --git a/telegramer/include/pytz/lazy.py b/telegramer/include/pytz/lazy.py deleted file mode 100644 index 39344fc..0000000 --- a/telegramer/include/pytz/lazy.py +++ /dev/null @@ -1,172 +0,0 @@ -from threading import RLock -try: - from collections.abc import Mapping as DictMixin -except ImportError: # Python < 3.3 - try: - from UserDict import DictMixin # Python 2 - except ImportError: # Python 3.0-3.3 - from collections import Mapping as DictMixin - - -# With lazy loading, we might end up with multiple threads triggering -# it at the same time. We need a lock. -_fill_lock = RLock() - - -class LazyDict(DictMixin): - """Dictionary populated on first use.""" - data = None - - def __getitem__(self, key): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return self.data[key.upper()] - - def __contains__(self, key): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return key in self.data - - def __iter__(self): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return iter(self.data) - - def __len__(self): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return len(self.data) - - def keys(self): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return self.data.keys() - - -class LazyList(list): - """List populated on first use.""" - - _props = [ - '__str__', '__repr__', '__unicode__', - '__hash__', '__sizeof__', '__cmp__', - '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', - 'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove', - 'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__', - '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__', - '__getitem__', '__setitem__', '__delitem__', '__iter__', - '__reversed__', '__getslice__', '__setslice__', '__delslice__'] - - def __new__(cls, fill_iter=None): - - if fill_iter is None: - return list() - - # We need a new class as we will be dynamically messing with its - # methods. - class LazyList(list): - pass - - fill_iter = [fill_iter] - - def lazy(name): - def _lazy(self, *args, **kw): - _fill_lock.acquire() - try: - if len(fill_iter) > 0: - list.extend(self, fill_iter.pop()) - for method_name in cls._props: - delattr(LazyList, method_name) - finally: - _fill_lock.release() - return getattr(list, name)(self, *args, **kw) - return _lazy - - for name in cls._props: - setattr(LazyList, name, lazy(name)) - - new_list = LazyList() - return new_list - -# Not all versions of Python declare the same magic methods. -# Filter out properties that don't exist in this version of Python -# from the list. -LazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)] - - -class LazySet(set): - """Set populated on first use.""" - - _props = ( - '__str__', '__repr__', '__unicode__', - '__hash__', '__sizeof__', '__cmp__', - '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', - '__contains__', '__len__', '__nonzero__', - '__getitem__', '__setitem__', '__delitem__', '__iter__', - '__sub__', '__and__', '__xor__', '__or__', - '__rsub__', '__rand__', '__rxor__', '__ror__', - '__isub__', '__iand__', '__ixor__', '__ior__', - 'add', 'clear', 'copy', 'difference', 'difference_update', - 'discard', 'intersection', 'intersection_update', 'isdisjoint', - 'issubset', 'issuperset', 'pop', 'remove', - 'symmetric_difference', 'symmetric_difference_update', - 'union', 'update') - - def __new__(cls, fill_iter=None): - - if fill_iter is None: - return set() - - class LazySet(set): - pass - - fill_iter = [fill_iter] - - def lazy(name): - def _lazy(self, *args, **kw): - _fill_lock.acquire() - try: - if len(fill_iter) > 0: - for i in fill_iter.pop(): - set.add(self, i) - for method_name in cls._props: - delattr(LazySet, method_name) - finally: - _fill_lock.release() - return getattr(set, name)(self, *args, **kw) - return _lazy - - for name in cls._props: - setattr(LazySet, name, lazy(name)) - - new_set = LazySet() - return new_set - -# Not all versions of Python declare the same magic methods. -# Filter out properties that don't exist in this version of Python -# from the list. -LazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)] diff --git a/telegramer/include/pytz/reference.py b/telegramer/include/pytz/reference.py deleted file mode 100644 index f765ca0..0000000 --- a/telegramer/include/pytz/reference.py +++ /dev/null @@ -1,140 +0,0 @@ -''' -Reference tzinfo implementations from the Python docs. -Used for testing against as they are only correct for the years -1987 to 2006. Do not use these for real code. -''' - -from datetime import tzinfo, timedelta, datetime -from pytz import HOUR, ZERO, UTC - -__all__ = [ - 'FixedOffset', - 'LocalTimezone', - 'USTimeZone', - 'Eastern', - 'Central', - 'Mountain', - 'Pacific', - 'UTC' -] - - -# A class building tzinfo objects for fixed-offset time zones. -# Note that FixedOffset(0, "UTC") is a different way to build a -# UTC tzinfo object. -class FixedOffset(tzinfo): - """Fixed offset in minutes east from UTC.""" - - def __init__(self, offset, name): - self.__offset = timedelta(minutes=offset) - self.__name = name - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - -import time as _time - -STDOFFSET = timedelta(seconds=-_time.timezone) -if _time.daylight: - DSTOFFSET = timedelta(seconds=-_time.altzone) -else: - DSTOFFSET = STDOFFSET - -DSTDIFF = DSTOFFSET - STDOFFSET - - -# A class capturing the platform's idea of local time. -class LocalTimezone(tzinfo): - - def utcoffset(self, dt): - if self._isdst(dt): - return DSTOFFSET - else: - return STDOFFSET - - def dst(self, dt): - if self._isdst(dt): - return DSTDIFF - else: - return ZERO - - def tzname(self, dt): - return _time.tzname[self._isdst(dt)] - - def _isdst(self, dt): - tt = (dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second, - dt.weekday(), 0, -1) - stamp = _time.mktime(tt) - tt = _time.localtime(stamp) - return tt.tm_isdst > 0 - -Local = LocalTimezone() - - -def first_sunday_on_or_after(dt): - days_to_go = 6 - dt.weekday() - if days_to_go: - dt += timedelta(days_to_go) - return dt - - -# In the US, DST starts at 2am (standard time) on the first Sunday in April. -DSTSTART = datetime(1, 4, 1, 2) -# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct. -# which is the first Sunday on or after Oct 25. -DSTEND = datetime(1, 10, 25, 1) - - -# A complete implementation of current DST rules for major US time zones. -class USTimeZone(tzinfo): - - def __init__(self, hours, reprname, stdname, dstname): - self.stdoffset = timedelta(hours=hours) - self.reprname = reprname - self.stdname = stdname - self.dstname = dstname - - def __repr__(self): - return self.reprname - - def tzname(self, dt): - if self.dst(dt): - return self.dstname - else: - return self.stdname - - def utcoffset(self, dt): - return self.stdoffset + self.dst(dt) - - def dst(self, dt): - if dt is None or dt.tzinfo is None: - # An exception may be sensible here, in one or both cases. - # It depends on how you want to treat them. The default - # fromutc() implementation (called by the default astimezone() - # implementation) passes a datetime with dt.tzinfo is self. - return ZERO - assert dt.tzinfo is self - - # Find first Sunday in April & the last in October. - start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) - end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) - - # Can't compare naive to aware objects, so strip the timezone from - # dt first. - if start <= dt.replace(tzinfo=None) < end: - return HOUR - else: - return ZERO - -Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") -Central = USTimeZone(-6, "Central", "CST", "CDT") -Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") -Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") diff --git a/telegramer/include/pytz/tzfile.py b/telegramer/include/pytz/tzfile.py deleted file mode 100644 index 99e7448..0000000 --- a/telegramer/include/pytz/tzfile.py +++ /dev/null @@ -1,133 +0,0 @@ -''' -$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ -''' - -from datetime import datetime -from struct import unpack, calcsize - -from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo -from pytz.tzinfo import memorized_datetime, memorized_timedelta - - -def _byte_string(s): - """Cast a string or byte string to an ASCII byte string.""" - return s.encode('ASCII') - -_NULL = _byte_string('\0') - - -def _std_string(s): - """Cast a string or byte string to an ASCII string.""" - return str(s.decode('ASCII')) - - -def build_tzinfo(zone, fp): - head_fmt = '>4s c 15x 6l' - head_size = calcsize(head_fmt) - (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, - typecnt, charcnt) = unpack(head_fmt, fp.read(head_size)) - - # Make sure it is a tzfile(5) file - assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic) - - # Read out the transition times, localtime indices and ttinfo structures. - data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict( - timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt) - data_size = calcsize(data_fmt) - data = unpack(data_fmt, fp.read(data_size)) - - # make sure we unpacked the right number of values - assert len(data) == 2 * timecnt + 3 * typecnt + 1 - transitions = [memorized_datetime(trans) - for trans in data[:timecnt]] - lindexes = list(data[timecnt:2 * timecnt]) - ttinfo_raw = data[2 * timecnt:-1] - tznames_raw = data[-1] - del data - - # Process ttinfo into separate structs - ttinfo = [] - tznames = {} - i = 0 - while i < len(ttinfo_raw): - # have we looked up this timezone name yet? - tzname_offset = ttinfo_raw[i + 2] - if tzname_offset not in tznames: - nul = tznames_raw.find(_NULL, tzname_offset) - if nul < 0: - nul = len(tznames_raw) - tznames[tzname_offset] = _std_string( - tznames_raw[tzname_offset:nul]) - ttinfo.append((ttinfo_raw[i], - bool(ttinfo_raw[i + 1]), - tznames[tzname_offset])) - i += 3 - - # Now build the timezone object - if len(ttinfo) == 1 or len(transitions) == 0: - ttinfo[0][0], ttinfo[0][2] - cls = type(zone, (StaticTzInfo,), dict( - zone=zone, - _utcoffset=memorized_timedelta(ttinfo[0][0]), - _tzname=ttinfo[0][2])) - else: - # Early dates use the first standard time ttinfo - i = 0 - while ttinfo[i][1]: - i += 1 - if ttinfo[i] == ttinfo[lindexes[0]]: - transitions[0] = datetime.min - else: - transitions.insert(0, datetime.min) - lindexes.insert(0, i) - - # calculate transition info - transition_info = [] - for i in range(len(transitions)): - inf = ttinfo[lindexes[i]] - utcoffset = inf[0] - if not inf[1]: - dst = 0 - else: - for j in range(i - 1, -1, -1): - prev_inf = ttinfo[lindexes[j]] - if not prev_inf[1]: - break - dst = inf[0] - prev_inf[0] # dst offset - - # Bad dst? Look further. DST > 24 hours happens when - # a timzone has moved across the international dateline. - if dst <= 0 or dst > 3600 * 3: - for j in range(i + 1, len(transitions)): - stdinf = ttinfo[lindexes[j]] - if not stdinf[1]: - dst = inf[0] - stdinf[0] - if dst > 0: - break # Found a useful std time. - - tzname = inf[2] - - # Round utcoffset and dst to the nearest minute or the - # datetime library will complain. Conversions to these timezones - # might be up to plus or minus 30 seconds out, but it is - # the best we can do. - utcoffset = int((utcoffset + 30) // 60) * 60 - dst = int((dst + 30) // 60) * 60 - transition_info.append(memorized_ttinfo(utcoffset, dst, tzname)) - - cls = type(zone, (DstTzInfo,), dict( - zone=zone, - _utc_transition_times=transitions, - _transition_info=transition_info)) - - return cls() - -if __name__ == '__main__': - import os.path - from pprint import pprint - base = os.path.join(os.path.dirname(__file__), 'zoneinfo') - tz = build_tzinfo('Australia/Melbourne', - open(os.path.join(base, 'Australia', 'Melbourne'), 'rb')) - tz = build_tzinfo('US/Eastern', - open(os.path.join(base, 'US', 'Eastern'), 'rb')) - pprint(tz._utc_transition_times) diff --git a/telegramer/include/pytz/tzinfo.py b/telegramer/include/pytz/tzinfo.py deleted file mode 100644 index 725978d..0000000 --- a/telegramer/include/pytz/tzinfo.py +++ /dev/null @@ -1,577 +0,0 @@ -'''Base classes and helpers for building zone specific tzinfo classes''' - -from datetime import datetime, timedelta, tzinfo -from bisect import bisect_right -try: - set -except NameError: - from sets import Set as set - -import pytz -from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError - -__all__ = [] - -_timedelta_cache = {} - - -def memorized_timedelta(seconds): - '''Create only one instance of each distinct timedelta''' - try: - return _timedelta_cache[seconds] - except KeyError: - delta = timedelta(seconds=seconds) - _timedelta_cache[seconds] = delta - return delta - -_epoch = datetime.utcfromtimestamp(0) -_datetime_cache = {0: _epoch} - - -def memorized_datetime(seconds): - '''Create only one instance of each distinct datetime''' - try: - return _datetime_cache[seconds] - except KeyError: - # NB. We can't just do datetime.utcfromtimestamp(seconds) as this - # fails with negative values under Windows (Bug #90096) - dt = _epoch + timedelta(seconds=seconds) - _datetime_cache[seconds] = dt - return dt - -_ttinfo_cache = {} - - -def memorized_ttinfo(*args): - '''Create only one instance of each distinct tuple''' - try: - return _ttinfo_cache[args] - except KeyError: - ttinfo = ( - memorized_timedelta(args[0]), - memorized_timedelta(args[1]), - args[2] - ) - _ttinfo_cache[args] = ttinfo - return ttinfo - -_notime = memorized_timedelta(0) - - -def _to_seconds(td): - '''Convert a timedelta to seconds''' - return td.seconds + td.days * 24 * 60 * 60 - - -class BaseTzInfo(tzinfo): - # Overridden in subclass - _utcoffset = None - _tzname = None - zone = None - - def __str__(self): - return self.zone - - -class StaticTzInfo(BaseTzInfo): - '''A timezone that has a constant offset from UTC - - These timezones are rare, as most locations have changed their - offset at some point in their history - ''' - def fromutc(self, dt): - '''See datetime.tzinfo.fromutc''' - if dt.tzinfo is not None and dt.tzinfo is not self: - raise ValueError('fromutc: dt.tzinfo is not self') - return (dt + self._utcoffset).replace(tzinfo=self) - - def utcoffset(self, dt, is_dst=None): - '''See datetime.tzinfo.utcoffset - - is_dst is ignored for StaticTzInfo, and exists only to - retain compatibility with DstTzInfo. - ''' - return self._utcoffset - - def dst(self, dt, is_dst=None): - '''See datetime.tzinfo.dst - - is_dst is ignored for StaticTzInfo, and exists only to - retain compatibility with DstTzInfo. - ''' - return _notime - - def tzname(self, dt, is_dst=None): - '''See datetime.tzinfo.tzname - - is_dst is ignored for StaticTzInfo, and exists only to - retain compatibility with DstTzInfo. - ''' - return self._tzname - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - return dt.replace(tzinfo=self) - - def normalize(self, dt, is_dst=False): - '''Correct the timezone information on the given datetime. - - This is normally a no-op, as StaticTzInfo timezones never have - ambiguous cases to correct: - - >>> from pytz import timezone - >>> gmt = timezone('GMT') - >>> isinstance(gmt, StaticTzInfo) - True - >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt) - >>> gmt.normalize(dt) is dt - True - - The supported method of converting between timezones is to use - datetime.astimezone(). Currently normalize() also works: - - >>> la = timezone('America/Los_Angeles') - >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3)) - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> gmt.normalize(dt).strftime(fmt) - '2011-05-07 08:02:03 GMT (+0000)' - ''' - if dt.tzinfo is self: - return dt - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - return dt.astimezone(self) - - def __repr__(self): - return '' % (self.zone,) - - def __reduce__(self): - # Special pickle to zone remains a singleton and to cope with - # database changes. - return pytz._p, (self.zone,) - - -class DstTzInfo(BaseTzInfo): - '''A timezone that has a variable offset from UTC - - The offset might change if daylight saving time comes into effect, - or at a point in history when the region decides to change their - timezone definition. - ''' - # Overridden in subclass - - # Sorted list of DST transition times, UTC - _utc_transition_times = None - - # [(utcoffset, dstoffset, tzname)] corresponding to - # _utc_transition_times entries - _transition_info = None - - zone = None - - # Set in __init__ - - _tzinfos = None - _dst = None # DST offset - - def __init__(self, _inf=None, _tzinfos=None): - if _inf: - self._tzinfos = _tzinfos - self._utcoffset, self._dst, self._tzname = _inf - else: - _tzinfos = {} - self._tzinfos = _tzinfos - self._utcoffset, self._dst, self._tzname = ( - self._transition_info[0]) - _tzinfos[self._transition_info[0]] = self - for inf in self._transition_info[1:]: - if inf not in _tzinfos: - _tzinfos[inf] = self.__class__(inf, _tzinfos) - - def fromutc(self, dt): - '''See datetime.tzinfo.fromutc''' - if (dt.tzinfo is not None and - getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos): - raise ValueError('fromutc: dt.tzinfo is not self') - dt = dt.replace(tzinfo=None) - idx = max(0, bisect_right(self._utc_transition_times, dt) - 1) - inf = self._transition_info[idx] - return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf]) - - def normalize(self, dt): - '''Correct the timezone information on the given datetime - - If date arithmetic crosses DST boundaries, the tzinfo - is not magically adjusted. This method normalizes the - tzinfo to the correct one. - - To test, first we need to do some setup - - >>> from pytz import timezone - >>> utc = timezone('UTC') - >>> eastern = timezone('US/Eastern') - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - - We next create a datetime right on an end-of-DST transition point, - the instant when the wallclocks are wound back one hour. - - >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) - >>> loc_dt = utc_dt.astimezone(eastern) - >>> loc_dt.strftime(fmt) - '2002-10-27 01:00:00 EST (-0500)' - - Now, if we subtract a few minutes from it, note that the timezone - information has not changed. - - >>> before = loc_dt - timedelta(minutes=10) - >>> before.strftime(fmt) - '2002-10-27 00:50:00 EST (-0500)' - - But we can fix that by calling the normalize method - - >>> before = eastern.normalize(before) - >>> before.strftime(fmt) - '2002-10-27 01:50:00 EDT (-0400)' - - The supported method of converting between timezones is to use - datetime.astimezone(). Currently, normalize() also works: - - >>> th = timezone('Asia/Bangkok') - >>> am = timezone('Europe/Amsterdam') - >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3)) - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> am.normalize(dt).strftime(fmt) - '2011-05-06 20:02:03 CEST (+0200)' - ''' - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - - # Convert dt in localtime to UTC - offset = dt.tzinfo._utcoffset - dt = dt.replace(tzinfo=None) - dt = dt - offset - # convert it back, and return it - return self.fromutc(dt) - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time. - - This method should be used to construct localtimes, rather - than passing a tzinfo argument to a datetime constructor. - - is_dst is used to determine the correct timezone in the ambigous - period at the end of daylight saving time. - - >>> from pytz import timezone - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> amdam = timezone('Europe/Amsterdam') - >>> dt = datetime(2004, 10, 31, 2, 0, 0) - >>> loc_dt1 = amdam.localize(dt, is_dst=True) - >>> loc_dt2 = amdam.localize(dt, is_dst=False) - >>> loc_dt1.strftime(fmt) - '2004-10-31 02:00:00 CEST (+0200)' - >>> loc_dt2.strftime(fmt) - '2004-10-31 02:00:00 CET (+0100)' - >>> str(loc_dt2 - loc_dt1) - '1:00:00' - - Use is_dst=None to raise an AmbiguousTimeError for ambiguous - times at the end of daylight saving time - - >>> try: - ... loc_dt1 = amdam.localize(dt, is_dst=None) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - - is_dst defaults to False - - >>> amdam.localize(dt) == amdam.localize(dt, False) - True - - is_dst is also used to determine the correct timezone in the - wallclock times jumped over at the start of daylight saving time. - - >>> pacific = timezone('US/Pacific') - >>> dt = datetime(2008, 3, 9, 2, 0, 0) - >>> ploc_dt1 = pacific.localize(dt, is_dst=True) - >>> ploc_dt2 = pacific.localize(dt, is_dst=False) - >>> ploc_dt1.strftime(fmt) - '2008-03-09 02:00:00 PDT (-0700)' - >>> ploc_dt2.strftime(fmt) - '2008-03-09 02:00:00 PST (-0800)' - >>> str(ploc_dt2 - ploc_dt1) - '1:00:00' - - Use is_dst=None to raise a NonExistentTimeError for these skipped - times. - - >>> try: - ... loc_dt1 = pacific.localize(dt, is_dst=None) - ... except NonExistentTimeError: - ... print('Non-existent') - Non-existent - ''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - - # Find the two best possibilities. - possible_loc_dt = set() - for delta in [timedelta(days=-1), timedelta(days=1)]: - loc_dt = dt + delta - idx = max(0, bisect_right( - self._utc_transition_times, loc_dt) - 1) - inf = self._transition_info[idx] - tzinfo = self._tzinfos[inf] - loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo)) - if loc_dt.replace(tzinfo=None) == dt: - possible_loc_dt.add(loc_dt) - - if len(possible_loc_dt) == 1: - return possible_loc_dt.pop() - - # If there are no possibly correct timezones, we are attempting - # to convert a time that never happened - the time period jumped - # during the start-of-DST transition period. - if len(possible_loc_dt) == 0: - # If we refuse to guess, raise an exception. - if is_dst is None: - raise NonExistentTimeError(dt) - - # If we are forcing the pre-DST side of the DST transition, we - # obtain the correct timezone by winding the clock forward a few - # hours. - elif is_dst: - return self.localize( - dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6) - - # If we are forcing the post-DST side of the DST transition, we - # obtain the correct timezone by winding the clock back. - else: - return self.localize( - dt - timedelta(hours=6), - is_dst=False) + timedelta(hours=6) - - # If we get this far, we have multiple possible timezones - this - # is an ambiguous case occuring during the end-of-DST transition. - - # If told to be strict, raise an exception since we have an - # ambiguous case - if is_dst is None: - raise AmbiguousTimeError(dt) - - # Filter out the possiblilities that don't match the requested - # is_dst - filtered_possible_loc_dt = [ - p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst - ] - - # Hopefully we only have one possibility left. Return it. - if len(filtered_possible_loc_dt) == 1: - return filtered_possible_loc_dt[0] - - if len(filtered_possible_loc_dt) == 0: - filtered_possible_loc_dt = list(possible_loc_dt) - - # If we get this far, we have in a wierd timezone transition - # where the clocks have been wound back but is_dst is the same - # in both (eg. Europe/Warsaw 1915 when they switched to CET). - # At this point, we just have to guess unless we allow more - # hints to be passed in (such as the UTC offset or abbreviation), - # but that is just getting silly. - # - # Choose the earliest (by UTC) applicable timezone if is_dst=True - # Choose the latest (by UTC) applicable timezone if is_dst=False - # i.e., behave like end-of-DST transition - dates = {} # utc -> local - for local_dt in filtered_possible_loc_dt: - utc_time = ( - local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset) - assert utc_time not in dates - dates[utc_time] = local_dt - return dates[[min, max][not is_dst](dates)] - - def utcoffset(self, dt, is_dst=None): - '''See datetime.tzinfo.utcoffset - - The is_dst parameter may be used to remove ambiguity during DST - transitions. - - >>> from pytz import timezone - >>> tz = timezone('America/St_Johns') - >>> ambiguous = datetime(2009, 10, 31, 23, 30) - - >>> str(tz.utcoffset(ambiguous, is_dst=False)) - '-1 day, 20:30:00' - - >>> str(tz.utcoffset(ambiguous, is_dst=True)) - '-1 day, 21:30:00' - - >>> try: - ... tz.utcoffset(ambiguous) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - - ''' - if dt is None: - return None - elif dt.tzinfo is not self: - dt = self.localize(dt, is_dst) - return dt.tzinfo._utcoffset - else: - return self._utcoffset - - def dst(self, dt, is_dst=None): - '''See datetime.tzinfo.dst - - The is_dst parameter may be used to remove ambiguity during DST - transitions. - - >>> from pytz import timezone - >>> tz = timezone('America/St_Johns') - - >>> normal = datetime(2009, 9, 1) - - >>> str(tz.dst(normal)) - '1:00:00' - >>> str(tz.dst(normal, is_dst=False)) - '1:00:00' - >>> str(tz.dst(normal, is_dst=True)) - '1:00:00' - - >>> ambiguous = datetime(2009, 10, 31, 23, 30) - - >>> str(tz.dst(ambiguous, is_dst=False)) - '0:00:00' - >>> str(tz.dst(ambiguous, is_dst=True)) - '1:00:00' - >>> try: - ... tz.dst(ambiguous) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - - ''' - if dt is None: - return None - elif dt.tzinfo is not self: - dt = self.localize(dt, is_dst) - return dt.tzinfo._dst - else: - return self._dst - - def tzname(self, dt, is_dst=None): - '''See datetime.tzinfo.tzname - - The is_dst parameter may be used to remove ambiguity during DST - transitions. - - >>> from pytz import timezone - >>> tz = timezone('America/St_Johns') - - >>> normal = datetime(2009, 9, 1) - - >>> tz.tzname(normal) - 'NDT' - >>> tz.tzname(normal, is_dst=False) - 'NDT' - >>> tz.tzname(normal, is_dst=True) - 'NDT' - - >>> ambiguous = datetime(2009, 10, 31, 23, 30) - - >>> tz.tzname(ambiguous, is_dst=False) - 'NST' - >>> tz.tzname(ambiguous, is_dst=True) - 'NDT' - >>> try: - ... tz.tzname(ambiguous) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - ''' - if dt is None: - return self.zone - elif dt.tzinfo is not self: - dt = self.localize(dt, is_dst) - return dt.tzinfo._tzname - else: - return self._tzname - - def __repr__(self): - if self._dst: - dst = 'DST' - else: - dst = 'STD' - if self._utcoffset > _notime: - return '' % ( - self.zone, self._tzname, self._utcoffset, dst - ) - else: - return '' % ( - self.zone, self._tzname, self._utcoffset, dst - ) - - def __reduce__(self): - # Special pickle to zone remains a singleton and to cope with - # database changes. - return pytz._p, ( - self.zone, - _to_seconds(self._utcoffset), - _to_seconds(self._dst), - self._tzname - ) - - -def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None): - """Factory function for unpickling pytz tzinfo instances. - - This is shared for both StaticTzInfo and DstTzInfo instances, because - database changes could cause a zones implementation to switch between - these two base classes and we can't break pickles on a pytz version - upgrade. - """ - # Raises a KeyError if zone no longer exists, which should never happen - # and would be a bug. - tz = pytz.timezone(zone) - - # A StaticTzInfo - just return it - if utcoffset is None: - return tz - - # This pickle was created from a DstTzInfo. We need to - # determine which of the list of tzinfo instances for this zone - # to use in order to restore the state of any datetime instances using - # it correctly. - utcoffset = memorized_timedelta(utcoffset) - dstoffset = memorized_timedelta(dstoffset) - try: - return tz._tzinfos[(utcoffset, dstoffset, tzname)] - except KeyError: - # The particular state requested in this timezone no longer exists. - # This indicates a corrupt pickle, or the timezone database has been - # corrected violently enough to make this particular - # (utcoffset,dstoffset) no longer exist in the zone, or the - # abbreviation has been changed. - pass - - # See if we can find an entry differing only by tzname. Abbreviations - # get changed from the initial guess by the database maintainers to - # match reality when this information is discovered. - for localized_tz in tz._tzinfos.values(): - if (localized_tz._utcoffset == utcoffset and - localized_tz._dst == dstoffset): - return localized_tz - - # This (utcoffset, dstoffset) information has been removed from the - # zone. Add it back. This might occur when the database maintainers have - # corrected incorrect information. datetime instances using this - # incorrect information will continue to do so, exactly as they were - # before being pickled. This is purely an overly paranoid safety net - I - # doubt this will ever been needed in real life. - inf = (utcoffset, dstoffset, tzname) - tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos) - return tz._tzinfos[inf] diff --git a/telegramer/include/pytz/zoneinfo/Africa/Abidjan b/telegramer/include/pytz/zoneinfo/Africa/Abidjan deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Abidjan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Accra b/telegramer/include/pytz/zoneinfo/Africa/Accra deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Accra and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Addis_Ababa b/telegramer/include/pytz/zoneinfo/Africa/Addis_Ababa deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Addis_Ababa and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Algiers b/telegramer/include/pytz/zoneinfo/Africa/Algiers deleted file mode 100644 index 6cfd8a1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Algiers and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Asmara b/telegramer/include/pytz/zoneinfo/Africa/Asmara deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Asmara and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Asmera b/telegramer/include/pytz/zoneinfo/Africa/Asmera deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Asmera and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Bamako b/telegramer/include/pytz/zoneinfo/Africa/Bamako deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Bamako and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Bangui b/telegramer/include/pytz/zoneinfo/Africa/Bangui deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Bangui and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Banjul b/telegramer/include/pytz/zoneinfo/Africa/Banjul deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Banjul and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Bissau b/telegramer/include/pytz/zoneinfo/Africa/Bissau deleted file mode 100644 index 82ea5aa..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Bissau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Blantyre b/telegramer/include/pytz/zoneinfo/Africa/Blantyre deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Blantyre and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Brazzaville b/telegramer/include/pytz/zoneinfo/Africa/Brazzaville deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Brazzaville and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Bujumbura b/telegramer/include/pytz/zoneinfo/Africa/Bujumbura deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Bujumbura and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Cairo b/telegramer/include/pytz/zoneinfo/Africa/Cairo deleted file mode 100644 index d3f8196..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Cairo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Casablanca b/telegramer/include/pytz/zoneinfo/Africa/Casablanca deleted file mode 100644 index 17e0d1b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Casablanca and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Ceuta b/telegramer/include/pytz/zoneinfo/Africa/Ceuta deleted file mode 100644 index 850c8f0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Ceuta and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Conakry b/telegramer/include/pytz/zoneinfo/Africa/Conakry deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Conakry and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Dakar b/telegramer/include/pytz/zoneinfo/Africa/Dakar deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Dakar and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Dar_es_Salaam b/telegramer/include/pytz/zoneinfo/Africa/Dar_es_Salaam deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Dar_es_Salaam and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Djibouti b/telegramer/include/pytz/zoneinfo/Africa/Djibouti deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Djibouti and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Douala b/telegramer/include/pytz/zoneinfo/Africa/Douala deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Douala and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/El_Aaiun b/telegramer/include/pytz/zoneinfo/Africa/El_Aaiun deleted file mode 100644 index 64f1b76..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/El_Aaiun and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Freetown b/telegramer/include/pytz/zoneinfo/Africa/Freetown deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Freetown and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Gaborone b/telegramer/include/pytz/zoneinfo/Africa/Gaborone deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Gaborone and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Harare b/telegramer/include/pytz/zoneinfo/Africa/Harare deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Harare and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Johannesburg b/telegramer/include/pytz/zoneinfo/Africa/Johannesburg deleted file mode 100644 index b1c425d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Johannesburg and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Juba b/telegramer/include/pytz/zoneinfo/Africa/Juba deleted file mode 100644 index 0648294..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Juba and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Kampala b/telegramer/include/pytz/zoneinfo/Africa/Kampala deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Kampala and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Khartoum b/telegramer/include/pytz/zoneinfo/Africa/Khartoum deleted file mode 100644 index 8ee8cb9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Khartoum and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Kigali b/telegramer/include/pytz/zoneinfo/Africa/Kigali deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Kigali and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Kinshasa b/telegramer/include/pytz/zoneinfo/Africa/Kinshasa deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Kinshasa and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Lagos b/telegramer/include/pytz/zoneinfo/Africa/Lagos deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Lagos and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Libreville b/telegramer/include/pytz/zoneinfo/Africa/Libreville deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Libreville and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Lome b/telegramer/include/pytz/zoneinfo/Africa/Lome deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Lome and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Luanda b/telegramer/include/pytz/zoneinfo/Africa/Luanda deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Luanda and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Lubumbashi b/telegramer/include/pytz/zoneinfo/Africa/Lubumbashi deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Lubumbashi and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Lusaka b/telegramer/include/pytz/zoneinfo/Africa/Lusaka deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Lusaka and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Malabo b/telegramer/include/pytz/zoneinfo/Africa/Malabo deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Malabo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Maputo b/telegramer/include/pytz/zoneinfo/Africa/Maputo deleted file mode 100644 index 52753c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Maputo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Maseru b/telegramer/include/pytz/zoneinfo/Africa/Maseru deleted file mode 100644 index b1c425d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Maseru and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Mbabane b/telegramer/include/pytz/zoneinfo/Africa/Mbabane deleted file mode 100644 index b1c425d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Mbabane and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Mogadishu b/telegramer/include/pytz/zoneinfo/Africa/Mogadishu deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Mogadishu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Monrovia b/telegramer/include/pytz/zoneinfo/Africa/Monrovia deleted file mode 100644 index 6d68850..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Monrovia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Nairobi b/telegramer/include/pytz/zoneinfo/Africa/Nairobi deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Nairobi and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Ndjamena b/telegramer/include/pytz/zoneinfo/Africa/Ndjamena deleted file mode 100644 index a968845..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Ndjamena and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Niamey b/telegramer/include/pytz/zoneinfo/Africa/Niamey deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Niamey and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Nouakchott b/telegramer/include/pytz/zoneinfo/Africa/Nouakchott deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Nouakchott and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Ouagadougou b/telegramer/include/pytz/zoneinfo/Africa/Ouagadougou deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Ouagadougou and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Porto-Novo b/telegramer/include/pytz/zoneinfo/Africa/Porto-Novo deleted file mode 100644 index afb6a4a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Porto-Novo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Sao_Tome b/telegramer/include/pytz/zoneinfo/Africa/Sao_Tome deleted file mode 100644 index 59f3759..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Sao_Tome and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Timbuktu b/telegramer/include/pytz/zoneinfo/Africa/Timbuktu deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Timbuktu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Tripoli b/telegramer/include/pytz/zoneinfo/Africa/Tripoli deleted file mode 100644 index 07b393b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Tripoli and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Tunis b/telegramer/include/pytz/zoneinfo/Africa/Tunis deleted file mode 100644 index 427fa56..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Tunis and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Africa/Windhoek b/telegramer/include/pytz/zoneinfo/Africa/Windhoek deleted file mode 100644 index abecd13..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Africa/Windhoek and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Adak b/telegramer/include/pytz/zoneinfo/America/Adak deleted file mode 100644 index 4323649..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Adak and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Anchorage b/telegramer/include/pytz/zoneinfo/America/Anchorage deleted file mode 100644 index 9bbb2fd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Anchorage and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Anguilla b/telegramer/include/pytz/zoneinfo/America/Anguilla deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Anguilla and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Antigua b/telegramer/include/pytz/zoneinfo/America/Antigua deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Antigua and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Araguaina b/telegramer/include/pytz/zoneinfo/America/Araguaina deleted file mode 100644 index 49381b4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Araguaina and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Buenos_Aires b/telegramer/include/pytz/zoneinfo/America/Argentina/Buenos_Aires deleted file mode 100644 index 260f86a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Buenos_Aires and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Catamarca b/telegramer/include/pytz/zoneinfo/America/Argentina/Catamarca deleted file mode 100644 index 0ae222a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Catamarca and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/ComodRivadavia b/telegramer/include/pytz/zoneinfo/America/Argentina/ComodRivadavia deleted file mode 100644 index 0ae222a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/ComodRivadavia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Cordoba b/telegramer/include/pytz/zoneinfo/America/Argentina/Cordoba deleted file mode 100644 index da4c23a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Cordoba and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Jujuy b/telegramer/include/pytz/zoneinfo/America/Argentina/Jujuy deleted file mode 100644 index 604b856..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Jujuy and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/La_Rioja b/telegramer/include/pytz/zoneinfo/America/Argentina/La_Rioja deleted file mode 100644 index 2218e36..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/La_Rioja and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Mendoza b/telegramer/include/pytz/zoneinfo/America/Argentina/Mendoza deleted file mode 100644 index f9e677f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Mendoza and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Rio_Gallegos b/telegramer/include/pytz/zoneinfo/America/Argentina/Rio_Gallegos deleted file mode 100644 index c36587e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Rio_Gallegos and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Salta b/telegramer/include/pytz/zoneinfo/America/Argentina/Salta deleted file mode 100644 index 0e797f2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Salta and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/San_Juan b/telegramer/include/pytz/zoneinfo/America/Argentina/San_Juan deleted file mode 100644 index 2698495..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/San_Juan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/San_Luis b/telegramer/include/pytz/zoneinfo/America/Argentina/San_Luis deleted file mode 100644 index fe50f62..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/San_Luis and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Tucuman b/telegramer/include/pytz/zoneinfo/America/Argentina/Tucuman deleted file mode 100644 index c954000..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Tucuman and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Argentina/Ushuaia b/telegramer/include/pytz/zoneinfo/America/Argentina/Ushuaia deleted file mode 100644 index 3643628..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Argentina/Ushuaia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Aruba b/telegramer/include/pytz/zoneinfo/America/Aruba deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Aruba and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Asuncion b/telegramer/include/pytz/zoneinfo/America/Asuncion deleted file mode 100644 index 2f3bbda..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Asuncion and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Atikokan b/telegramer/include/pytz/zoneinfo/America/Atikokan deleted file mode 100644 index 9964b9a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Atikokan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Atka b/telegramer/include/pytz/zoneinfo/America/Atka deleted file mode 100644 index 4323649..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Atka and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Bahia b/telegramer/include/pytz/zoneinfo/America/Bahia deleted file mode 100644 index 15808d3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Bahia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Bahia_Banderas b/telegramer/include/pytz/zoneinfo/America/Bahia_Banderas deleted file mode 100644 index 896af3f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Bahia_Banderas and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Barbados b/telegramer/include/pytz/zoneinfo/America/Barbados deleted file mode 100644 index 00cd045..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Barbados and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Belem b/telegramer/include/pytz/zoneinfo/America/Belem deleted file mode 100644 index 60b5924..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Belem and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Belize b/telegramer/include/pytz/zoneinfo/America/Belize deleted file mode 100644 index e6f5dfa..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Belize and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Blanc-Sablon b/telegramer/include/pytz/zoneinfo/America/Blanc-Sablon deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Blanc-Sablon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Boa_Vista b/telegramer/include/pytz/zoneinfo/America/Boa_Vista deleted file mode 100644 index 978c331..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Boa_Vista and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Bogota b/telegramer/include/pytz/zoneinfo/America/Bogota deleted file mode 100644 index b2647d7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Bogota and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Boise b/telegramer/include/pytz/zoneinfo/America/Boise deleted file mode 100644 index f8d54e2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Boise and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Buenos_Aires b/telegramer/include/pytz/zoneinfo/America/Buenos_Aires deleted file mode 100644 index 260f86a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Buenos_Aires and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Cambridge_Bay b/telegramer/include/pytz/zoneinfo/America/Cambridge_Bay deleted file mode 100644 index f8db4b6..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Cambridge_Bay and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Campo_Grande b/telegramer/include/pytz/zoneinfo/America/Campo_Grande deleted file mode 100644 index 8120624..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Campo_Grande and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Cancun b/telegramer/include/pytz/zoneinfo/America/Cancun deleted file mode 100644 index f907f0a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Cancun and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Caracas b/telegramer/include/pytz/zoneinfo/America/Caracas deleted file mode 100644 index eedf725..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Caracas and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Catamarca b/telegramer/include/pytz/zoneinfo/America/Catamarca deleted file mode 100644 index 0ae222a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Catamarca and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Cayenne b/telegramer/include/pytz/zoneinfo/America/Cayenne deleted file mode 100644 index e5bc06f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Cayenne and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Cayman b/telegramer/include/pytz/zoneinfo/America/Cayman deleted file mode 100644 index 9964b9a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Cayman and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Chicago b/telegramer/include/pytz/zoneinfo/America/Chicago deleted file mode 100644 index a5b1617..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Chicago and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Chihuahua b/telegramer/include/pytz/zoneinfo/America/Chihuahua deleted file mode 100644 index 8ed5f93..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Chihuahua and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Coral_Harbour b/telegramer/include/pytz/zoneinfo/America/Coral_Harbour deleted file mode 100644 index 9964b9a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Coral_Harbour and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Cordoba b/telegramer/include/pytz/zoneinfo/America/Cordoba deleted file mode 100644 index da4c23a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Cordoba and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Costa_Rica b/telegramer/include/pytz/zoneinfo/America/Costa_Rica deleted file mode 100644 index 37cb85e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Costa_Rica and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Creston b/telegramer/include/pytz/zoneinfo/America/Creston deleted file mode 100644 index ac6bb0c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Creston and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Cuiaba b/telegramer/include/pytz/zoneinfo/America/Cuiaba deleted file mode 100644 index 9bea3d4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Cuiaba and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Curacao b/telegramer/include/pytz/zoneinfo/America/Curacao deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Curacao and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Danmarkshavn b/telegramer/include/pytz/zoneinfo/America/Danmarkshavn deleted file mode 100644 index 9549adc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Danmarkshavn and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Dawson b/telegramer/include/pytz/zoneinfo/America/Dawson deleted file mode 100644 index 343b632..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Dawson and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Dawson_Creek b/telegramer/include/pytz/zoneinfo/America/Dawson_Creek deleted file mode 100644 index db9e339..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Dawson_Creek and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Denver b/telegramer/include/pytz/zoneinfo/America/Denver deleted file mode 100644 index 5fbe26b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Denver and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Detroit b/telegramer/include/pytz/zoneinfo/America/Detroit deleted file mode 100644 index e104faa..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Detroit and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Dominica b/telegramer/include/pytz/zoneinfo/America/Dominica deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Dominica and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Edmonton b/telegramer/include/pytz/zoneinfo/America/Edmonton deleted file mode 100644 index cd78a6f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Edmonton and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Eirunepe b/telegramer/include/pytz/zoneinfo/America/Eirunepe deleted file mode 100644 index 39d6dae..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Eirunepe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/El_Salvador b/telegramer/include/pytz/zoneinfo/America/El_Salvador deleted file mode 100644 index e2f2230..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/El_Salvador and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Ensenada b/telegramer/include/pytz/zoneinfo/America/Ensenada deleted file mode 100644 index ada6bf7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Ensenada and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Fort_Nelson b/telegramer/include/pytz/zoneinfo/America/Fort_Nelson deleted file mode 100644 index 5a0b7f1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Fort_Nelson and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Fort_Wayne b/telegramer/include/pytz/zoneinfo/America/Fort_Wayne deleted file mode 100644 index 09511cc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Fort_Wayne and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Fortaleza b/telegramer/include/pytz/zoneinfo/America/Fortaleza deleted file mode 100644 index be57dc2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Fortaleza and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Glace_Bay b/telegramer/include/pytz/zoneinfo/America/Glace_Bay deleted file mode 100644 index 48412a4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Glace_Bay and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Godthab b/telegramer/include/pytz/zoneinfo/America/Godthab deleted file mode 100644 index 0160308..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Godthab and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Goose_Bay b/telegramer/include/pytz/zoneinfo/America/Goose_Bay deleted file mode 100644 index a3f2990..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Goose_Bay and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Grand_Turk b/telegramer/include/pytz/zoneinfo/America/Grand_Turk deleted file mode 100644 index 06da1a6..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Grand_Turk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Grenada b/telegramer/include/pytz/zoneinfo/America/Grenada deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Grenada and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Guadeloupe b/telegramer/include/pytz/zoneinfo/America/Guadeloupe deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Guadeloupe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Guatemala b/telegramer/include/pytz/zoneinfo/America/Guatemala deleted file mode 100644 index 407138c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Guatemala and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Guayaquil b/telegramer/include/pytz/zoneinfo/America/Guayaquil deleted file mode 100644 index 0559a7a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Guayaquil and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Guyana b/telegramer/include/pytz/zoneinfo/America/Guyana deleted file mode 100644 index 7af58e5..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Guyana and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Halifax b/telegramer/include/pytz/zoneinfo/America/Halifax deleted file mode 100644 index 756099a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Halifax and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Havana b/telegramer/include/pytz/zoneinfo/America/Havana deleted file mode 100644 index b69ac45..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Havana and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Hermosillo b/telegramer/include/pytz/zoneinfo/America/Hermosillo deleted file mode 100644 index 791a9fa..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Hermosillo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Indianapolis b/telegramer/include/pytz/zoneinfo/America/Indiana/Indianapolis deleted file mode 100644 index 09511cc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Indianapolis and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Knox b/telegramer/include/pytz/zoneinfo/America/Indiana/Knox deleted file mode 100644 index fcd408d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Knox and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Marengo b/telegramer/include/pytz/zoneinfo/America/Indiana/Marengo deleted file mode 100644 index 1abf75e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Marengo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Petersburg b/telegramer/include/pytz/zoneinfo/America/Indiana/Petersburg deleted file mode 100644 index 0133548..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Petersburg and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Tell_City b/telegramer/include/pytz/zoneinfo/America/Indiana/Tell_City deleted file mode 100644 index 7bbb653..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Tell_City and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Vevay b/telegramer/include/pytz/zoneinfo/America/Indiana/Vevay deleted file mode 100644 index d236b7c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Vevay and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Vincennes b/telegramer/include/pytz/zoneinfo/America/Indiana/Vincennes deleted file mode 100644 index c818929..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Vincennes and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indiana/Winamac b/telegramer/include/pytz/zoneinfo/America/Indiana/Winamac deleted file mode 100644 index 630935c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indiana/Winamac and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Indianapolis b/telegramer/include/pytz/zoneinfo/America/Indianapolis deleted file mode 100644 index 09511cc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Indianapolis and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Inuvik b/telegramer/include/pytz/zoneinfo/America/Inuvik deleted file mode 100644 index 87bb355..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Inuvik and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Iqaluit b/telegramer/include/pytz/zoneinfo/America/Iqaluit deleted file mode 100644 index c8138bd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Iqaluit and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Jamaica b/telegramer/include/pytz/zoneinfo/America/Jamaica deleted file mode 100644 index 2a9b7fd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Jamaica and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Jujuy b/telegramer/include/pytz/zoneinfo/America/Jujuy deleted file mode 100644 index 604b856..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Jujuy and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Juneau b/telegramer/include/pytz/zoneinfo/America/Juneau deleted file mode 100644 index 451f349..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Juneau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Kentucky/Louisville b/telegramer/include/pytz/zoneinfo/America/Kentucky/Louisville deleted file mode 100644 index 177836e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Kentucky/Louisville and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Kentucky/Monticello b/telegramer/include/pytz/zoneinfo/America/Kentucky/Monticello deleted file mode 100644 index 438e3ea..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Kentucky/Monticello and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Knox_IN b/telegramer/include/pytz/zoneinfo/America/Knox_IN deleted file mode 100644 index fcd408d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Knox_IN and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Kralendijk b/telegramer/include/pytz/zoneinfo/America/Kralendijk deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Kralendijk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/La_Paz b/telegramer/include/pytz/zoneinfo/America/La_Paz deleted file mode 100644 index a101372..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/La_Paz and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Lima b/telegramer/include/pytz/zoneinfo/America/Lima deleted file mode 100644 index 3c6529b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Lima and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Los_Angeles b/telegramer/include/pytz/zoneinfo/America/Los_Angeles deleted file mode 100644 index 9dad4f4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Los_Angeles and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Louisville b/telegramer/include/pytz/zoneinfo/America/Louisville deleted file mode 100644 index 177836e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Louisville and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Lower_Princes b/telegramer/include/pytz/zoneinfo/America/Lower_Princes deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Lower_Princes and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Maceio b/telegramer/include/pytz/zoneinfo/America/Maceio deleted file mode 100644 index bc8b951..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Maceio and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Managua b/telegramer/include/pytz/zoneinfo/America/Managua deleted file mode 100644 index e0242bf..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Managua and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Manaus b/telegramer/include/pytz/zoneinfo/America/Manaus deleted file mode 100644 index 63d58f8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Manaus and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Marigot b/telegramer/include/pytz/zoneinfo/America/Marigot deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Marigot and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Martinique b/telegramer/include/pytz/zoneinfo/America/Martinique deleted file mode 100644 index 8df43dc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Martinique and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Matamoros b/telegramer/include/pytz/zoneinfo/America/Matamoros deleted file mode 100644 index 047968d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Matamoros and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Mazatlan b/telegramer/include/pytz/zoneinfo/America/Mazatlan deleted file mode 100644 index e4a7857..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Mazatlan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Mendoza b/telegramer/include/pytz/zoneinfo/America/Mendoza deleted file mode 100644 index f9e677f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Mendoza and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Menominee b/telegramer/include/pytz/zoneinfo/America/Menominee deleted file mode 100644 index 3146138..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Menominee and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Merida b/telegramer/include/pytz/zoneinfo/America/Merida deleted file mode 100644 index ea852da..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Merida and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Metlakatla b/telegramer/include/pytz/zoneinfo/America/Metlakatla deleted file mode 100644 index 1e94be3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Metlakatla and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Mexico_City b/telegramer/include/pytz/zoneinfo/America/Mexico_City deleted file mode 100644 index e7fb6f2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Mexico_City and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Miquelon b/telegramer/include/pytz/zoneinfo/America/Miquelon deleted file mode 100644 index b924b71..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Miquelon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Moncton b/telegramer/include/pytz/zoneinfo/America/Moncton deleted file mode 100644 index 9df8d0f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Moncton and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Monterrey b/telegramer/include/pytz/zoneinfo/America/Monterrey deleted file mode 100644 index a8928c8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Monterrey and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Montevideo b/telegramer/include/pytz/zoneinfo/America/Montevideo deleted file mode 100644 index 2f357bc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Montevideo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Montreal b/telegramer/include/pytz/zoneinfo/America/Montreal deleted file mode 100644 index 6752c5b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Montreal and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Montserrat b/telegramer/include/pytz/zoneinfo/America/Montserrat deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Montserrat and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Nassau b/telegramer/include/pytz/zoneinfo/America/Nassau deleted file mode 100644 index 6752c5b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Nassau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/New_York b/telegramer/include/pytz/zoneinfo/America/New_York deleted file mode 100644 index 2f75480..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/New_York and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Nipigon b/telegramer/include/pytz/zoneinfo/America/Nipigon deleted file mode 100644 index f6a856e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Nipigon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Nome b/telegramer/include/pytz/zoneinfo/America/Nome deleted file mode 100644 index 10998df..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Nome and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Noronha b/telegramer/include/pytz/zoneinfo/America/Noronha deleted file mode 100644 index f140726..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Noronha and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/North_Dakota/Beulah b/telegramer/include/pytz/zoneinfo/America/North_Dakota/Beulah deleted file mode 100644 index 246345d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/North_Dakota/Beulah and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/North_Dakota/Center b/telegramer/include/pytz/zoneinfo/America/North_Dakota/Center deleted file mode 100644 index 1fa0703..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/North_Dakota/Center and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/North_Dakota/New_Salem b/telegramer/include/pytz/zoneinfo/America/North_Dakota/New_Salem deleted file mode 100644 index 123f2ae..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/North_Dakota/New_Salem and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Nuuk b/telegramer/include/pytz/zoneinfo/America/Nuuk deleted file mode 100644 index 0160308..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Nuuk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Ojinaga b/telegramer/include/pytz/zoneinfo/America/Ojinaga deleted file mode 100644 index fc4a03e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Ojinaga and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Panama b/telegramer/include/pytz/zoneinfo/America/Panama deleted file mode 100644 index 9964b9a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Panama and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Pangnirtung b/telegramer/include/pytz/zoneinfo/America/Pangnirtung deleted file mode 100644 index 3e4e0db..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Pangnirtung and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Paramaribo b/telegramer/include/pytz/zoneinfo/America/Paramaribo deleted file mode 100644 index bc8a6ed..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Paramaribo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Phoenix b/telegramer/include/pytz/zoneinfo/America/Phoenix deleted file mode 100644 index ac6bb0c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Phoenix and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Port-au-Prince b/telegramer/include/pytz/zoneinfo/America/Port-au-Prince deleted file mode 100644 index 287f143..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Port-au-Prince and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Port_of_Spain b/telegramer/include/pytz/zoneinfo/America/Port_of_Spain deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Port_of_Spain and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Porto_Acre b/telegramer/include/pytz/zoneinfo/America/Porto_Acre deleted file mode 100644 index a374cb4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Porto_Acre and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Porto_Velho b/telegramer/include/pytz/zoneinfo/America/Porto_Velho deleted file mode 100644 index 2e873a5..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Porto_Velho and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Puerto_Rico b/telegramer/include/pytz/zoneinfo/America/Puerto_Rico deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Puerto_Rico and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Punta_Arenas b/telegramer/include/pytz/zoneinfo/America/Punta_Arenas deleted file mode 100644 index 13bd1d9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Punta_Arenas and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Rainy_River b/telegramer/include/pytz/zoneinfo/America/Rainy_River deleted file mode 100644 index ea66099..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Rainy_River and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Rankin_Inlet b/telegramer/include/pytz/zoneinfo/America/Rankin_Inlet deleted file mode 100644 index 3a70587..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Rankin_Inlet and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Recife b/telegramer/include/pytz/zoneinfo/America/Recife deleted file mode 100644 index d7abb16..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Recife and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Regina b/telegramer/include/pytz/zoneinfo/America/Regina deleted file mode 100644 index 20c9c84..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Regina and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Resolute b/telegramer/include/pytz/zoneinfo/America/Resolute deleted file mode 100644 index 0a73b75..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Resolute and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Rio_Branco b/telegramer/include/pytz/zoneinfo/America/Rio_Branco deleted file mode 100644 index a374cb4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Rio_Branco and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Rosario b/telegramer/include/pytz/zoneinfo/America/Rosario deleted file mode 100644 index da4c23a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Rosario and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Santa_Isabel b/telegramer/include/pytz/zoneinfo/America/Santa_Isabel deleted file mode 100644 index ada6bf7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Santa_Isabel and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Santarem b/telegramer/include/pytz/zoneinfo/America/Santarem deleted file mode 100644 index c28f360..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Santarem and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Santiago b/telegramer/include/pytz/zoneinfo/America/Santiago deleted file mode 100644 index aa29060..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Santiago and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Santo_Domingo b/telegramer/include/pytz/zoneinfo/America/Santo_Domingo deleted file mode 100644 index 4fe36fd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Santo_Domingo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Sao_Paulo b/telegramer/include/pytz/zoneinfo/America/Sao_Paulo deleted file mode 100644 index 13ff083..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Sao_Paulo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Scoresbysund b/telegramer/include/pytz/zoneinfo/America/Scoresbysund deleted file mode 100644 index e20e9e1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Scoresbysund and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Shiprock b/telegramer/include/pytz/zoneinfo/America/Shiprock deleted file mode 100644 index 5fbe26b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Shiprock and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Sitka b/telegramer/include/pytz/zoneinfo/America/Sitka deleted file mode 100644 index 31f7061..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Sitka and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/St_Barthelemy b/telegramer/include/pytz/zoneinfo/America/St_Barthelemy deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/St_Barthelemy and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/St_Johns b/telegramer/include/pytz/zoneinfo/America/St_Johns deleted file mode 100644 index 65a5b0c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/St_Johns and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/St_Kitts b/telegramer/include/pytz/zoneinfo/America/St_Kitts deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/St_Kitts and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/St_Lucia b/telegramer/include/pytz/zoneinfo/America/St_Lucia deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/St_Lucia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/St_Thomas b/telegramer/include/pytz/zoneinfo/America/St_Thomas deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/St_Thomas and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/St_Vincent b/telegramer/include/pytz/zoneinfo/America/St_Vincent deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/St_Vincent and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Swift_Current b/telegramer/include/pytz/zoneinfo/America/Swift_Current deleted file mode 100644 index 8e9ef25..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Swift_Current and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Tegucigalpa b/telegramer/include/pytz/zoneinfo/America/Tegucigalpa deleted file mode 100644 index 2adacb2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Tegucigalpa and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Thule b/telegramer/include/pytz/zoneinfo/America/Thule deleted file mode 100644 index 6f802f1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Thule and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Thunder_Bay b/telegramer/include/pytz/zoneinfo/America/Thunder_Bay deleted file mode 100644 index e504c9a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Thunder_Bay and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Tijuana b/telegramer/include/pytz/zoneinfo/America/Tijuana deleted file mode 100644 index ada6bf7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Tijuana and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Toronto b/telegramer/include/pytz/zoneinfo/America/Toronto deleted file mode 100644 index 6752c5b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Toronto and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Tortola b/telegramer/include/pytz/zoneinfo/America/Tortola deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Tortola and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Vancouver b/telegramer/include/pytz/zoneinfo/America/Vancouver deleted file mode 100644 index bb60cbc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Vancouver and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Virgin b/telegramer/include/pytz/zoneinfo/America/Virgin deleted file mode 100644 index a662a57..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Virgin and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Whitehorse b/telegramer/include/pytz/zoneinfo/America/Whitehorse deleted file mode 100644 index 9ee229c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Whitehorse and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Winnipeg b/telegramer/include/pytz/zoneinfo/America/Winnipeg deleted file mode 100644 index ac40299..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Winnipeg and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Yakutat b/telegramer/include/pytz/zoneinfo/America/Yakutat deleted file mode 100644 index da209f9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Yakutat and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/America/Yellowknife b/telegramer/include/pytz/zoneinfo/America/Yellowknife deleted file mode 100644 index e6afa39..0000000 Binary files a/telegramer/include/pytz/zoneinfo/America/Yellowknife and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Casey b/telegramer/include/pytz/zoneinfo/Antarctica/Casey deleted file mode 100644 index cbcbe4e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Casey and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Davis b/telegramer/include/pytz/zoneinfo/Antarctica/Davis deleted file mode 100644 index 916f2c2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Davis and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/DumontDUrville b/telegramer/include/pytz/zoneinfo/Antarctica/DumontDUrville deleted file mode 100644 index 920ad27..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/DumontDUrville and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Macquarie b/telegramer/include/pytz/zoneinfo/Antarctica/Macquarie deleted file mode 100644 index 9e7cc68..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Macquarie and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Mawson b/telegramer/include/pytz/zoneinfo/Antarctica/Mawson deleted file mode 100644 index b32e7fd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Mawson and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/McMurdo b/telegramer/include/pytz/zoneinfo/Antarctica/McMurdo deleted file mode 100644 index 6575fdc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/McMurdo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Palmer b/telegramer/include/pytz/zoneinfo/Antarctica/Palmer deleted file mode 100644 index 3dd85f8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Palmer and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Rothera b/telegramer/include/pytz/zoneinfo/Antarctica/Rothera deleted file mode 100644 index 8b2430a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Rothera and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/South_Pole b/telegramer/include/pytz/zoneinfo/Antarctica/South_Pole deleted file mode 100644 index 6575fdc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/South_Pole and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Syowa b/telegramer/include/pytz/zoneinfo/Antarctica/Syowa deleted file mode 100644 index 2aea25f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Syowa and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Troll b/telegramer/include/pytz/zoneinfo/Antarctica/Troll deleted file mode 100644 index 5e565da..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Troll and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Antarctica/Vostok b/telegramer/include/pytz/zoneinfo/Antarctica/Vostok deleted file mode 100644 index 7283053..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Antarctica/Vostok and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Arctic/Longyearbyen b/telegramer/include/pytz/zoneinfo/Arctic/Longyearbyen deleted file mode 100644 index 15a34c3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Arctic/Longyearbyen and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Aden b/telegramer/include/pytz/zoneinfo/Asia/Aden deleted file mode 100644 index 2aea25f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Aden and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Almaty b/telegramer/include/pytz/zoneinfo/Asia/Almaty deleted file mode 100644 index a4b0077..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Almaty and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Amman b/telegramer/include/pytz/zoneinfo/Asia/Amman deleted file mode 100644 index 5dcf7e0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Amman and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Anadyr b/telegramer/include/pytz/zoneinfo/Asia/Anadyr deleted file mode 100644 index 6ed8b7c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Anadyr and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Aqtau b/telegramer/include/pytz/zoneinfo/Asia/Aqtau deleted file mode 100644 index e2d0f91..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Aqtau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Aqtobe b/telegramer/include/pytz/zoneinfo/Asia/Aqtobe deleted file mode 100644 index 06f0a13..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Aqtobe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Ashgabat b/telegramer/include/pytz/zoneinfo/Asia/Ashgabat deleted file mode 100644 index 73891af..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Ashgabat and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Ashkhabad b/telegramer/include/pytz/zoneinfo/Asia/Ashkhabad deleted file mode 100644 index 73891af..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Ashkhabad and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Atyrau b/telegramer/include/pytz/zoneinfo/Asia/Atyrau deleted file mode 100644 index 8b5153e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Atyrau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Baghdad b/telegramer/include/pytz/zoneinfo/Asia/Baghdad deleted file mode 100644 index f7162ed..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Baghdad and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Bahrain b/telegramer/include/pytz/zoneinfo/Asia/Bahrain deleted file mode 100644 index 63188b2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Bahrain and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Baku b/telegramer/include/pytz/zoneinfo/Asia/Baku deleted file mode 100644 index a0de74b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Baku and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Bangkok b/telegramer/include/pytz/zoneinfo/Asia/Bangkok deleted file mode 100644 index c292ac5..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Bangkok and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Barnaul b/telegramer/include/pytz/zoneinfo/Asia/Barnaul deleted file mode 100644 index 759592a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Barnaul and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Beirut b/telegramer/include/pytz/zoneinfo/Asia/Beirut deleted file mode 100644 index fb266ed..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Beirut and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Bishkek b/telegramer/include/pytz/zoneinfo/Asia/Bishkek deleted file mode 100644 index f6e20dd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Bishkek and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Brunei b/telegramer/include/pytz/zoneinfo/Asia/Brunei deleted file mode 100644 index 3dab0ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Brunei and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Calcutta b/telegramer/include/pytz/zoneinfo/Asia/Calcutta deleted file mode 100644 index 0014046..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Calcutta and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Chita b/telegramer/include/pytz/zoneinfo/Asia/Chita deleted file mode 100644 index c4149c0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Chita and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Choibalsan b/telegramer/include/pytz/zoneinfo/Asia/Choibalsan deleted file mode 100644 index e48daa8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Choibalsan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Chongqing b/telegramer/include/pytz/zoneinfo/Asia/Chongqing deleted file mode 100644 index 91f6f8b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Chongqing and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Chungking b/telegramer/include/pytz/zoneinfo/Asia/Chungking deleted file mode 100644 index 91f6f8b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Chungking and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Colombo b/telegramer/include/pytz/zoneinfo/Asia/Colombo deleted file mode 100644 index 62c64d8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Colombo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Dacca b/telegramer/include/pytz/zoneinfo/Asia/Dacca deleted file mode 100644 index b11c928..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Dacca and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Damascus b/telegramer/include/pytz/zoneinfo/Asia/Damascus deleted file mode 100644 index d9104a7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Damascus and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Dhaka b/telegramer/include/pytz/zoneinfo/Asia/Dhaka deleted file mode 100644 index b11c928..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Dhaka and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Dili b/telegramer/include/pytz/zoneinfo/Asia/Dili deleted file mode 100644 index 30943bb..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Dili and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Dubai b/telegramer/include/pytz/zoneinfo/Asia/Dubai deleted file mode 100644 index fc0a589..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Dubai and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Dushanbe b/telegramer/include/pytz/zoneinfo/Asia/Dushanbe deleted file mode 100644 index 82d85b8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Dushanbe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Famagusta b/telegramer/include/pytz/zoneinfo/Asia/Famagusta deleted file mode 100644 index 653b146..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Famagusta and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Gaza b/telegramer/include/pytz/zoneinfo/Asia/Gaza deleted file mode 100644 index 266981a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Gaza and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Harbin b/telegramer/include/pytz/zoneinfo/Asia/Harbin deleted file mode 100644 index 91f6f8b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Harbin and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Hebron b/telegramer/include/pytz/zoneinfo/Asia/Hebron deleted file mode 100644 index 0078bf0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Hebron and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Ho_Chi_Minh b/telegramer/include/pytz/zoneinfo/Asia/Ho_Chi_Minh deleted file mode 100644 index e2934e3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Ho_Chi_Minh and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Hong_Kong b/telegramer/include/pytz/zoneinfo/Asia/Hong_Kong deleted file mode 100644 index 23d0375..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Hong_Kong and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Hovd b/telegramer/include/pytz/zoneinfo/Asia/Hovd deleted file mode 100644 index 4cb800a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Hovd and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Irkutsk b/telegramer/include/pytz/zoneinfo/Asia/Irkutsk deleted file mode 100644 index 4dcbbb7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Irkutsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Istanbul b/telegramer/include/pytz/zoneinfo/Asia/Istanbul deleted file mode 100644 index 508446b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Istanbul and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Jakarta b/telegramer/include/pytz/zoneinfo/Asia/Jakarta deleted file mode 100644 index 5baa3a8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Jakarta and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Jayapura b/telegramer/include/pytz/zoneinfo/Asia/Jayapura deleted file mode 100644 index 3002c82..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Jayapura and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Jerusalem b/telegramer/include/pytz/zoneinfo/Asia/Jerusalem deleted file mode 100644 index 1ebd066..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Jerusalem and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kabul b/telegramer/include/pytz/zoneinfo/Asia/Kabul deleted file mode 100644 index d19b9bd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kabul and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kamchatka b/telegramer/include/pytz/zoneinfo/Asia/Kamchatka deleted file mode 100644 index 3e80b4e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kamchatka and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Karachi b/telegramer/include/pytz/zoneinfo/Asia/Karachi deleted file mode 100644 index ba65c0e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Karachi and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kashgar b/telegramer/include/pytz/zoneinfo/Asia/Kashgar deleted file mode 100644 index faa14d9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kashgar and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kathmandu b/telegramer/include/pytz/zoneinfo/Asia/Kathmandu deleted file mode 100644 index a5d5107..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kathmandu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Katmandu b/telegramer/include/pytz/zoneinfo/Asia/Katmandu deleted file mode 100644 index a5d5107..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Katmandu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Khandyga b/telegramer/include/pytz/zoneinfo/Asia/Khandyga deleted file mode 100644 index 72bea64..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Khandyga and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kolkata b/telegramer/include/pytz/zoneinfo/Asia/Kolkata deleted file mode 100644 index 0014046..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kolkata and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Krasnoyarsk b/telegramer/include/pytz/zoneinfo/Asia/Krasnoyarsk deleted file mode 100644 index 30c6f16..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Krasnoyarsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kuala_Lumpur b/telegramer/include/pytz/zoneinfo/Asia/Kuala_Lumpur deleted file mode 100644 index 612b01e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kuala_Lumpur and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kuching b/telegramer/include/pytz/zoneinfo/Asia/Kuching deleted file mode 100644 index c86750c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kuching and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Kuwait b/telegramer/include/pytz/zoneinfo/Asia/Kuwait deleted file mode 100644 index 2aea25f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Kuwait and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Macao b/telegramer/include/pytz/zoneinfo/Asia/Macao deleted file mode 100644 index cac6506..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Macao and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Macau b/telegramer/include/pytz/zoneinfo/Asia/Macau deleted file mode 100644 index cac6506..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Macau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Magadan b/telegramer/include/pytz/zoneinfo/Asia/Magadan deleted file mode 100644 index b4fcac1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Magadan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Makassar b/telegramer/include/pytz/zoneinfo/Asia/Makassar deleted file mode 100644 index 556ba86..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Makassar and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Manila b/telegramer/include/pytz/zoneinfo/Asia/Manila deleted file mode 100644 index f4f4b04..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Manila and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Muscat b/telegramer/include/pytz/zoneinfo/Asia/Muscat deleted file mode 100644 index fc0a589..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Muscat and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Nicosia b/telegramer/include/pytz/zoneinfo/Asia/Nicosia deleted file mode 100644 index f7f10ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Nicosia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Novokuznetsk b/telegramer/include/pytz/zoneinfo/Asia/Novokuznetsk deleted file mode 100644 index d983276..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Novokuznetsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Novosibirsk b/telegramer/include/pytz/zoneinfo/Asia/Novosibirsk deleted file mode 100644 index e0ee5fc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Novosibirsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Omsk b/telegramer/include/pytz/zoneinfo/Asia/Omsk deleted file mode 100644 index b29b769..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Omsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Oral b/telegramer/include/pytz/zoneinfo/Asia/Oral deleted file mode 100644 index ad1f9ca..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Oral and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Phnom_Penh b/telegramer/include/pytz/zoneinfo/Asia/Phnom_Penh deleted file mode 100644 index c292ac5..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Phnom_Penh and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Pontianak b/telegramer/include/pytz/zoneinfo/Asia/Pontianak deleted file mode 100644 index 12ce24c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Pontianak and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Pyongyang b/telegramer/include/pytz/zoneinfo/Asia/Pyongyang deleted file mode 100644 index 7ad7e0b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Pyongyang and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Qatar b/telegramer/include/pytz/zoneinfo/Asia/Qatar deleted file mode 100644 index 63188b2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Qatar and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Qostanay b/telegramer/include/pytz/zoneinfo/Asia/Qostanay deleted file mode 100644 index 73b9d96..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Qostanay and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Qyzylorda b/telegramer/include/pytz/zoneinfo/Asia/Qyzylorda deleted file mode 100644 index c2fe4c1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Qyzylorda and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Rangoon b/telegramer/include/pytz/zoneinfo/Asia/Rangoon deleted file mode 100644 index dd77395..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Rangoon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Riyadh b/telegramer/include/pytz/zoneinfo/Asia/Riyadh deleted file mode 100644 index 2aea25f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Riyadh and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Saigon b/telegramer/include/pytz/zoneinfo/Asia/Saigon deleted file mode 100644 index e2934e3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Saigon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Sakhalin b/telegramer/include/pytz/zoneinfo/Asia/Sakhalin deleted file mode 100644 index 485459c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Sakhalin and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Samarkand b/telegramer/include/pytz/zoneinfo/Asia/Samarkand deleted file mode 100644 index 030d47c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Samarkand and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Seoul b/telegramer/include/pytz/zoneinfo/Asia/Seoul deleted file mode 100644 index 96199e7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Seoul and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Shanghai b/telegramer/include/pytz/zoneinfo/Asia/Shanghai deleted file mode 100644 index 91f6f8b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Shanghai and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Singapore b/telegramer/include/pytz/zoneinfo/Asia/Singapore deleted file mode 100644 index 2364b21..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Singapore and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Srednekolymsk b/telegramer/include/pytz/zoneinfo/Asia/Srednekolymsk deleted file mode 100644 index 261a983..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Srednekolymsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Taipei b/telegramer/include/pytz/zoneinfo/Asia/Taipei deleted file mode 100644 index 24c4344..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Taipei and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Tashkent b/telegramer/include/pytz/zoneinfo/Asia/Tashkent deleted file mode 100644 index 32a9d7d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Tashkent and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Tbilisi b/telegramer/include/pytz/zoneinfo/Asia/Tbilisi deleted file mode 100644 index b608d79..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Tbilisi and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Tehran b/telegramer/include/pytz/zoneinfo/Asia/Tehran deleted file mode 100644 index 8cec5ad..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Tehran and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Tel_Aviv b/telegramer/include/pytz/zoneinfo/Asia/Tel_Aviv deleted file mode 100644 index 1ebd066..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Tel_Aviv and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Thimbu b/telegramer/include/pytz/zoneinfo/Asia/Thimbu deleted file mode 100644 index fe409c7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Thimbu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Thimphu b/telegramer/include/pytz/zoneinfo/Asia/Thimphu deleted file mode 100644 index fe409c7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Thimphu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Tokyo b/telegramer/include/pytz/zoneinfo/Asia/Tokyo deleted file mode 100644 index 26f4d34..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Tokyo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Tomsk b/telegramer/include/pytz/zoneinfo/Asia/Tomsk deleted file mode 100644 index 670e2ad..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Tomsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Ujung_Pandang b/telegramer/include/pytz/zoneinfo/Asia/Ujung_Pandang deleted file mode 100644 index 556ba86..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Ujung_Pandang and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Ulaanbaatar b/telegramer/include/pytz/zoneinfo/Asia/Ulaanbaatar deleted file mode 100644 index 2e20cc3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Ulaanbaatar and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Ulan_Bator b/telegramer/include/pytz/zoneinfo/Asia/Ulan_Bator deleted file mode 100644 index 2e20cc3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Ulan_Bator and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Urumqi b/telegramer/include/pytz/zoneinfo/Asia/Urumqi deleted file mode 100644 index faa14d9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Urumqi and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Ust-Nera b/telegramer/include/pytz/zoneinfo/Asia/Ust-Nera deleted file mode 100644 index 9e4a78f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Ust-Nera and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Vientiane b/telegramer/include/pytz/zoneinfo/Asia/Vientiane deleted file mode 100644 index c292ac5..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Vientiane and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Vladivostok b/telegramer/include/pytz/zoneinfo/Asia/Vladivostok deleted file mode 100644 index 8ab253c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Vladivostok and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Yakutsk b/telegramer/include/pytz/zoneinfo/Asia/Yakutsk deleted file mode 100644 index c815e99..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Yakutsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Yangon b/telegramer/include/pytz/zoneinfo/Asia/Yangon deleted file mode 100644 index dd77395..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Yangon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Yekaterinburg b/telegramer/include/pytz/zoneinfo/Asia/Yekaterinburg deleted file mode 100644 index 6958d7e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Yekaterinburg and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Asia/Yerevan b/telegramer/include/pytz/zoneinfo/Asia/Yerevan deleted file mode 100644 index 250bfe0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Asia/Yerevan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Azores b/telegramer/include/pytz/zoneinfo/Atlantic/Azores deleted file mode 100644 index 00a564f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Azores and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Bermuda b/telegramer/include/pytz/zoneinfo/Atlantic/Bermuda deleted file mode 100644 index 527524e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Bermuda and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Canary b/telegramer/include/pytz/zoneinfo/Atlantic/Canary deleted file mode 100644 index f319215..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Canary and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Cape_Verde b/telegramer/include/pytz/zoneinfo/Atlantic/Cape_Verde deleted file mode 100644 index e2a49d2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Cape_Verde and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Faeroe b/telegramer/include/pytz/zoneinfo/Atlantic/Faeroe deleted file mode 100644 index 4dab7ef..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Faeroe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Faroe b/telegramer/include/pytz/zoneinfo/Atlantic/Faroe deleted file mode 100644 index 4dab7ef..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Faroe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Jan_Mayen b/telegramer/include/pytz/zoneinfo/Atlantic/Jan_Mayen deleted file mode 100644 index 15a34c3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Jan_Mayen and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Madeira b/telegramer/include/pytz/zoneinfo/Atlantic/Madeira deleted file mode 100644 index 7ddcd88..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Madeira and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Reykjavik b/telegramer/include/pytz/zoneinfo/Atlantic/Reykjavik deleted file mode 100644 index 10e0fc8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Reykjavik and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/South_Georgia b/telegramer/include/pytz/zoneinfo/Atlantic/South_Georgia deleted file mode 100644 index 4466608..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/South_Georgia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/St_Helena b/telegramer/include/pytz/zoneinfo/Atlantic/St_Helena deleted file mode 100644 index 28b32ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/St_Helena and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Atlantic/Stanley b/telegramer/include/pytz/zoneinfo/Atlantic/Stanley deleted file mode 100644 index 88077f1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Atlantic/Stanley and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/ACT b/telegramer/include/pytz/zoneinfo/Australia/ACT deleted file mode 100644 index 0aea4c3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/ACT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Adelaide b/telegramer/include/pytz/zoneinfo/Australia/Adelaide deleted file mode 100644 index f5dedca..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Adelaide and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Brisbane b/telegramer/include/pytz/zoneinfo/Australia/Brisbane deleted file mode 100644 index 7ff9949..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Brisbane and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Broken_Hill b/telegramer/include/pytz/zoneinfo/Australia/Broken_Hill deleted file mode 100644 index 698c76e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Broken_Hill and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Canberra b/telegramer/include/pytz/zoneinfo/Australia/Canberra deleted file mode 100644 index 0aea4c3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Canberra and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Currie b/telegramer/include/pytz/zoneinfo/Australia/Currie deleted file mode 100644 index 3adb8e1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Currie and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Darwin b/telegramer/include/pytz/zoneinfo/Australia/Darwin deleted file mode 100644 index 74a3087..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Darwin and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Eucla b/telegramer/include/pytz/zoneinfo/Australia/Eucla deleted file mode 100644 index 3bf1171..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Eucla and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Hobart b/telegramer/include/pytz/zoneinfo/Australia/Hobart deleted file mode 100644 index 3adb8e1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Hobart and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/LHI b/telegramer/include/pytz/zoneinfo/Australia/LHI deleted file mode 100644 index 9e04a80..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/LHI and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Lindeman b/telegramer/include/pytz/zoneinfo/Australia/Lindeman deleted file mode 100644 index 4ee1825..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Lindeman and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Lord_Howe b/telegramer/include/pytz/zoneinfo/Australia/Lord_Howe deleted file mode 100644 index 9e04a80..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Lord_Howe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Melbourne b/telegramer/include/pytz/zoneinfo/Australia/Melbourne deleted file mode 100644 index ee903f4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Melbourne and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/NSW b/telegramer/include/pytz/zoneinfo/Australia/NSW deleted file mode 100644 index 0aea4c3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/NSW and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/North b/telegramer/include/pytz/zoneinfo/Australia/North deleted file mode 100644 index 74a3087..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/North and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Perth b/telegramer/include/pytz/zoneinfo/Australia/Perth deleted file mode 100644 index f8ddbdf..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Perth and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Queensland b/telegramer/include/pytz/zoneinfo/Australia/Queensland deleted file mode 100644 index 7ff9949..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Queensland and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/South b/telegramer/include/pytz/zoneinfo/Australia/South deleted file mode 100644 index f5dedca..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/South and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Sydney b/telegramer/include/pytz/zoneinfo/Australia/Sydney deleted file mode 100644 index 0aea4c3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Sydney and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Tasmania b/telegramer/include/pytz/zoneinfo/Australia/Tasmania deleted file mode 100644 index 3adb8e1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Tasmania and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Victoria b/telegramer/include/pytz/zoneinfo/Australia/Victoria deleted file mode 100644 index ee903f4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Victoria and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/West b/telegramer/include/pytz/zoneinfo/Australia/West deleted file mode 100644 index f8ddbdf..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/West and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Australia/Yancowinna b/telegramer/include/pytz/zoneinfo/Australia/Yancowinna deleted file mode 100644 index 698c76e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Australia/Yancowinna and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Brazil/Acre b/telegramer/include/pytz/zoneinfo/Brazil/Acre deleted file mode 100644 index a374cb4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Brazil/Acre and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Brazil/DeNoronha b/telegramer/include/pytz/zoneinfo/Brazil/DeNoronha deleted file mode 100644 index f140726..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Brazil/DeNoronha and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Brazil/East b/telegramer/include/pytz/zoneinfo/Brazil/East deleted file mode 100644 index 13ff083..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Brazil/East and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Brazil/West b/telegramer/include/pytz/zoneinfo/Brazil/West deleted file mode 100644 index 63d58f8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Brazil/West and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/CET b/telegramer/include/pytz/zoneinfo/CET deleted file mode 100644 index 122e934..0000000 Binary files a/telegramer/include/pytz/zoneinfo/CET and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/CST6CDT b/telegramer/include/pytz/zoneinfo/CST6CDT deleted file mode 100644 index ca67929..0000000 Binary files a/telegramer/include/pytz/zoneinfo/CST6CDT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Atlantic b/telegramer/include/pytz/zoneinfo/Canada/Atlantic deleted file mode 100644 index 756099a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Atlantic and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Central b/telegramer/include/pytz/zoneinfo/Canada/Central deleted file mode 100644 index ac40299..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Central and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Eastern b/telegramer/include/pytz/zoneinfo/Canada/Eastern deleted file mode 100644 index 6752c5b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Eastern and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Mountain b/telegramer/include/pytz/zoneinfo/Canada/Mountain deleted file mode 100644 index cd78a6f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Mountain and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Newfoundland b/telegramer/include/pytz/zoneinfo/Canada/Newfoundland deleted file mode 100644 index 65a5b0c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Newfoundland and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Pacific b/telegramer/include/pytz/zoneinfo/Canada/Pacific deleted file mode 100644 index bb60cbc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Pacific and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Saskatchewan b/telegramer/include/pytz/zoneinfo/Canada/Saskatchewan deleted file mode 100644 index 20c9c84..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Saskatchewan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Canada/Yukon b/telegramer/include/pytz/zoneinfo/Canada/Yukon deleted file mode 100644 index 9ee229c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Canada/Yukon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Chile/Continental b/telegramer/include/pytz/zoneinfo/Chile/Continental deleted file mode 100644 index aa29060..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Chile/Continental and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Chile/EasterIsland b/telegramer/include/pytz/zoneinfo/Chile/EasterIsland deleted file mode 100644 index cae3744..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Chile/EasterIsland and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Cuba b/telegramer/include/pytz/zoneinfo/Cuba deleted file mode 100644 index b69ac45..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Cuba and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/EET b/telegramer/include/pytz/zoneinfo/EET deleted file mode 100644 index cbdb71d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/EET and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/EST b/telegramer/include/pytz/zoneinfo/EST deleted file mode 100644 index 21ebc00..0000000 Binary files a/telegramer/include/pytz/zoneinfo/EST and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/EST5EDT b/telegramer/include/pytz/zoneinfo/EST5EDT deleted file mode 100644 index 9bce500..0000000 Binary files a/telegramer/include/pytz/zoneinfo/EST5EDT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Egypt b/telegramer/include/pytz/zoneinfo/Egypt deleted file mode 100644 index d3f8196..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Egypt and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Eire b/telegramer/include/pytz/zoneinfo/Eire deleted file mode 100644 index 1d99490..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Eire and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT b/telegramer/include/pytz/zoneinfo/Etc/GMT deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+0 b/telegramer/include/pytz/zoneinfo/Etc/GMT+0 deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+0 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+1 b/telegramer/include/pytz/zoneinfo/Etc/GMT+1 deleted file mode 100644 index 4dab6f9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+1 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+10 b/telegramer/include/pytz/zoneinfo/Etc/GMT+10 deleted file mode 100644 index c749290..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+10 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+11 b/telegramer/include/pytz/zoneinfo/Etc/GMT+11 deleted file mode 100644 index d969982..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+11 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+12 b/telegramer/include/pytz/zoneinfo/Etc/GMT+12 deleted file mode 100644 index cdeec90..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+12 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+2 b/telegramer/include/pytz/zoneinfo/Etc/GMT+2 deleted file mode 100644 index fbd2a94..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+2 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+3 b/telegramer/include/pytz/zoneinfo/Etc/GMT+3 deleted file mode 100644 index ee246ef..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+3 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+4 b/telegramer/include/pytz/zoneinfo/Etc/GMT+4 deleted file mode 100644 index 5a25ff2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+4 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+5 b/telegramer/include/pytz/zoneinfo/Etc/GMT+5 deleted file mode 100644 index c0b745f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+5 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+6 b/telegramer/include/pytz/zoneinfo/Etc/GMT+6 deleted file mode 100644 index 06e777d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+6 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+7 b/telegramer/include/pytz/zoneinfo/Etc/GMT+7 deleted file mode 100644 index 4e0b53a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+7 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+8 b/telegramer/include/pytz/zoneinfo/Etc/GMT+8 deleted file mode 100644 index 714b0c5..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+8 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT+9 b/telegramer/include/pytz/zoneinfo/Etc/GMT+9 deleted file mode 100644 index 78b9daa..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT+9 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-0 b/telegramer/include/pytz/zoneinfo/Etc/GMT-0 deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-0 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-1 b/telegramer/include/pytz/zoneinfo/Etc/GMT-1 deleted file mode 100644 index a838beb..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-1 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-10 b/telegramer/include/pytz/zoneinfo/Etc/GMT-10 deleted file mode 100644 index 68ff77d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-10 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-11 b/telegramer/include/pytz/zoneinfo/Etc/GMT-11 deleted file mode 100644 index 66af5a4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-11 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-12 b/telegramer/include/pytz/zoneinfo/Etc/GMT-12 deleted file mode 100644 index 17ba505..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-12 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-13 b/telegramer/include/pytz/zoneinfo/Etc/GMT-13 deleted file mode 100644 index 5f3706c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-13 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-14 b/telegramer/include/pytz/zoneinfo/Etc/GMT-14 deleted file mode 100644 index 7e9f9c4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-14 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-2 b/telegramer/include/pytz/zoneinfo/Etc/GMT-2 deleted file mode 100644 index fcef6d9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-2 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-3 b/telegramer/include/pytz/zoneinfo/Etc/GMT-3 deleted file mode 100644 index 27973bc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-3 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-4 b/telegramer/include/pytz/zoneinfo/Etc/GMT-4 deleted file mode 100644 index 1efd841..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-4 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-5 b/telegramer/include/pytz/zoneinfo/Etc/GMT-5 deleted file mode 100644 index 1f76184..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-5 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-6 b/telegramer/include/pytz/zoneinfo/Etc/GMT-6 deleted file mode 100644 index 952681e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-6 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-7 b/telegramer/include/pytz/zoneinfo/Etc/GMT-7 deleted file mode 100644 index cefc912..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-7 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-8 b/telegramer/include/pytz/zoneinfo/Etc/GMT-8 deleted file mode 100644 index afb093d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-8 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT-9 b/telegramer/include/pytz/zoneinfo/Etc/GMT-9 deleted file mode 100644 index 9265fb7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT-9 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/GMT0 b/telegramer/include/pytz/zoneinfo/Etc/GMT0 deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/GMT0 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/Greenwich b/telegramer/include/pytz/zoneinfo/Etc/Greenwich deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/Greenwich and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/UCT b/telegramer/include/pytz/zoneinfo/Etc/UCT deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/UCT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/UTC b/telegramer/include/pytz/zoneinfo/Etc/UTC deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/UTC and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/Universal b/telegramer/include/pytz/zoneinfo/Etc/Universal deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/Universal and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Etc/Zulu b/telegramer/include/pytz/zoneinfo/Etc/Zulu deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Etc/Zulu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Amsterdam b/telegramer/include/pytz/zoneinfo/Europe/Amsterdam deleted file mode 100644 index c3ff07b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Amsterdam and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Andorra b/telegramer/include/pytz/zoneinfo/Europe/Andorra deleted file mode 100644 index 5962550..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Andorra and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Astrakhan b/telegramer/include/pytz/zoneinfo/Europe/Astrakhan deleted file mode 100644 index 73a4d01..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Astrakhan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Athens b/telegramer/include/pytz/zoneinfo/Europe/Athens deleted file mode 100644 index 9f3a067..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Athens and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Belfast b/telegramer/include/pytz/zoneinfo/Europe/Belfast deleted file mode 100644 index ac02a81..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Belfast and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Belgrade b/telegramer/include/pytz/zoneinfo/Europe/Belgrade deleted file mode 100644 index 27de456..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Belgrade and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Berlin b/telegramer/include/pytz/zoneinfo/Europe/Berlin deleted file mode 100644 index 7f6d958..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Berlin and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Bratislava b/telegramer/include/pytz/zoneinfo/Europe/Bratislava deleted file mode 100644 index ce8f433..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Bratislava and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Brussels b/telegramer/include/pytz/zoneinfo/Europe/Brussels deleted file mode 100644 index 40d7124..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Brussels and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Bucharest b/telegramer/include/pytz/zoneinfo/Europe/Bucharest deleted file mode 100644 index 4303b90..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Bucharest and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Budapest b/telegramer/include/pytz/zoneinfo/Europe/Budapest deleted file mode 100644 index b76c873..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Budapest and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Busingen b/telegramer/include/pytz/zoneinfo/Europe/Busingen deleted file mode 100644 index ad6cf59..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Busingen and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Chisinau b/telegramer/include/pytz/zoneinfo/Europe/Chisinau deleted file mode 100644 index 5ee23fe..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Chisinau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Copenhagen b/telegramer/include/pytz/zoneinfo/Europe/Copenhagen deleted file mode 100644 index 776be6e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Copenhagen and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Dublin b/telegramer/include/pytz/zoneinfo/Europe/Dublin deleted file mode 100644 index 1d99490..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Dublin and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Gibraltar b/telegramer/include/pytz/zoneinfo/Europe/Gibraltar deleted file mode 100644 index 117aadb..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Gibraltar and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Guernsey b/telegramer/include/pytz/zoneinfo/Europe/Guernsey deleted file mode 100644 index ac02a81..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Guernsey and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Helsinki b/telegramer/include/pytz/zoneinfo/Europe/Helsinki deleted file mode 100644 index b4f8f9c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Helsinki and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Isle_of_Man b/telegramer/include/pytz/zoneinfo/Europe/Isle_of_Man deleted file mode 100644 index ac02a81..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Isle_of_Man and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Istanbul b/telegramer/include/pytz/zoneinfo/Europe/Istanbul deleted file mode 100644 index 508446b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Istanbul and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Jersey b/telegramer/include/pytz/zoneinfo/Europe/Jersey deleted file mode 100644 index ac02a81..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Jersey and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Kaliningrad b/telegramer/include/pytz/zoneinfo/Europe/Kaliningrad deleted file mode 100644 index cc99bea..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Kaliningrad and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Kiev b/telegramer/include/pytz/zoneinfo/Europe/Kiev deleted file mode 100644 index 52efea8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Kiev and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Kirov b/telegramer/include/pytz/zoneinfo/Europe/Kirov deleted file mode 100644 index a3b5320..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Kirov and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Lisbon b/telegramer/include/pytz/zoneinfo/Europe/Lisbon deleted file mode 100644 index 55f0193..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Lisbon and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Ljubljana b/telegramer/include/pytz/zoneinfo/Europe/Ljubljana deleted file mode 100644 index 27de456..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Ljubljana and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/London b/telegramer/include/pytz/zoneinfo/Europe/London deleted file mode 100644 index ac02a81..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/London and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Luxembourg b/telegramer/include/pytz/zoneinfo/Europe/Luxembourg deleted file mode 100644 index c4ca733..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Luxembourg and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Madrid b/telegramer/include/pytz/zoneinfo/Europe/Madrid deleted file mode 100644 index 16f6420..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Madrid and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Malta b/telegramer/include/pytz/zoneinfo/Europe/Malta deleted file mode 100644 index bf2452d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Malta and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Mariehamn b/telegramer/include/pytz/zoneinfo/Europe/Mariehamn deleted file mode 100644 index b4f8f9c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Mariehamn and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Minsk b/telegramer/include/pytz/zoneinfo/Europe/Minsk deleted file mode 100644 index 453306c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Minsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Monaco b/telegramer/include/pytz/zoneinfo/Europe/Monaco deleted file mode 100644 index adbe45d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Monaco and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Moscow b/telegramer/include/pytz/zoneinfo/Europe/Moscow deleted file mode 100644 index ddb3f4e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Moscow and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Nicosia b/telegramer/include/pytz/zoneinfo/Europe/Nicosia deleted file mode 100644 index f7f10ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Nicosia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Oslo b/telegramer/include/pytz/zoneinfo/Europe/Oslo deleted file mode 100644 index 15a34c3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Oslo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Paris b/telegramer/include/pytz/zoneinfo/Europe/Paris deleted file mode 100644 index 7d366c6..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Paris and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Podgorica b/telegramer/include/pytz/zoneinfo/Europe/Podgorica deleted file mode 100644 index 27de456..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Podgorica and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Prague b/telegramer/include/pytz/zoneinfo/Europe/Prague deleted file mode 100644 index ce8f433..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Prague and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Riga b/telegramer/include/pytz/zoneinfo/Europe/Riga deleted file mode 100644 index 8db477d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Riga and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Rome b/telegramer/include/pytz/zoneinfo/Europe/Rome deleted file mode 100644 index ac4c163..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Rome and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Samara b/telegramer/include/pytz/zoneinfo/Europe/Samara deleted file mode 100644 index 97d5dd9..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Samara and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/San_Marino b/telegramer/include/pytz/zoneinfo/Europe/San_Marino deleted file mode 100644 index ac4c163..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/San_Marino and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Sarajevo b/telegramer/include/pytz/zoneinfo/Europe/Sarajevo deleted file mode 100644 index 27de456..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Sarajevo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Saratov b/telegramer/include/pytz/zoneinfo/Europe/Saratov deleted file mode 100644 index 8fd5f6d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Saratov and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Simferopol b/telegramer/include/pytz/zoneinfo/Europe/Simferopol deleted file mode 100644 index 29107a0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Simferopol and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Skopje b/telegramer/include/pytz/zoneinfo/Europe/Skopje deleted file mode 100644 index 27de456..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Skopje and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Sofia b/telegramer/include/pytz/zoneinfo/Europe/Sofia deleted file mode 100644 index 0e4d879..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Sofia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Stockholm b/telegramer/include/pytz/zoneinfo/Europe/Stockholm deleted file mode 100644 index f3e0c7f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Stockholm and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Tallinn b/telegramer/include/pytz/zoneinfo/Europe/Tallinn deleted file mode 100644 index b5acca3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Tallinn and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Tirane b/telegramer/include/pytz/zoneinfo/Europe/Tirane deleted file mode 100644 index 0b86017..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Tirane and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Tiraspol b/telegramer/include/pytz/zoneinfo/Europe/Tiraspol deleted file mode 100644 index 5ee23fe..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Tiraspol and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Ulyanovsk b/telegramer/include/pytz/zoneinfo/Europe/Ulyanovsk deleted file mode 100644 index 7b61bdc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Ulyanovsk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Uzhgorod b/telegramer/include/pytz/zoneinfo/Europe/Uzhgorod deleted file mode 100644 index 0eb2150..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Uzhgorod and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Vaduz b/telegramer/include/pytz/zoneinfo/Europe/Vaduz deleted file mode 100644 index ad6cf59..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Vaduz and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Vatican b/telegramer/include/pytz/zoneinfo/Europe/Vatican deleted file mode 100644 index ac4c163..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Vatican and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Vienna b/telegramer/include/pytz/zoneinfo/Europe/Vienna deleted file mode 100644 index 3582bb1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Vienna and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Vilnius b/telegramer/include/pytz/zoneinfo/Europe/Vilnius deleted file mode 100644 index 7abd63f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Vilnius and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Volgograd b/telegramer/include/pytz/zoneinfo/Europe/Volgograd deleted file mode 100644 index 11739ac..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Volgograd and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Warsaw b/telegramer/include/pytz/zoneinfo/Europe/Warsaw deleted file mode 100644 index e33cf67..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Warsaw and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Zagreb b/telegramer/include/pytz/zoneinfo/Europe/Zagreb deleted file mode 100644 index 27de456..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Zagreb and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Zaporozhye b/telegramer/include/pytz/zoneinfo/Europe/Zaporozhye deleted file mode 100644 index f0406c1..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Zaporozhye and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Europe/Zurich b/telegramer/include/pytz/zoneinfo/Europe/Zurich deleted file mode 100644 index ad6cf59..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Europe/Zurich and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Factory b/telegramer/include/pytz/zoneinfo/Factory deleted file mode 100644 index 60aa2a0..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Factory and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/GB b/telegramer/include/pytz/zoneinfo/GB deleted file mode 100644 index ac02a81..0000000 Binary files a/telegramer/include/pytz/zoneinfo/GB and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/GB-Eire b/telegramer/include/pytz/zoneinfo/GB-Eire deleted file mode 100644 index ac02a81..0000000 Binary files a/telegramer/include/pytz/zoneinfo/GB-Eire and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/GMT b/telegramer/include/pytz/zoneinfo/GMT deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/GMT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/GMT+0 b/telegramer/include/pytz/zoneinfo/GMT+0 deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/GMT+0 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/GMT-0 b/telegramer/include/pytz/zoneinfo/GMT-0 deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/GMT-0 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/GMT0 b/telegramer/include/pytz/zoneinfo/GMT0 deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/GMT0 and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Greenwich b/telegramer/include/pytz/zoneinfo/Greenwich deleted file mode 100644 index c634746..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Greenwich and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/HST b/telegramer/include/pytz/zoneinfo/HST deleted file mode 100644 index cccd45e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/HST and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Hongkong b/telegramer/include/pytz/zoneinfo/Hongkong deleted file mode 100644 index 23d0375..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Hongkong and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Iceland b/telegramer/include/pytz/zoneinfo/Iceland deleted file mode 100644 index 10e0fc8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Iceland and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Antananarivo b/telegramer/include/pytz/zoneinfo/Indian/Antananarivo deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Antananarivo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Chagos b/telegramer/include/pytz/zoneinfo/Indian/Chagos deleted file mode 100644 index 93d6dda..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Chagos and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Christmas b/telegramer/include/pytz/zoneinfo/Indian/Christmas deleted file mode 100644 index d18c381..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Christmas and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Cocos b/telegramer/include/pytz/zoneinfo/Indian/Cocos deleted file mode 100644 index f8116e7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Cocos and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Comoro b/telegramer/include/pytz/zoneinfo/Indian/Comoro deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Comoro and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Kerguelen b/telegramer/include/pytz/zoneinfo/Indian/Kerguelen deleted file mode 100644 index cde4cf7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Kerguelen and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Mahe b/telegramer/include/pytz/zoneinfo/Indian/Mahe deleted file mode 100644 index 208f938..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Mahe and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Maldives b/telegramer/include/pytz/zoneinfo/Indian/Maldives deleted file mode 100644 index 7c839cf..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Maldives and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Mauritius b/telegramer/include/pytz/zoneinfo/Indian/Mauritius deleted file mode 100644 index 17f2616..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Mauritius and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Mayotte b/telegramer/include/pytz/zoneinfo/Indian/Mayotte deleted file mode 100644 index 9dcfc19..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Mayotte and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Indian/Reunion b/telegramer/include/pytz/zoneinfo/Indian/Reunion deleted file mode 100644 index dfe0831..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Indian/Reunion and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Iran b/telegramer/include/pytz/zoneinfo/Iran deleted file mode 100644 index 8cec5ad..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Iran and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Israel b/telegramer/include/pytz/zoneinfo/Israel deleted file mode 100644 index 1ebd066..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Israel and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Jamaica b/telegramer/include/pytz/zoneinfo/Jamaica deleted file mode 100644 index 2a9b7fd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Jamaica and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Japan b/telegramer/include/pytz/zoneinfo/Japan deleted file mode 100644 index 26f4d34..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Japan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Kwajalein b/telegramer/include/pytz/zoneinfo/Kwajalein deleted file mode 100644 index 1a7975f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Kwajalein and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Libya b/telegramer/include/pytz/zoneinfo/Libya deleted file mode 100644 index 07b393b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Libya and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/MET b/telegramer/include/pytz/zoneinfo/MET deleted file mode 100644 index 4a826bb..0000000 Binary files a/telegramer/include/pytz/zoneinfo/MET and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/MST b/telegramer/include/pytz/zoneinfo/MST deleted file mode 100644 index c93a58e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/MST and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/MST7MDT b/telegramer/include/pytz/zoneinfo/MST7MDT deleted file mode 100644 index 4506a6e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/MST7MDT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Mexico/BajaNorte b/telegramer/include/pytz/zoneinfo/Mexico/BajaNorte deleted file mode 100644 index ada6bf7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Mexico/BajaNorte and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Mexico/BajaSur b/telegramer/include/pytz/zoneinfo/Mexico/BajaSur deleted file mode 100644 index e4a7857..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Mexico/BajaSur and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Mexico/General b/telegramer/include/pytz/zoneinfo/Mexico/General deleted file mode 100644 index e7fb6f2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Mexico/General and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/NZ b/telegramer/include/pytz/zoneinfo/NZ deleted file mode 100644 index 6575fdc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/NZ and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/NZ-CHAT b/telegramer/include/pytz/zoneinfo/NZ-CHAT deleted file mode 100644 index c004109..0000000 Binary files a/telegramer/include/pytz/zoneinfo/NZ-CHAT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Navajo b/telegramer/include/pytz/zoneinfo/Navajo deleted file mode 100644 index 5fbe26b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Navajo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/PRC b/telegramer/include/pytz/zoneinfo/PRC deleted file mode 100644 index 91f6f8b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/PRC and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/PST8PDT b/telegramer/include/pytz/zoneinfo/PST8PDT deleted file mode 100644 index 99d246b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/PST8PDT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Apia b/telegramer/include/pytz/zoneinfo/Pacific/Apia deleted file mode 100644 index 999c367..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Apia and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Auckland b/telegramer/include/pytz/zoneinfo/Pacific/Auckland deleted file mode 100644 index 6575fdc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Auckland and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Bougainville b/telegramer/include/pytz/zoneinfo/Pacific/Bougainville deleted file mode 100644 index 2892d26..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Bougainville and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Chatham b/telegramer/include/pytz/zoneinfo/Pacific/Chatham deleted file mode 100644 index c004109..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Chatham and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Chuuk b/telegramer/include/pytz/zoneinfo/Pacific/Chuuk deleted file mode 100644 index 07c84b7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Chuuk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Easter b/telegramer/include/pytz/zoneinfo/Pacific/Easter deleted file mode 100644 index cae3744..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Easter and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Efate b/telegramer/include/pytz/zoneinfo/Pacific/Efate deleted file mode 100644 index d8d4093..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Efate and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Enderbury b/telegramer/include/pytz/zoneinfo/Pacific/Enderbury deleted file mode 100644 index 39b786e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Enderbury and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Fakaofo b/telegramer/include/pytz/zoneinfo/Pacific/Fakaofo deleted file mode 100644 index e40307f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Fakaofo and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Fiji b/telegramer/include/pytz/zoneinfo/Pacific/Fiji deleted file mode 100644 index af07ac8..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Fiji and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Funafuti b/telegramer/include/pytz/zoneinfo/Pacific/Funafuti deleted file mode 100644 index ea72863..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Funafuti and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Galapagos b/telegramer/include/pytz/zoneinfo/Pacific/Galapagos deleted file mode 100644 index 31f0921..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Galapagos and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Gambier b/telegramer/include/pytz/zoneinfo/Pacific/Gambier deleted file mode 100644 index e1fc3da..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Gambier and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Guadalcanal b/telegramer/include/pytz/zoneinfo/Pacific/Guadalcanal deleted file mode 100644 index 7e9d10a..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Guadalcanal and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Guam b/telegramer/include/pytz/zoneinfo/Pacific/Guam deleted file mode 100644 index 66490d2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Guam and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Honolulu b/telegramer/include/pytz/zoneinfo/Pacific/Honolulu deleted file mode 100644 index c7cd060..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Honolulu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Johnston b/telegramer/include/pytz/zoneinfo/Pacific/Johnston deleted file mode 100644 index c7cd060..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Johnston and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Kanton b/telegramer/include/pytz/zoneinfo/Pacific/Kanton deleted file mode 100644 index 39b786e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Kanton and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Kiritimati b/telegramer/include/pytz/zoneinfo/Pacific/Kiritimati deleted file mode 100644 index 7cae0cb..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Kiritimati and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Kosrae b/telegramer/include/pytz/zoneinfo/Pacific/Kosrae deleted file mode 100644 index a584aae..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Kosrae and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Kwajalein b/telegramer/include/pytz/zoneinfo/Pacific/Kwajalein deleted file mode 100644 index 1a7975f..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Kwajalein and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Majuro b/telegramer/include/pytz/zoneinfo/Pacific/Majuro deleted file mode 100644 index 9ef8374..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Majuro and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Marquesas b/telegramer/include/pytz/zoneinfo/Pacific/Marquesas deleted file mode 100644 index 74d6792..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Marquesas and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Midway b/telegramer/include/pytz/zoneinfo/Pacific/Midway deleted file mode 100644 index cb56709..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Midway and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Nauru b/telegramer/include/pytz/zoneinfo/Pacific/Nauru deleted file mode 100644 index acec042..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Nauru and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Niue b/telegramer/include/pytz/zoneinfo/Pacific/Niue deleted file mode 100644 index 89117b3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Niue and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Norfolk b/telegramer/include/pytz/zoneinfo/Pacific/Norfolk deleted file mode 100644 index 53c1aad..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Norfolk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Noumea b/telegramer/include/pytz/zoneinfo/Pacific/Noumea deleted file mode 100644 index 931a1a3..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Noumea and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Pago_Pago b/telegramer/include/pytz/zoneinfo/Pacific/Pago_Pago deleted file mode 100644 index cb56709..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Pago_Pago and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Palau b/telegramer/include/pytz/zoneinfo/Pacific/Palau deleted file mode 100644 index 146b351..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Palau and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Pitcairn b/telegramer/include/pytz/zoneinfo/Pacific/Pitcairn deleted file mode 100644 index ef91b06..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Pitcairn and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Pohnpei b/telegramer/include/pytz/zoneinfo/Pacific/Pohnpei deleted file mode 100644 index c298ddd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Pohnpei and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Ponape b/telegramer/include/pytz/zoneinfo/Pacific/Ponape deleted file mode 100644 index c298ddd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Ponape and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Port_Moresby b/telegramer/include/pytz/zoneinfo/Pacific/Port_Moresby deleted file mode 100644 index 920ad27..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Port_Moresby and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Rarotonga b/telegramer/include/pytz/zoneinfo/Pacific/Rarotonga deleted file mode 100644 index eea37ab..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Rarotonga and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Saipan b/telegramer/include/pytz/zoneinfo/Pacific/Saipan deleted file mode 100644 index 66490d2..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Saipan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Samoa b/telegramer/include/pytz/zoneinfo/Pacific/Samoa deleted file mode 100644 index cb56709..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Samoa and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Tahiti b/telegramer/include/pytz/zoneinfo/Pacific/Tahiti deleted file mode 100644 index 442b8eb..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Tahiti and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Tarawa b/telegramer/include/pytz/zoneinfo/Pacific/Tarawa deleted file mode 100644 index 3db6c75..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Tarawa and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Tongatapu b/telegramer/include/pytz/zoneinfo/Pacific/Tongatapu deleted file mode 100644 index c2e5999..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Tongatapu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Truk b/telegramer/include/pytz/zoneinfo/Pacific/Truk deleted file mode 100644 index 07c84b7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Truk and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Wake b/telegramer/include/pytz/zoneinfo/Pacific/Wake deleted file mode 100644 index c9e3106..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Wake and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Wallis b/telegramer/include/pytz/zoneinfo/Pacific/Wallis deleted file mode 100644 index b35344b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Wallis and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Pacific/Yap b/telegramer/include/pytz/zoneinfo/Pacific/Yap deleted file mode 100644 index 07c84b7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Pacific/Yap and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Poland b/telegramer/include/pytz/zoneinfo/Poland deleted file mode 100644 index e33cf67..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Poland and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Portugal b/telegramer/include/pytz/zoneinfo/Portugal deleted file mode 100644 index 55f0193..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Portugal and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/ROC b/telegramer/include/pytz/zoneinfo/ROC deleted file mode 100644 index 24c4344..0000000 Binary files a/telegramer/include/pytz/zoneinfo/ROC and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/ROK b/telegramer/include/pytz/zoneinfo/ROK deleted file mode 100644 index 96199e7..0000000 Binary files a/telegramer/include/pytz/zoneinfo/ROK and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Singapore b/telegramer/include/pytz/zoneinfo/Singapore deleted file mode 100644 index 2364b21..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Singapore and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Turkey b/telegramer/include/pytz/zoneinfo/Turkey deleted file mode 100644 index 508446b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Turkey and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/UCT b/telegramer/include/pytz/zoneinfo/UCT deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/UCT and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Alaska b/telegramer/include/pytz/zoneinfo/US/Alaska deleted file mode 100644 index 9bbb2fd..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Alaska and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Aleutian b/telegramer/include/pytz/zoneinfo/US/Aleutian deleted file mode 100644 index 4323649..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Aleutian and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Arizona b/telegramer/include/pytz/zoneinfo/US/Arizona deleted file mode 100644 index ac6bb0c..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Arizona and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Central b/telegramer/include/pytz/zoneinfo/US/Central deleted file mode 100644 index a5b1617..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Central and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/East-Indiana b/telegramer/include/pytz/zoneinfo/US/East-Indiana deleted file mode 100644 index 09511cc..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/East-Indiana and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Eastern b/telegramer/include/pytz/zoneinfo/US/Eastern deleted file mode 100644 index 2f75480..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Eastern and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Hawaii b/telegramer/include/pytz/zoneinfo/US/Hawaii deleted file mode 100644 index c7cd060..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Hawaii and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Indiana-Starke b/telegramer/include/pytz/zoneinfo/US/Indiana-Starke deleted file mode 100644 index fcd408d..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Indiana-Starke and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Michigan b/telegramer/include/pytz/zoneinfo/US/Michigan deleted file mode 100644 index e104faa..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Michigan and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Mountain b/telegramer/include/pytz/zoneinfo/US/Mountain deleted file mode 100644 index 5fbe26b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Mountain and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Pacific b/telegramer/include/pytz/zoneinfo/US/Pacific deleted file mode 100644 index 9dad4f4..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Pacific and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/US/Samoa b/telegramer/include/pytz/zoneinfo/US/Samoa deleted file mode 100644 index cb56709..0000000 Binary files a/telegramer/include/pytz/zoneinfo/US/Samoa and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/UTC b/telegramer/include/pytz/zoneinfo/UTC deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/UTC and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Universal b/telegramer/include/pytz/zoneinfo/Universal deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Universal and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/W-SU b/telegramer/include/pytz/zoneinfo/W-SU deleted file mode 100644 index ddb3f4e..0000000 Binary files a/telegramer/include/pytz/zoneinfo/W-SU and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/WET b/telegramer/include/pytz/zoneinfo/WET deleted file mode 100644 index c27390b..0000000 Binary files a/telegramer/include/pytz/zoneinfo/WET and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/Zulu b/telegramer/include/pytz/zoneinfo/Zulu deleted file mode 100644 index 91558be..0000000 Binary files a/telegramer/include/pytz/zoneinfo/Zulu and /dev/null differ diff --git a/telegramer/include/pytz/zoneinfo/iso3166.tab b/telegramer/include/pytz/zoneinfo/iso3166.tab deleted file mode 100644 index a4ff61a..0000000 --- a/telegramer/include/pytz/zoneinfo/iso3166.tab +++ /dev/null @@ -1,274 +0,0 @@ -# ISO 3166 alpha-2 country codes -# -# This file is in the public domain, so clarified as of -# 2009-05-17 by Arthur David Olson. -# -# From Paul Eggert (2015-05-02): -# This file contains a table of two-letter country codes. Columns are -# separated by a single tab. Lines beginning with '#' are comments. -# All text uses UTF-8 encoding. The columns of the table are as follows: -# -# 1. ISO 3166-1 alpha-2 country code, current as of -# ISO 3166-1 N976 (2018-11-06). See: Updates on ISO 3166-1 -# https://isotc.iso.org/livelink/livelink/Open/16944257 -# 2. The usual English name for the coded region, -# chosen so that alphabetic sorting of subsets produces helpful lists. -# This is not the same as the English name in the ISO 3166 tables. -# -# The table is sorted by country code. -# -# This table is intended as an aid for users, to help them select time -# zone data appropriate for their practical needs. It is not intended -# to take or endorse any position on legal or territorial claims. -# -#country- -#code name of country, territory, area, or subdivision -AD Andorra -AE United Arab Emirates -AF Afghanistan -AG Antigua & Barbuda -AI Anguilla -AL Albania -AM Armenia -AO Angola -AQ Antarctica -AR Argentina -AS Samoa (American) -AT Austria -AU Australia -AW Aruba -AX Åland Islands -AZ Azerbaijan -BA Bosnia & Herzegovina -BB Barbados -BD Bangladesh -BE Belgium -BF Burkina Faso -BG Bulgaria -BH Bahrain -BI Burundi -BJ Benin -BL St Barthelemy -BM Bermuda -BN Brunei -BO Bolivia -BQ Caribbean NL -BR Brazil -BS Bahamas -BT Bhutan -BV Bouvet Island -BW Botswana -BY Belarus -BZ Belize -CA Canada -CC Cocos (Keeling) Islands -CD Congo (Dem. Rep.) -CF Central African Rep. -CG Congo (Rep.) -CH Switzerland -CI Côte d'Ivoire -CK Cook Islands -CL Chile -CM Cameroon -CN China -CO Colombia -CR Costa Rica -CU Cuba -CV Cape Verde -CW Curaçao -CX Christmas Island -CY Cyprus -CZ Czech Republic -DE Germany -DJ Djibouti -DK Denmark -DM Dominica -DO Dominican Republic -DZ Algeria -EC Ecuador -EE Estonia -EG Egypt -EH Western Sahara -ER Eritrea -ES Spain -ET Ethiopia -FI Finland -FJ Fiji -FK Falkland Islands -FM Micronesia -FO Faroe Islands -FR France -GA Gabon -GB Britain (UK) -GD Grenada -GE Georgia -GF French Guiana -GG Guernsey -GH Ghana -GI Gibraltar -GL Greenland -GM Gambia -GN Guinea -GP Guadeloupe -GQ Equatorial Guinea -GR Greece -GS South Georgia & the South Sandwich Islands -GT Guatemala -GU Guam -GW Guinea-Bissau -GY Guyana -HK Hong Kong -HM Heard Island & McDonald Islands -HN Honduras -HR Croatia -HT Haiti -HU Hungary -ID Indonesia -IE Ireland -IL Israel -IM Isle of Man -IN India -IO British Indian Ocean Territory -IQ Iraq -IR Iran -IS Iceland -IT Italy -JE Jersey -JM Jamaica -JO Jordan -JP Japan -KE Kenya -KG Kyrgyzstan -KH Cambodia -KI Kiribati -KM Comoros -KN St Kitts & Nevis -KP Korea (North) -KR Korea (South) -KW Kuwait -KY Cayman Islands -KZ Kazakhstan -LA Laos -LB Lebanon -LC St Lucia -LI Liechtenstein -LK Sri Lanka -LR Liberia -LS Lesotho -LT Lithuania -LU Luxembourg -LV Latvia -LY Libya -MA Morocco -MC Monaco -MD Moldova -ME Montenegro -MF St Martin (French) -MG Madagascar -MH Marshall Islands -MK North Macedonia -ML Mali -MM Myanmar (Burma) -MN Mongolia -MO Macau -MP Northern Mariana Islands -MQ Martinique -MR Mauritania -MS Montserrat -MT Malta -MU Mauritius -MV Maldives -MW Malawi -MX Mexico -MY Malaysia -MZ Mozambique -NA Namibia -NC New Caledonia -NE Niger -NF Norfolk Island -NG Nigeria -NI Nicaragua -NL Netherlands -NO Norway -NP Nepal -NR Nauru -NU Niue -NZ New Zealand -OM Oman -PA Panama -PE Peru -PF French Polynesia -PG Papua New Guinea -PH Philippines -PK Pakistan -PL Poland -PM St Pierre & Miquelon -PN Pitcairn -PR Puerto Rico -PS Palestine -PT Portugal -PW Palau -PY Paraguay -QA Qatar -RE Réunion -RO Romania -RS Serbia -RU Russia -RW Rwanda -SA Saudi Arabia -SB Solomon Islands -SC Seychelles -SD Sudan -SE Sweden -SG Singapore -SH St Helena -SI Slovenia -SJ Svalbard & Jan Mayen -SK Slovakia -SL Sierra Leone -SM San Marino -SN Senegal -SO Somalia -SR Suriname -SS South Sudan -ST Sao Tome & Principe -SV El Salvador -SX St Maarten (Dutch) -SY Syria -SZ Eswatini (Swaziland) -TC Turks & Caicos Is -TD Chad -TF French Southern & Antarctic Lands -TG Togo -TH Thailand -TJ Tajikistan -TK Tokelau -TL East Timor -TM Turkmenistan -TN Tunisia -TO Tonga -TR Turkey -TT Trinidad & Tobago -TV Tuvalu -TW Taiwan -TZ Tanzania -UA Ukraine -UG Uganda -UM US minor outlying islands -US United States -UY Uruguay -UZ Uzbekistan -VA Vatican City -VC St Vincent -VE Venezuela -VG Virgin Islands (UK) -VI Virgin Islands (US) -VN Vietnam -VU Vanuatu -WF Wallis & Futuna -WS Samoa (western) -YE Yemen -YT Mayotte -ZA South Africa -ZM Zambia -ZW Zimbabwe diff --git a/telegramer/include/pytz/zoneinfo/leapseconds b/telegramer/include/pytz/zoneinfo/leapseconds deleted file mode 100644 index ffa5eb8..0000000 --- a/telegramer/include/pytz/zoneinfo/leapseconds +++ /dev/null @@ -1,82 +0,0 @@ -# Allowance for leap seconds added to each time zone file. - -# This file is in the public domain. - -# This file is generated automatically from the data in the public-domain -# NIST format leap-seconds.list file, which can be copied from -# -# or . -# The NIST file is used instead of its IERS upstream counterpart -# -# because under US law the NIST file is public domain -# whereas the IERS file's copyright and license status is unclear. -# For more about leap-seconds.list, please see -# The NTP Timescale and Leap Seconds -# . - -# The rules for leap seconds are specified in Annex 1 (Time scales) of: -# Standard-frequency and time-signal emissions. -# International Telecommunication Union - Radiocommunication Sector -# (ITU-R) Recommendation TF.460-6 (02/2002) -# . -# The International Earth Rotation and Reference Systems Service (IERS) -# periodically uses leap seconds to keep UTC to within 0.9 s of UT1 -# (a proxy for Earth's angle in space as measured by astronomers) -# and publishes leap second data in a copyrighted file -# . -# See: Levine J. Coordinated Universal Time and the leap second. -# URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995 -# . - -# There were no leap seconds before 1972, as no official mechanism -# accounted for the discrepancy between atomic time (TAI) and the earth's -# rotation. The first ("1 Jan 1972") data line in leap-seconds.list -# does not denote a leap second; it denotes the start of the current definition -# of UTC. - -# All leap-seconds are Stationary (S) at the given UTC time. -# The correction (+ or -) is made at the given time, so in the unlikely -# event of a negative leap second, a line would look like this: -# Leap YEAR MON DAY 23:59:59 - S -# Typical lines look like this: -# Leap YEAR MON DAY 23:59:60 + S -Leap 1972 Jun 30 23:59:60 + S -Leap 1972 Dec 31 23:59:60 + S -Leap 1973 Dec 31 23:59:60 + S -Leap 1974 Dec 31 23:59:60 + S -Leap 1975 Dec 31 23:59:60 + S -Leap 1976 Dec 31 23:59:60 + S -Leap 1977 Dec 31 23:59:60 + S -Leap 1978 Dec 31 23:59:60 + S -Leap 1979 Dec 31 23:59:60 + S -Leap 1981 Jun 30 23:59:60 + S -Leap 1982 Jun 30 23:59:60 + S -Leap 1983 Jun 30 23:59:60 + S -Leap 1985 Jun 30 23:59:60 + S -Leap 1987 Dec 31 23:59:60 + S -Leap 1989 Dec 31 23:59:60 + S -Leap 1990 Dec 31 23:59:60 + S -Leap 1992 Jun 30 23:59:60 + S -Leap 1993 Jun 30 23:59:60 + S -Leap 1994 Jun 30 23:59:60 + S -Leap 1995 Dec 31 23:59:60 + S -Leap 1997 Jun 30 23:59:60 + S -Leap 1998 Dec 31 23:59:60 + S -Leap 2005 Dec 31 23:59:60 + S -Leap 2008 Dec 31 23:59:60 + S -Leap 2012 Jun 30 23:59:60 + S -Leap 2015 Jun 30 23:59:60 + S -Leap 2016 Dec 31 23:59:60 + S - -# UTC timestamp when this leap second list expires. -# Any additional leap seconds will come after this. -# This Expires line is commented out for now, -# so that pre-2020a zic implementations do not reject this file. -#Expires 2022 Dec 28 00:00:00 - -# POSIX timestamps for the data in this file: -#updated 1467936000 (2016-07-08 00:00:00 UTC) -#expires 1672185600 (2022-12-28 00:00:00 UTC) - -# Updated through IERS Bulletin C63 -# File expires on: 28 December 2022 diff --git a/telegramer/include/pytz/zoneinfo/tzdata.zi b/telegramer/include/pytz/zoneinfo/tzdata.zi deleted file mode 100644 index e21fc92..0000000 --- a/telegramer/include/pytz/zoneinfo/tzdata.zi +++ /dev/null @@ -1,4437 +0,0 @@ -# version unknown-dirty -# This zic input file is in the public domain. -R d 1916 o - Jun 14 23s 1 S -R d 1916 1919 - O Su>=1 23s 0 - -R d 1917 o - Mar 24 23s 1 S -R d 1918 o - Mar 9 23s 1 S -R d 1919 o - Mar 1 23s 1 S -R d 1920 o - F 14 23s 1 S -R d 1920 o - O 23 23s 0 - -R d 1921 o - Mar 14 23s 1 S -R d 1921 o - Jun 21 23s 0 - -R d 1939 o - S 11 23s 1 S -R d 1939 o - N 19 1 0 - -R d 1944 1945 - Ap M>=1 2 1 S -R d 1944 o - O 8 2 0 - -R d 1945 o - S 16 1 0 - -R d 1971 o - Ap 25 23s 1 S -R d 1971 o - S 26 23s 0 - -R d 1977 o - May 6 0 1 S -R d 1977 o - O 21 0 0 - -R d 1978 o - Mar 24 1 1 S -R d 1978 o - S 22 3 0 - -R d 1980 o - Ap 25 0 1 S -R d 1980 o - O 31 2 0 - -Z Africa/Algiers 0:12:12 - LMT 1891 Mar 16 -0:9:21 - PMT 1911 Mar 11 -0 d WE%sT 1940 F 25 2 -1 d CE%sT 1946 O 7 -0 - WET 1956 Ja 29 -1 - CET 1963 Ap 14 -0 d WE%sT 1977 O 21 -1 d CE%sT 1979 O 26 -0 d WE%sT 1981 May -1 - CET -Z Atlantic/Cape_Verde -1:34:4 - LMT 1912 Ja 1 2u --2 - -02 1942 S --2 1 -01 1945 O 15 --2 - -02 1975 N 25 2 --1 - -01 -Z Africa/Ndjamena 1:0:12 - LMT 1912 -1 - WAT 1979 O 14 -1 1 WAST 1980 Mar 8 -1 - WAT -Z Africa/Abidjan -0:16:8 - LMT 1912 -0 - GMT -L Africa/Abidjan Africa/Accra -L Africa/Abidjan Africa/Bamako -L Africa/Abidjan Africa/Banjul -L Africa/Abidjan Africa/Conakry -L Africa/Abidjan Africa/Dakar -L Africa/Abidjan Africa/Freetown -L Africa/Abidjan Africa/Lome -L Africa/Abidjan Africa/Nouakchott -L Africa/Abidjan Africa/Ouagadougou -L Africa/Abidjan Atlantic/St_Helena -R K 1940 o - Jul 15 0 1 S -R K 1940 o - O 1 0 0 - -R K 1941 o - Ap 15 0 1 S -R K 1941 o - S 16 0 0 - -R K 1942 1944 - Ap 1 0 1 S -R K 1942 o - O 27 0 0 - -R K 1943 1945 - N 1 0 0 - -R K 1945 o - Ap 16 0 1 S -R K 1957 o - May 10 0 1 S -R K 1957 1958 - O 1 0 0 - -R K 1958 o - May 1 0 1 S -R K 1959 1981 - May 1 1 1 S -R K 1959 1965 - S 30 3 0 - -R K 1966 1994 - O 1 3 0 - -R K 1982 o - Jul 25 1 1 S -R K 1983 o - Jul 12 1 1 S -R K 1984 1988 - May 1 1 1 S -R K 1989 o - May 6 1 1 S -R K 1990 1994 - May 1 1 1 S -R K 1995 2010 - Ap lastF 0s 1 S -R K 1995 2005 - S lastTh 24 0 - -R K 2006 o - S 21 24 0 - -R K 2007 o - S Th>=1 24 0 - -R K 2008 o - Au lastTh 24 0 - -R K 2009 o - Au 20 24 0 - -R K 2010 o - Au 10 24 0 - -R K 2010 o - S 9 24 1 S -R K 2010 o - S lastTh 24 0 - -R K 2014 o - May 15 24 1 S -R K 2014 o - Jun 26 24 0 - -R K 2014 o - Jul 31 24 1 S -R K 2014 o - S lastTh 24 0 - -Z Africa/Cairo 2:5:9 - LMT 1900 O -2 K EE%sT -Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u --1 - -01 1975 -0 - GMT -Z Africa/Nairobi 2:27:16 - LMT 1908 May -2:30 - +0230 1928 Jun 30 24 -3 - EAT 1930 Ja 4 24 -2:30 - +0230 1936 D 31 24 -2:45 - +0245 1942 Jul 31 24 -3 - EAT -L Africa/Nairobi Africa/Addis_Ababa -L Africa/Nairobi Africa/Asmara -L Africa/Nairobi Africa/Dar_es_Salaam -L Africa/Nairobi Africa/Djibouti -L Africa/Nairobi Africa/Kampala -L Africa/Nairobi Africa/Mogadishu -L Africa/Nairobi Indian/Antananarivo -L Africa/Nairobi Indian/Comoro -L Africa/Nairobi Indian/Mayotte -Z Africa/Monrovia -0:43:8 - LMT 1882 --0:43:8 - MMT 1919 Mar --0:44:30 - MMT 1972 Ja 7 -0 - GMT -R L 1951 o - O 14 2 1 S -R L 1952 o - Ja 1 0 0 - -R L 1953 o - O 9 2 1 S -R L 1954 o - Ja 1 0 0 - -R L 1955 o - S 30 0 1 S -R L 1956 o - Ja 1 0 0 - -R L 1982 1984 - Ap 1 0 1 S -R L 1982 1985 - O 1 0 0 - -R L 1985 o - Ap 6 0 1 S -R L 1986 o - Ap 4 0 1 S -R L 1986 o - O 3 0 0 - -R L 1987 1989 - Ap 1 0 1 S -R L 1987 1989 - O 1 0 0 - -R L 1997 o - Ap 4 0 1 S -R L 1997 o - O 4 0 0 - -R L 2013 o - Mar lastF 1 1 S -R L 2013 o - O lastF 2 0 - -Z Africa/Tripoli 0:52:44 - LMT 1920 -1 L CE%sT 1959 -2 - EET 1982 -1 L CE%sT 1990 May 4 -2 - EET 1996 S 30 -1 L CE%sT 1997 O 4 -2 - EET 2012 N 10 2 -1 L CE%sT 2013 O 25 2 -2 - EET -R MU 1982 o - O 10 0 1 - -R MU 1983 o - Mar 21 0 0 - -R MU 2008 o - O lastSu 2 1 - -R MU 2009 o - Mar lastSu 2 0 - -Z Indian/Mauritius 3:50 - LMT 1907 -4 MU +04/+05 -R M 1939 o - S 12 0 1 - -R M 1939 o - N 19 0 0 - -R M 1940 o - F 25 0 1 - -R M 1945 o - N 18 0 0 - -R M 1950 o - Jun 11 0 1 - -R M 1950 o - O 29 0 0 - -R M 1967 o - Jun 3 12 1 - -R M 1967 o - O 1 0 0 - -R M 1974 o - Jun 24 0 1 - -R M 1974 o - S 1 0 0 - -R M 1976 1977 - May 1 0 1 - -R M 1976 o - Au 1 0 0 - -R M 1977 o - S 28 0 0 - -R M 1978 o - Jun 1 0 1 - -R M 1978 o - Au 4 0 0 - -R M 2008 o - Jun 1 0 1 - -R M 2008 o - S 1 0 0 - -R M 2009 o - Jun 1 0 1 - -R M 2009 o - Au 21 0 0 - -R M 2010 o - May 2 0 1 - -R M 2010 o - Au 8 0 0 - -R M 2011 o - Ap 3 0 1 - -R M 2011 o - Jul 31 0 0 - -R M 2012 2013 - Ap lastSu 2 1 - -R M 2012 o - Jul 20 3 0 - -R M 2012 o - Au 20 2 1 - -R M 2012 o - S 30 3 0 - -R M 2013 o - Jul 7 3 0 - -R M 2013 o - Au 10 2 1 - -R M 2013 2018 - O lastSu 3 0 - -R M 2014 2018 - Mar lastSu 2 1 - -R M 2014 o - Jun 28 3 0 - -R M 2014 o - Au 2 2 1 - -R M 2015 o - Jun 14 3 0 - -R M 2015 o - Jul 19 2 1 - -R M 2016 o - Jun 5 3 0 - -R M 2016 o - Jul 10 2 1 - -R M 2017 o - May 21 3 0 - -R M 2017 o - Jul 2 2 1 - -R M 2018 o - May 13 3 0 - -R M 2018 o - Jun 17 2 1 - -R M 2019 o - May 5 3 -1 - -R M 2019 o - Jun 9 2 0 - -R M 2020 o - Ap 19 3 -1 - -R M 2020 o - May 31 2 0 - -R M 2021 o - Ap 11 3 -1 - -R M 2021 o - May 16 2 0 - -R M 2022 o - Mar 27 3 -1 - -R M 2022 o - May 8 2 0 - -R M 2023 o - Mar 19 3 -1 - -R M 2023 o - Ap 30 2 0 - -R M 2024 o - Mar 10 3 -1 - -R M 2024 o - Ap 14 2 0 - -R M 2025 o - F 23 3 -1 - -R M 2025 o - Ap 6 2 0 - -R M 2026 o - F 15 3 -1 - -R M 2026 o - Mar 22 2 0 - -R M 2027 o - F 7 3 -1 - -R M 2027 o - Mar 14 2 0 - -R M 2028 o - Ja 23 3 -1 - -R M 2028 o - Mar 5 2 0 - -R M 2029 o - Ja 14 3 -1 - -R M 2029 o - F 18 2 0 - -R M 2029 o - D 30 3 -1 - -R M 2030 o - F 10 2 0 - -R M 2030 o - D 22 3 -1 - -R M 2031 o - F 2 2 0 - -R M 2031 o - D 14 3 -1 - -R M 2032 o - Ja 18 2 0 - -R M 2032 o - N 28 3 -1 - -R M 2033 o - Ja 9 2 0 - -R M 2033 o - N 20 3 -1 - -R M 2033 o - D 25 2 0 - -R M 2034 o - N 5 3 -1 - -R M 2034 o - D 17 2 0 - -R M 2035 o - O 28 3 -1 - -R M 2035 o - D 9 2 0 - -R M 2036 o - O 19 3 -1 - -R M 2036 o - N 23 2 0 - -R M 2037 o - O 4 3 -1 - -R M 2037 o - N 15 2 0 - -R M 2038 o - S 26 3 -1 - -R M 2038 o - N 7 2 0 - -R M 2039 o - S 18 3 -1 - -R M 2039 o - O 23 2 0 - -R M 2040 o - S 2 3 -1 - -R M 2040 o - O 14 2 0 - -R M 2041 o - Au 25 3 -1 - -R M 2041 o - S 29 2 0 - -R M 2042 o - Au 10 3 -1 - -R M 2042 o - S 21 2 0 - -R M 2043 o - Au 2 3 -1 - -R M 2043 o - S 13 2 0 - -R M 2044 o - Jul 24 3 -1 - -R M 2044 o - Au 28 2 0 - -R M 2045 o - Jul 9 3 -1 - -R M 2045 o - Au 20 2 0 - -R M 2046 o - Jul 1 3 -1 - -R M 2046 o - Au 12 2 0 - -R M 2047 o - Jun 23 3 -1 - -R M 2047 o - Jul 28 2 0 - -R M 2048 o - Jun 7 3 -1 - -R M 2048 o - Jul 19 2 0 - -R M 2049 o - May 30 3 -1 - -R M 2049 o - Jul 4 2 0 - -R M 2050 o - May 15 3 -1 - -R M 2050 o - Jun 26 2 0 - -R M 2051 o - May 7 3 -1 - -R M 2051 o - Jun 18 2 0 - -R M 2052 o - Ap 28 3 -1 - -R M 2052 o - Jun 2 2 0 - -R M 2053 o - Ap 13 3 -1 - -R M 2053 o - May 25 2 0 - -R M 2054 o - Ap 5 3 -1 - -R M 2054 o - May 17 2 0 - -R M 2055 o - Mar 28 3 -1 - -R M 2055 o - May 2 2 0 - -R M 2056 o - Mar 12 3 -1 - -R M 2056 o - Ap 23 2 0 - -R M 2057 o - Mar 4 3 -1 - -R M 2057 o - Ap 8 2 0 - -R M 2058 o - F 17 3 -1 - -R M 2058 o - Mar 31 2 0 - -R M 2059 o - F 9 3 -1 - -R M 2059 o - Mar 23 2 0 - -R M 2060 o - F 1 3 -1 - -R M 2060 o - Mar 7 2 0 - -R M 2061 o - Ja 16 3 -1 - -R M 2061 o - F 27 2 0 - -R M 2062 o - Ja 8 3 -1 - -R M 2062 o - F 19 2 0 - -R M 2062 o - D 31 3 -1 - -R M 2063 o - F 4 2 0 - -R M 2063 o - D 16 3 -1 - -R M 2064 o - Ja 27 2 0 - -R M 2064 o - D 7 3 -1 - -R M 2065 o - Ja 11 2 0 - -R M 2065 o - N 22 3 -1 - -R M 2066 o - Ja 3 2 0 - -R M 2066 o - N 14 3 -1 - -R M 2066 o - D 26 2 0 - -R M 2067 o - N 6 3 -1 - -R M 2067 o - D 11 2 0 - -R M 2068 o - O 21 3 -1 - -R M 2068 o - D 2 2 0 - -R M 2069 o - O 13 3 -1 - -R M 2069 o - N 24 2 0 - -R M 2070 o - O 5 3 -1 - -R M 2070 o - N 9 2 0 - -R M 2071 o - S 20 3 -1 - -R M 2071 o - N 1 2 0 - -R M 2072 o - S 11 3 -1 - -R M 2072 o - O 16 2 0 - -R M 2073 o - Au 27 3 -1 - -R M 2073 o - O 8 2 0 - -R M 2074 o - Au 19 3 -1 - -R M 2074 o - S 30 2 0 - -R M 2075 o - Au 11 3 -1 - -R M 2075 o - S 15 2 0 - -R M 2076 o - Jul 26 3 -1 - -R M 2076 o - S 6 2 0 - -R M 2077 o - Jul 18 3 -1 - -R M 2077 o - Au 29 2 0 - -R M 2078 o - Jul 10 3 -1 - -R M 2078 o - Au 14 2 0 - -R M 2079 o - Jun 25 3 -1 - -R M 2079 o - Au 6 2 0 - -R M 2080 o - Jun 16 3 -1 - -R M 2080 o - Jul 21 2 0 - -R M 2081 o - Jun 1 3 -1 - -R M 2081 o - Jul 13 2 0 - -R M 2082 o - May 24 3 -1 - -R M 2082 o - Jul 5 2 0 - -R M 2083 o - May 16 3 -1 - -R M 2083 o - Jun 20 2 0 - -R M 2084 o - Ap 30 3 -1 - -R M 2084 o - Jun 11 2 0 - -R M 2085 o - Ap 22 3 -1 - -R M 2085 o - Jun 3 2 0 - -R M 2086 o - Ap 14 3 -1 - -R M 2086 o - May 19 2 0 - -R M 2087 o - Mar 30 3 -1 - -R M 2087 o - May 11 2 0 - -Z Africa/Casablanca -0:30:20 - LMT 1913 O 26 -0 M +00/+01 1984 Mar 16 -1 - +01 1986 -0 M +00/+01 2018 O 28 3 -1 M +01/+00 -Z Africa/El_Aaiun -0:52:48 - LMT 1934 --1 - -01 1976 Ap 14 -0 M +00/+01 2018 O 28 3 -1 M +01/+00 -Z Africa/Maputo 2:10:20 - LMT 1903 Mar -2 - CAT -L Africa/Maputo Africa/Blantyre -L Africa/Maputo Africa/Bujumbura -L Africa/Maputo Africa/Gaborone -L Africa/Maputo Africa/Harare -L Africa/Maputo Africa/Kigali -L Africa/Maputo Africa/Lubumbashi -L Africa/Maputo Africa/Lusaka -R NA 1994 o - Mar 21 0 -1 WAT -R NA 1994 2017 - S Su>=1 2 0 CAT -R NA 1995 2017 - Ap Su>=1 2 -1 WAT -Z Africa/Windhoek 1:8:24 - LMT 1892 F 8 -1:30 - +0130 1903 Mar -2 - SAST 1942 S 20 2 -2 1 SAST 1943 Mar 21 2 -2 - SAST 1990 Mar 21 -2 NA %s -Z Africa/Lagos 0:13:35 - LMT 1905 Jul -0 - GMT 1908 Jul -0:13:35 - LMT 1914 -0:30 - +0030 1919 S -1 - WAT -L Africa/Lagos Africa/Bangui -L Africa/Lagos Africa/Brazzaville -L Africa/Lagos Africa/Douala -L Africa/Lagos Africa/Kinshasa -L Africa/Lagos Africa/Libreville -L Africa/Lagos Africa/Luanda -L Africa/Lagos Africa/Malabo -L Africa/Lagos Africa/Niamey -L Africa/Lagos Africa/Porto-Novo -Z Indian/Reunion 3:41:52 - LMT 1911 Jun -4 - +04 -Z Africa/Sao_Tome 0:26:56 - LMT 1884 --0:36:45 - LMT 1912 Ja 1 0u -0 - GMT 2018 Ja 1 1 -1 - WAT 2019 Ja 1 2 -0 - GMT -Z Indian/Mahe 3:41:48 - LMT 1907 -4 - +04 -R SA 1942 1943 - S Su>=15 2 1 - -R SA 1943 1944 - Mar Su>=15 2 0 - -Z Africa/Johannesburg 1:52 - LMT 1892 F 8 -1:30 - SAST 1903 Mar -2 SA SAST -L Africa/Johannesburg Africa/Maseru -L Africa/Johannesburg Africa/Mbabane -R SD 1970 o - May 1 0 1 S -R SD 1970 1985 - O 15 0 0 - -R SD 1971 o - Ap 30 0 1 S -R SD 1972 1985 - Ap lastSu 0 1 S -Z Africa/Khartoum 2:10:8 - LMT 1931 -2 SD CA%sT 2000 Ja 15 12 -3 - EAT 2017 N -2 - CAT -Z Africa/Juba 2:6:28 - LMT 1931 -2 SD CA%sT 2000 Ja 15 12 -3 - EAT 2021 F -2 - CAT -R n 1939 o - Ap 15 23s 1 S -R n 1939 o - N 18 23s 0 - -R n 1940 o - F 25 23s 1 S -R n 1941 o - O 6 0 0 - -R n 1942 o - Mar 9 0 1 S -R n 1942 o - N 2 3 0 - -R n 1943 o - Mar 29 2 1 S -R n 1943 o - Ap 17 2 0 - -R n 1943 o - Ap 25 2 1 S -R n 1943 o - O 4 2 0 - -R n 1944 1945 - Ap M>=1 2 1 S -R n 1944 o - O 8 0 0 - -R n 1945 o - S 16 0 0 - -R n 1977 o - Ap 30 0s 1 S -R n 1977 o - S 24 0s 0 - -R n 1978 o - May 1 0s 1 S -R n 1978 o - O 1 0s 0 - -R n 1988 o - Jun 1 0s 1 S -R n 1988 1990 - S lastSu 0s 0 - -R n 1989 o - Mar 26 0s 1 S -R n 1990 o - May 1 0s 1 S -R n 2005 o - May 1 0s 1 S -R n 2005 o - S 30 1s 0 - -R n 2006 2008 - Mar lastSu 2s 1 S -R n 2006 2008 - O lastSu 2s 0 - -Z Africa/Tunis 0:40:44 - LMT 1881 May 12 -0:9:21 - PMT 1911 Mar 11 -1 n CE%sT -Z Antarctica/Casey 0 - -00 1969 -8 - +08 2009 O 18 2 -11 - +11 2010 Mar 5 2 -8 - +08 2011 O 28 2 -11 - +11 2012 F 21 17u -8 - +08 2016 O 22 -11 - +11 2018 Mar 11 4 -8 - +08 2018 O 7 4 -11 - +11 2019 Mar 17 3 -8 - +08 2019 O 4 3 -11 - +11 2020 Mar 8 3 -8 - +08 2020 O 4 0:1 -11 - +11 -Z Antarctica/Davis 0 - -00 1957 Ja 13 -7 - +07 1964 N -0 - -00 1969 F -7 - +07 2009 O 18 2 -5 - +05 2010 Mar 10 20u -7 - +07 2011 O 28 2 -5 - +05 2012 F 21 20u -7 - +07 -Z Antarctica/Mawson 0 - -00 1954 F 13 -6 - +06 2009 O 18 2 -5 - +05 -Z Indian/Kerguelen 0 - -00 1950 -5 - +05 -R Tr 2005 ma - Mar lastSu 1u 2 +02 -R Tr 2004 ma - O lastSu 1u 0 +00 -Z Antarctica/Troll 0 - -00 2005 F 12 -0 Tr %s -Z Antarctica/Vostok 0 - -00 1957 D 16 -6 - +06 -Z Antarctica/Rothera 0 - -00 1976 D --3 - -03 -Z Asia/Kabul 4:36:48 - LMT 1890 -4 - +04 1945 -4:30 - +0430 -R AM 2011 o - Mar lastSu 2s 1 - -R AM 2011 o - O lastSu 2s 0 - -Z Asia/Yerevan 2:58 - LMT 1924 May 2 -3 - +03 1957 Mar -4 R +04/+05 1991 Mar 31 2s -3 R +03/+04 1995 S 24 2s -4 - +04 1997 -4 R +04/+05 2011 -4 AM +04/+05 -R AZ 1997 2015 - Mar lastSu 4 1 - -R AZ 1997 2015 - O lastSu 5 0 - -Z Asia/Baku 3:19:24 - LMT 1924 May 2 -3 - +03 1957 Mar -4 R +04/+05 1991 Mar 31 2s -3 R +03/+04 1992 S lastSu 2s -4 - +04 1996 -4 E +04/+05 1997 -4 AZ +04/+05 -R BD 2009 o - Jun 19 23 1 - -R BD 2009 o - D 31 24 0 - -Z Asia/Dhaka 6:1:40 - LMT 1890 -5:53:20 - HMT 1941 O -6:30 - +0630 1942 May 15 -5:30 - +0530 1942 S -6:30 - +0630 1951 S 30 -6 - +06 2009 -6 BD +06/+07 -Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 -5:30 - +0530 1987 O -6 - +06 -Z Indian/Chagos 4:49:40 - LMT 1907 -5 - +05 1996 -6 - +06 -Z Asia/Brunei 7:39:40 - LMT 1926 Mar -7:30 - +0730 1933 -8 - +08 -Z Asia/Yangon 6:24:47 - LMT 1880 -6:24:47 - RMT 1920 -6:30 - +0630 1942 May -9 - +09 1945 May 3 -6:30 - +0630 -R Sh 1919 o - Ap 12 24 1 D -R Sh 1919 o - S 30 24 0 S -R Sh 1940 o - Jun 1 0 1 D -R Sh 1940 o - O 12 24 0 S -R Sh 1941 o - Mar 15 0 1 D -R Sh 1941 o - N 1 24 0 S -R Sh 1942 o - Ja 31 0 1 D -R Sh 1945 o - S 1 24 0 S -R Sh 1946 o - May 15 0 1 D -R Sh 1946 o - S 30 24 0 S -R Sh 1947 o - Ap 15 0 1 D -R Sh 1947 o - O 31 24 0 S -R Sh 1948 1949 - May 1 0 1 D -R Sh 1948 1949 - S 30 24 0 S -R CN 1986 o - May 4 2 1 D -R CN 1986 1991 - S Su>=11 2 0 S -R CN 1987 1991 - Ap Su>=11 2 1 D -Z Asia/Shanghai 8:5:43 - LMT 1901 -8 Sh C%sT 1949 May 28 -8 CN C%sT -Z Asia/Urumqi 5:50:20 - LMT 1928 -6 - +06 -R HK 1946 o - Ap 21 0 1 S -R HK 1946 o - D 1 3:30s 0 - -R HK 1947 o - Ap 13 3:30s 1 S -R HK 1947 o - N 30 3:30s 0 - -R HK 1948 o - May 2 3:30s 1 S -R HK 1948 1952 - O Su>=28 3:30s 0 - -R HK 1949 1953 - Ap Su>=1 3:30 1 S -R HK 1953 1964 - O Su>=31 3:30 0 - -R HK 1954 1964 - Mar Su>=18 3:30 1 S -R HK 1965 1976 - Ap Su>=16 3:30 1 S -R HK 1965 1976 - O Su>=16 3:30 0 - -R HK 1973 o - D 30 3:30 1 S -R HK 1979 o - May 13 3:30 1 S -R HK 1979 o - O 21 3:30 0 - -Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 0:36:42 -8 - HKT 1941 Jun 15 3 -8 1 HKST 1941 O 1 4 -8 0:30 HKWT 1941 D 25 -9 - JST 1945 N 18 2 -8 HK HK%sT -R f 1946 o - May 15 0 1 D -R f 1946 o - O 1 0 0 S -R f 1947 o - Ap 15 0 1 D -R f 1947 o - N 1 0 0 S -R f 1948 1951 - May 1 0 1 D -R f 1948 1951 - O 1 0 0 S -R f 1952 o - Mar 1 0 1 D -R f 1952 1954 - N 1 0 0 S -R f 1953 1959 - Ap 1 0 1 D -R f 1955 1961 - O 1 0 0 S -R f 1960 1961 - Jun 1 0 1 D -R f 1974 1975 - Ap 1 0 1 D -R f 1974 1975 - O 1 0 0 S -R f 1979 o - Jul 1 0 1 D -R f 1979 o - O 1 0 0 S -Z Asia/Taipei 8:6 - LMT 1896 -8 - CST 1937 O -9 - JST 1945 S 21 1 -8 f C%sT -R _ 1942 1943 - Ap 30 23 1 - -R _ 1942 o - N 17 23 0 - -R _ 1943 o - S 30 23 0 S -R _ 1946 o - Ap 30 23s 1 D -R _ 1946 o - S 30 23s 0 S -R _ 1947 o - Ap 19 23s 1 D -R _ 1947 o - N 30 23s 0 S -R _ 1948 o - May 2 23s 1 D -R _ 1948 o - O 31 23s 0 S -R _ 1949 1950 - Ap Sa>=1 23s 1 D -R _ 1949 1950 - O lastSa 23s 0 S -R _ 1951 o - Mar 31 23s 1 D -R _ 1951 o - O 28 23s 0 S -R _ 1952 1953 - Ap Sa>=1 23s 1 D -R _ 1952 o - N 1 23s 0 S -R _ 1953 1954 - O lastSa 23s 0 S -R _ 1954 1956 - Mar Sa>=17 23s 1 D -R _ 1955 o - N 5 23s 0 S -R _ 1956 1964 - N Su>=1 3:30 0 S -R _ 1957 1964 - Mar Su>=18 3:30 1 D -R _ 1965 1973 - Ap Su>=16 3:30 1 D -R _ 1965 1966 - O Su>=16 2:30 0 S -R _ 1967 1976 - O Su>=16 3:30 0 S -R _ 1973 o - D 30 3:30 1 D -R _ 1975 1976 - Ap Su>=16 3:30 1 D -R _ 1979 o - May 13 3:30 1 D -R _ 1979 o - O Su>=16 3:30 0 S -Z Asia/Macau 7:34:10 - LMT 1904 O 30 -8 - CST 1941 D 21 23 -9 _ +09/+10 1945 S 30 24 -8 _ C%sT -R CY 1975 o - Ap 13 0 1 S -R CY 1975 o - O 12 0 0 - -R CY 1976 o - May 15 0 1 S -R CY 1976 o - O 11 0 0 - -R CY 1977 1980 - Ap Su>=1 0 1 S -R CY 1977 o - S 25 0 0 - -R CY 1978 o - O 2 0 0 - -R CY 1979 1997 - S lastSu 0 0 - -R CY 1981 1998 - Mar lastSu 0 1 S -Z Asia/Nicosia 2:13:28 - LMT 1921 N 14 -2 CY EE%sT 1998 S -2 E EE%sT -Z Asia/Famagusta 2:15:48 - LMT 1921 N 14 -2 CY EE%sT 1998 S -2 E EE%sT 2016 S 8 -3 - +03 2017 O 29 1u -2 E EE%sT -L Asia/Nicosia Europe/Nicosia -Z Asia/Tbilisi 2:59:11 - LMT 1880 -2:59:11 - TBMT 1924 May 2 -3 - +03 1957 Mar -4 R +04/+05 1991 Mar 31 2s -3 R +03/+04 1992 -3 e +03/+04 1994 S lastSu -4 e +04/+05 1996 O lastSu -4 1 +05 1997 Mar lastSu -4 e +04/+05 2004 Jun 27 -3 R +03/+04 2005 Mar lastSu 2 -4 - +04 -Z Asia/Dili 8:22:20 - LMT 1912 -8 - +08 1942 F 21 23 -9 - +09 1976 May 3 -8 - +08 2000 S 17 -9 - +09 -Z Asia/Kolkata 5:53:28 - LMT 1854 Jun 28 -5:53:20 - HMT 1870 -5:21:10 - MMT 1906 -5:30 - IST 1941 O -5:30 1 +0630 1942 May 15 -5:30 - IST 1942 S -5:30 1 +0630 1945 O 15 -5:30 - IST -Z Asia/Jakarta 7:7:12 - LMT 1867 Au 10 -7:7:12 - BMT 1923 D 31 23:47:12 -7:20 - +0720 1932 N -7:30 - +0730 1942 Mar 23 -9 - +09 1945 S 23 -7:30 - +0730 1948 May -8 - +08 1950 May -7:30 - +0730 1964 -7 - WIB -Z Asia/Pontianak 7:17:20 - LMT 1908 May -7:17:20 - PMT 1932 N -7:30 - +0730 1942 Ja 29 -9 - +09 1945 S 23 -7:30 - +0730 1948 May -8 - +08 1950 May -7:30 - +0730 1964 -8 - WITA 1988 -7 - WIB -Z Asia/Makassar 7:57:36 - LMT 1920 -7:57:36 - MMT 1932 N -8 - +08 1942 F 9 -9 - +09 1945 S 23 -8 - WITA -Z Asia/Jayapura 9:22:48 - LMT 1932 N -9 - +09 1944 S -9:30 - +0930 1964 -9 - WIT -R i 1978 1980 - Mar 20 24 1 - -R i 1978 o - O 20 24 0 - -R i 1979 o - S 18 24 0 - -R i 1980 o - S 22 24 0 - -R i 1991 o - May 2 24 1 - -R i 1992 1995 - Mar 21 24 1 - -R i 1991 1995 - S 21 24 0 - -R i 1996 o - Mar 20 24 1 - -R i 1996 o - S 20 24 0 - -R i 1997 1999 - Mar 21 24 1 - -R i 1997 1999 - S 21 24 0 - -R i 2000 o - Mar 20 24 1 - -R i 2000 o - S 20 24 0 - -R i 2001 2003 - Mar 21 24 1 - -R i 2001 2003 - S 21 24 0 - -R i 2004 o - Mar 20 24 1 - -R i 2004 o - S 20 24 0 - -R i 2005 o - Mar 21 24 1 - -R i 2005 o - S 21 24 0 - -R i 2008 o - Mar 20 24 1 - -R i 2008 o - S 20 24 0 - -R i 2009 2011 - Mar 21 24 1 - -R i 2009 2011 - S 21 24 0 - -R i 2012 o - Mar 20 24 1 - -R i 2012 o - S 20 24 0 - -R i 2013 2015 - Mar 21 24 1 - -R i 2013 2015 - S 21 24 0 - -R i 2016 o - Mar 20 24 1 - -R i 2016 o - S 20 24 0 - -R i 2017 2019 - Mar 21 24 1 - -R i 2017 2019 - S 21 24 0 - -R i 2020 o - Mar 20 24 1 - -R i 2020 o - S 20 24 0 - -R i 2021 2023 - Mar 21 24 1 - -R i 2021 2023 - S 21 24 0 - -R i 2024 o - Mar 20 24 1 - -R i 2024 o - S 20 24 0 - -R i 2025 2027 - Mar 21 24 1 - -R i 2025 2027 - S 21 24 0 - -R i 2028 2029 - Mar 20 24 1 - -R i 2028 2029 - S 20 24 0 - -R i 2030 2031 - Mar 21 24 1 - -R i 2030 2031 - S 21 24 0 - -R i 2032 2033 - Mar 20 24 1 - -R i 2032 2033 - S 20 24 0 - -R i 2034 2035 - Mar 21 24 1 - -R i 2034 2035 - S 21 24 0 - -R i 2036 2037 - Mar 20 24 1 - -R i 2036 2037 - S 20 24 0 - -R i 2038 2039 - Mar 21 24 1 - -R i 2038 2039 - S 21 24 0 - -R i 2040 2041 - Mar 20 24 1 - -R i 2040 2041 - S 20 24 0 - -R i 2042 2043 - Mar 21 24 1 - -R i 2042 2043 - S 21 24 0 - -R i 2044 2045 - Mar 20 24 1 - -R i 2044 2045 - S 20 24 0 - -R i 2046 2047 - Mar 21 24 1 - -R i 2046 2047 - S 21 24 0 - -R i 2048 2049 - Mar 20 24 1 - -R i 2048 2049 - S 20 24 0 - -R i 2050 2051 - Mar 21 24 1 - -R i 2050 2051 - S 21 24 0 - -R i 2052 2053 - Mar 20 24 1 - -R i 2052 2053 - S 20 24 0 - -R i 2054 2055 - Mar 21 24 1 - -R i 2054 2055 - S 21 24 0 - -R i 2056 2057 - Mar 20 24 1 - -R i 2056 2057 - S 20 24 0 - -R i 2058 2059 - Mar 21 24 1 - -R i 2058 2059 - S 21 24 0 - -R i 2060 2062 - Mar 20 24 1 - -R i 2060 2062 - S 20 24 0 - -R i 2063 o - Mar 21 24 1 - -R i 2063 o - S 21 24 0 - -R i 2064 2066 - Mar 20 24 1 - -R i 2064 2066 - S 20 24 0 - -R i 2067 o - Mar 21 24 1 - -R i 2067 o - S 21 24 0 - -R i 2068 2070 - Mar 20 24 1 - -R i 2068 2070 - S 20 24 0 - -R i 2071 o - Mar 21 24 1 - -R i 2071 o - S 21 24 0 - -R i 2072 2074 - Mar 20 24 1 - -R i 2072 2074 - S 20 24 0 - -R i 2075 o - Mar 21 24 1 - -R i 2075 o - S 21 24 0 - -R i 2076 2078 - Mar 20 24 1 - -R i 2076 2078 - S 20 24 0 - -R i 2079 o - Mar 21 24 1 - -R i 2079 o - S 21 24 0 - -R i 2080 2082 - Mar 20 24 1 - -R i 2080 2082 - S 20 24 0 - -R i 2083 o - Mar 21 24 1 - -R i 2083 o - S 21 24 0 - -R i 2084 2086 - Mar 20 24 1 - -R i 2084 2086 - S 20 24 0 - -R i 2087 o - Mar 21 24 1 - -R i 2087 o - S 21 24 0 - -R i 2088 ma - Mar 20 24 1 - -R i 2088 ma - S 20 24 0 - -Z Asia/Tehran 3:25:44 - LMT 1916 -3:25:44 - TMT 1946 -3:30 - +0330 1977 N -4 i +04/+05 1979 -3:30 i +0330/+0430 -R IQ 1982 o - May 1 0 1 - -R IQ 1982 1984 - O 1 0 0 - -R IQ 1983 o - Mar 31 0 1 - -R IQ 1984 1985 - Ap 1 0 1 - -R IQ 1985 1990 - S lastSu 1s 0 - -R IQ 1986 1990 - Mar lastSu 1s 1 - -R IQ 1991 2007 - Ap 1 3s 1 - -R IQ 1991 2007 - O 1 3s 0 - -Z Asia/Baghdad 2:57:40 - LMT 1890 -2:57:36 - BMT 1918 -3 - +03 1982 May -3 IQ +03/+04 -R Z 1940 o - May 31 24u 1 D -R Z 1940 o - S 30 24u 0 S -R Z 1940 o - N 16 24u 1 D -R Z 1942 1946 - O 31 24u 0 S -R Z 1943 1944 - Mar 31 24u 1 D -R Z 1945 1946 - Ap 15 24u 1 D -R Z 1948 o - May 22 24u 2 DD -R Z 1948 o - Au 31 24u 1 D -R Z 1948 1949 - O 31 24u 0 S -R Z 1949 o - Ap 30 24u 1 D -R Z 1950 o - Ap 15 24u 1 D -R Z 1950 o - S 14 24u 0 S -R Z 1951 o - Mar 31 24u 1 D -R Z 1951 o - N 10 24u 0 S -R Z 1952 o - Ap 19 24u 1 D -R Z 1952 o - O 18 24u 0 S -R Z 1953 o - Ap 11 24u 1 D -R Z 1953 o - S 12 24u 0 S -R Z 1954 o - Jun 12 24u 1 D -R Z 1954 o - S 11 24u 0 S -R Z 1955 o - Jun 11 24u 1 D -R Z 1955 o - S 10 24u 0 S -R Z 1956 o - Jun 2 24u 1 D -R Z 1956 o - S 29 24u 0 S -R Z 1957 o - Ap 27 24u 1 D -R Z 1957 o - S 21 24u 0 S -R Z 1974 o - Jul 6 24 1 D -R Z 1974 o - O 12 24 0 S -R Z 1975 o - Ap 19 24 1 D -R Z 1975 o - Au 30 24 0 S -R Z 1980 o - Au 2 24s 1 D -R Z 1980 o - S 13 24s 0 S -R Z 1984 o - May 5 24s 1 D -R Z 1984 o - Au 25 24s 0 S -R Z 1985 o - Ap 13 24 1 D -R Z 1985 o - Au 31 24 0 S -R Z 1986 o - May 17 24 1 D -R Z 1986 o - S 6 24 0 S -R Z 1987 o - Ap 14 24 1 D -R Z 1987 o - S 12 24 0 S -R Z 1988 o - Ap 9 24 1 D -R Z 1988 o - S 3 24 0 S -R Z 1989 o - Ap 29 24 1 D -R Z 1989 o - S 2 24 0 S -R Z 1990 o - Mar 24 24 1 D -R Z 1990 o - Au 25 24 0 S -R Z 1991 o - Mar 23 24 1 D -R Z 1991 o - Au 31 24 0 S -R Z 1992 o - Mar 28 24 1 D -R Z 1992 o - S 5 24 0 S -R Z 1993 o - Ap 2 0 1 D -R Z 1993 o - S 5 0 0 S -R Z 1994 o - Ap 1 0 1 D -R Z 1994 o - Au 28 0 0 S -R Z 1995 o - Mar 31 0 1 D -R Z 1995 o - S 3 0 0 S -R Z 1996 o - Mar 14 24 1 D -R Z 1996 o - S 15 24 0 S -R Z 1997 o - Mar 20 24 1 D -R Z 1997 o - S 13 24 0 S -R Z 1998 o - Mar 20 0 1 D -R Z 1998 o - S 6 0 0 S -R Z 1999 o - Ap 2 2 1 D -R Z 1999 o - S 3 2 0 S -R Z 2000 o - Ap 14 2 1 D -R Z 2000 o - O 6 1 0 S -R Z 2001 o - Ap 9 1 1 D -R Z 2001 o - S 24 1 0 S -R Z 2002 o - Mar 29 1 1 D -R Z 2002 o - O 7 1 0 S -R Z 2003 o - Mar 28 1 1 D -R Z 2003 o - O 3 1 0 S -R Z 2004 o - Ap 7 1 1 D -R Z 2004 o - S 22 1 0 S -R Z 2005 2012 - Ap F<=1 2 1 D -R Z 2005 o - O 9 2 0 S -R Z 2006 o - O 1 2 0 S -R Z 2007 o - S 16 2 0 S -R Z 2008 o - O 5 2 0 S -R Z 2009 o - S 27 2 0 S -R Z 2010 o - S 12 2 0 S -R Z 2011 o - O 2 2 0 S -R Z 2012 o - S 23 2 0 S -R Z 2013 ma - Mar F>=23 2 1 D -R Z 2013 ma - O lastSu 2 0 S -Z Asia/Jerusalem 2:20:54 - LMT 1880 -2:20:40 - JMT 1918 -2 Z I%sT -R JP 1948 o - May Sa>=1 24 1 D -R JP 1948 1951 - S Sa>=8 25 0 S -R JP 1949 o - Ap Sa>=1 24 1 D -R JP 1950 1951 - May Sa>=1 24 1 D -Z Asia/Tokyo 9:18:59 - LMT 1887 D 31 15u -9 JP J%sT -R J 1973 o - Jun 6 0 1 S -R J 1973 1975 - O 1 0 0 - -R J 1974 1977 - May 1 0 1 S -R J 1976 o - N 1 0 0 - -R J 1977 o - O 1 0 0 - -R J 1978 o - Ap 30 0 1 S -R J 1978 o - S 30 0 0 - -R J 1985 o - Ap 1 0 1 S -R J 1985 o - O 1 0 0 - -R J 1986 1988 - Ap F>=1 0 1 S -R J 1986 1990 - O F>=1 0 0 - -R J 1989 o - May 8 0 1 S -R J 1990 o - Ap 27 0 1 S -R J 1991 o - Ap 17 0 1 S -R J 1991 o - S 27 0 0 - -R J 1992 o - Ap 10 0 1 S -R J 1992 1993 - O F>=1 0 0 - -R J 1993 1998 - Ap F>=1 0 1 S -R J 1994 o - S F>=15 0 0 - -R J 1995 1998 - S F>=15 0s 0 - -R J 1999 o - Jul 1 0s 1 S -R J 1999 2002 - S lastF 0s 0 - -R J 2000 2001 - Mar lastTh 0s 1 S -R J 2002 2012 - Mar lastTh 24 1 S -R J 2003 o - O 24 0s 0 - -R J 2004 o - O 15 0s 0 - -R J 2005 o - S lastF 0s 0 - -R J 2006 2011 - O lastF 0s 0 - -R J 2013 o - D 20 0 0 - -R J 2014 2021 - Mar lastTh 24 1 S -R J 2014 ma - O lastF 0s 0 - -R J 2022 ma - F lastTh 24 1 S -Z Asia/Amman 2:23:44 - LMT 1931 -2 J EE%sT -Z Asia/Almaty 5:7:48 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 R +05/+06 1992 Ja 19 2s -6 R +06/+07 2004 O 31 2s -6 - +06 -Z Asia/Qyzylorda 4:21:52 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1991 S 29 2s -5 R +05/+06 1992 Ja 19 2s -6 R +06/+07 1992 Mar 29 2s -5 R +05/+06 2004 O 31 2s -6 - +06 2018 D 21 -5 - +05 -Z Asia/Qostanay 4:14:28 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 2004 O 31 2s -6 - +06 -Z Asia/Aqtobe 3:48:40 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 2004 O 31 2s -5 - +05 -Z Asia/Aqtau 3:21:4 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 1994 S 25 2s -4 R +04/+05 2004 O 31 2s -5 - +05 -Z Asia/Atyrau 3:27:44 - LMT 1924 May 2 -3 - +03 1930 Jun 21 -5 - +05 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 1999 Mar 28 2s -4 R +04/+05 2004 O 31 2s -5 - +05 -Z Asia/Oral 3:25:24 - LMT 1924 May 2 -3 - +03 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1989 Mar 26 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 1992 Mar 29 2s -4 R +04/+05 2004 O 31 2s -5 - +05 -R KG 1992 1996 - Ap Su>=7 0s 1 - -R KG 1992 1996 - S lastSu 0 0 - -R KG 1997 2005 - Mar lastSu 2:30 1 - -R KG 1997 2004 - O lastSu 2:30 0 - -Z Asia/Bishkek 4:58:24 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 R +05/+06 1991 Au 31 2 -5 KG +05/+06 2005 Au 12 -6 - +06 -R KR 1948 o - Jun 1 0 1 D -R KR 1948 o - S 12 24 0 S -R KR 1949 o - Ap 3 0 1 D -R KR 1949 1951 - S Sa>=7 24 0 S -R KR 1950 o - Ap 1 0 1 D -R KR 1951 o - May 6 0 1 D -R KR 1955 o - May 5 0 1 D -R KR 1955 o - S 8 24 0 S -R KR 1956 o - May 20 0 1 D -R KR 1956 o - S 29 24 0 S -R KR 1957 1960 - May Su>=1 0 1 D -R KR 1957 1960 - S Sa>=17 24 0 S -R KR 1987 1988 - May Su>=8 2 1 D -R KR 1987 1988 - O Su>=8 3 0 S -Z Asia/Seoul 8:27:52 - LMT 1908 Ap -8:30 - KST 1912 -9 - JST 1945 S 8 -9 KR K%sT 1954 Mar 21 -8:30 KR K%sT 1961 Au 10 -9 KR K%sT -Z Asia/Pyongyang 8:23 - LMT 1908 Ap -8:30 - KST 1912 -9 - JST 1945 Au 24 -9 - KST 2015 Au 15 -8:30 - KST 2018 May 4 23:30 -9 - KST -R l 1920 o - Mar 28 0 1 S -R l 1920 o - O 25 0 0 - -R l 1921 o - Ap 3 0 1 S -R l 1921 o - O 3 0 0 - -R l 1922 o - Mar 26 0 1 S -R l 1922 o - O 8 0 0 - -R l 1923 o - Ap 22 0 1 S -R l 1923 o - S 16 0 0 - -R l 1957 1961 - May 1 0 1 S -R l 1957 1961 - O 1 0 0 - -R l 1972 o - Jun 22 0 1 S -R l 1972 1977 - O 1 0 0 - -R l 1973 1977 - May 1 0 1 S -R l 1978 o - Ap 30 0 1 S -R l 1978 o - S 30 0 0 - -R l 1984 1987 - May 1 0 1 S -R l 1984 1991 - O 16 0 0 - -R l 1988 o - Jun 1 0 1 S -R l 1989 o - May 10 0 1 S -R l 1990 1992 - May 1 0 1 S -R l 1992 o - O 4 0 0 - -R l 1993 ma - Mar lastSu 0 1 S -R l 1993 1998 - S lastSu 0 0 - -R l 1999 ma - O lastSu 0 0 - -Z Asia/Beirut 2:22 - LMT 1880 -2 l EE%sT -R NB 1935 1941 - S 14 0 0:20 - -R NB 1935 1941 - D 14 0 0 - -Z Asia/Kuala_Lumpur 6:46:46 - LMT 1901 -6:55:25 - SMT 1905 Jun -7 - +07 1933 -7 0:20 +0720 1936 -7:20 - +0720 1941 S -7:30 - +0730 1942 F 16 -9 - +09 1945 S 12 -7:30 - +0730 1982 -8 - +08 -Z Asia/Kuching 7:21:20 - LMT 1926 Mar -7:30 - +0730 1933 -8 NB +08/+0820 1942 F 16 -9 - +09 1945 S 12 -8 - +08 -Z Indian/Maldives 4:54 - LMT 1880 -4:54 - MMT 1960 -5 - +05 -R X 1983 1984 - Ap 1 0 1 - -R X 1983 o - O 1 0 0 - -R X 1985 1998 - Mar lastSu 0 1 - -R X 1984 1998 - S lastSu 0 0 - -R X 2001 o - Ap lastSa 2 1 - -R X 2001 2006 - S lastSa 2 0 - -R X 2002 2006 - Mar lastSa 2 1 - -R X 2015 2016 - Mar lastSa 2 1 - -R X 2015 2016 - S lastSa 0 0 - -Z Asia/Hovd 6:6:36 - LMT 1905 Au -6 - +06 1978 -7 X +07/+08 -Z Asia/Ulaanbaatar 7:7:32 - LMT 1905 Au -7 - +07 1978 -8 X +08/+09 -Z Asia/Choibalsan 7:38 - LMT 1905 Au -7 - +07 1978 -8 - +08 1983 Ap -9 X +09/+10 2008 Mar 31 -8 X +08/+09 -Z Asia/Kathmandu 5:41:16 - LMT 1920 -5:30 - +0530 1986 -5:45 - +0545 -R PK 2002 o - Ap Su>=2 0 1 S -R PK 2002 o - O Su>=2 0 0 - -R PK 2008 o - Jun 1 0 1 S -R PK 2008 2009 - N 1 0 0 - -R PK 2009 o - Ap 15 0 1 S -Z Asia/Karachi 4:28:12 - LMT 1907 -5:30 - +0530 1942 S -5:30 1 +0630 1945 O 15 -5:30 - +0530 1951 S 30 -5 - +05 1971 Mar 26 -5 PK PK%sT -R P 1999 2005 - Ap F>=15 0 1 S -R P 1999 2003 - O F>=15 0 0 - -R P 2004 o - O 1 1 0 - -R P 2005 o - O 4 2 0 - -R P 2006 2007 - Ap 1 0 1 S -R P 2006 o - S 22 0 0 - -R P 2007 o - S 13 2 0 - -R P 2008 2009 - Mar lastF 0 1 S -R P 2008 o - S 1 0 0 - -R P 2009 o - S 4 1 0 - -R P 2010 o - Mar 26 0 1 S -R P 2010 o - Au 11 0 0 - -R P 2011 o - Ap 1 0:1 1 S -R P 2011 o - Au 1 0 0 - -R P 2011 o - Au 30 0 1 S -R P 2011 o - S 30 0 0 - -R P 2012 2014 - Mar lastTh 24 1 S -R P 2012 o - S 21 1 0 - -R P 2013 o - S 27 0 0 - -R P 2014 o - O 24 0 0 - -R P 2015 o - Mar 28 0 1 S -R P 2015 o - O 23 1 0 - -R P 2016 2018 - Mar Sa>=24 1 1 S -R P 2016 2018 - O Sa>=24 1 0 - -R P 2019 o - Mar 29 0 1 S -R P 2019 o - O Sa>=24 0 0 - -R P 2020 2021 - Mar Sa>=24 0 1 S -R P 2020 o - O 24 1 0 - -R P 2021 ma - O F>=23 1 0 - -R P 2022 ma - Mar Su>=25 0 1 S -Z Asia/Gaza 2:17:52 - LMT 1900 O -2 Z EET/EEST 1948 May 15 -2 K EE%sT 1967 Jun 5 -2 Z I%sT 1996 -2 J EE%sT 1999 -2 P EE%sT 2008 Au 29 -2 - EET 2008 S -2 P EE%sT 2010 -2 - EET 2010 Mar 27 0:1 -2 P EE%sT 2011 Au -2 - EET 2012 -2 P EE%sT -Z Asia/Hebron 2:20:23 - LMT 1900 O -2 Z EET/EEST 1948 May 15 -2 K EE%sT 1967 Jun 5 -2 Z I%sT 1996 -2 J EE%sT 1999 -2 P EE%sT -R PH 1936 o - N 1 0 1 D -R PH 1937 o - F 1 0 0 S -R PH 1954 o - Ap 12 0 1 D -R PH 1954 o - Jul 1 0 0 S -R PH 1978 o - Mar 22 0 1 D -R PH 1978 o - S 21 0 0 S -Z Asia/Manila -15:56 - LMT 1844 D 31 -8:4 - LMT 1899 May 11 -8 PH P%sT 1942 May -9 - JST 1944 N -8 PH P%sT -Z Asia/Qatar 3:26:8 - LMT 1920 -4 - +04 1972 Jun -3 - +03 -L Asia/Qatar Asia/Bahrain -Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 -3 - +03 -L Asia/Riyadh Antarctica/Syowa -L Asia/Riyadh Asia/Aden -L Asia/Riyadh Asia/Kuwait -Z Asia/Singapore 6:55:25 - LMT 1901 -6:55:25 - SMT 1905 Jun -7 - +07 1933 -7 0:20 +0720 1936 -7:20 - +0720 1941 S -7:30 - +0730 1942 F 16 -9 - +09 1945 S 12 -7:30 - +0730 1982 -8 - +08 -Z Asia/Colombo 5:19:24 - LMT 1880 -5:19:32 - MMT 1906 -5:30 - +0530 1942 Ja 5 -5:30 0:30 +06 1942 S -5:30 1 +0630 1945 O 16 2 -5:30 - +0530 1996 May 25 -6:30 - +0630 1996 O 26 0:30 -6 - +06 2006 Ap 15 0:30 -5:30 - +0530 -R S 1920 1923 - Ap Su>=15 2 1 S -R S 1920 1923 - O Su>=1 2 0 - -R S 1962 o - Ap 29 2 1 S -R S 1962 o - O 1 2 0 - -R S 1963 1965 - May 1 2 1 S -R S 1963 o - S 30 2 0 - -R S 1964 o - O 1 2 0 - -R S 1965 o - S 30 2 0 - -R S 1966 o - Ap 24 2 1 S -R S 1966 1976 - O 1 2 0 - -R S 1967 1978 - May 1 2 1 S -R S 1977 1978 - S 1 2 0 - -R S 1983 1984 - Ap 9 2 1 S -R S 1983 1984 - O 1 2 0 - -R S 1986 o - F 16 2 1 S -R S 1986 o - O 9 2 0 - -R S 1987 o - Mar 1 2 1 S -R S 1987 1988 - O 31 2 0 - -R S 1988 o - Mar 15 2 1 S -R S 1989 o - Mar 31 2 1 S -R S 1989 o - O 1 2 0 - -R S 1990 o - Ap 1 2 1 S -R S 1990 o - S 30 2 0 - -R S 1991 o - Ap 1 0 1 S -R S 1991 1992 - O 1 0 0 - -R S 1992 o - Ap 8 0 1 S -R S 1993 o - Mar 26 0 1 S -R S 1993 o - S 25 0 0 - -R S 1994 1996 - Ap 1 0 1 S -R S 1994 2005 - O 1 0 0 - -R S 1997 1998 - Mar lastM 0 1 S -R S 1999 2006 - Ap 1 0 1 S -R S 2006 o - S 22 0 0 - -R S 2007 o - Mar lastF 0 1 S -R S 2007 o - N F>=1 0 0 - -R S 2008 o - Ap F>=1 0 1 S -R S 2008 o - N 1 0 0 - -R S 2009 o - Mar lastF 0 1 S -R S 2010 2011 - Ap F>=1 0 1 S -R S 2012 ma - Mar lastF 0 1 S -R S 2009 ma - O lastF 0 0 - -Z Asia/Damascus 2:25:12 - LMT 1920 -2 S EE%sT -Z Asia/Dushanbe 4:35:12 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 1 +05/+06 1991 S 9 2s -5 - +05 -Z Asia/Bangkok 6:42:4 - LMT 1880 -6:42:4 - BMT 1920 Ap -7 - +07 -L Asia/Bangkok Asia/Phnom_Penh -L Asia/Bangkok Asia/Vientiane -Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 R +05/+06 1991 Mar 31 2 -4 R +04/+05 1992 Ja 19 2 -5 - +05 -Z Asia/Dubai 3:41:12 - LMT 1920 -4 - +04 -L Asia/Dubai Asia/Muscat -Z Asia/Samarkand 4:27:53 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1992 -5 - +05 -Z Asia/Tashkent 4:37:11 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2 -5 R +05/+06 1992 -5 - +05 -Z Asia/Ho_Chi_Minh 7:6:40 - LMT 1906 Jul -7:6:30 - PLMT 1911 May -7 - +07 1942 D 31 23 -8 - +08 1945 Mar 14 23 -9 - +09 1945 S 2 -7 - +07 1947 Ap -8 - +08 1955 Jul -7 - +07 1959 D 31 23 -8 - +08 1975 Jun 13 -7 - +07 -R AU 1917 o - Ja 1 2s 1 D -R AU 1917 o - Mar lastSu 2s 0 S -R AU 1942 o - Ja 1 2s 1 D -R AU 1942 o - Mar lastSu 2s 0 S -R AU 1942 o - S 27 2s 1 D -R AU 1943 1944 - Mar lastSu 2s 0 S -R AU 1943 o - O 3 2s 1 D -Z Australia/Darwin 8:43:20 - LMT 1895 F -9 - ACST 1899 May -9:30 AU AC%sT -R AW 1974 o - O lastSu 2s 1 D -R AW 1975 o - Mar Su>=1 2s 0 S -R AW 1983 o - O lastSu 2s 1 D -R AW 1984 o - Mar Su>=1 2s 0 S -R AW 1991 o - N 17 2s 1 D -R AW 1992 o - Mar Su>=1 2s 0 S -R AW 2006 o - D 3 2s 1 D -R AW 2007 2009 - Mar lastSu 2s 0 S -R AW 2007 2008 - O lastSu 2s 1 D -Z Australia/Perth 7:43:24 - LMT 1895 D -8 AU AW%sT 1943 Jul -8 AW AW%sT -Z Australia/Eucla 8:35:28 - LMT 1895 D -8:45 AU +0845/+0945 1943 Jul -8:45 AW +0845/+0945 -R AQ 1971 o - O lastSu 2s 1 D -R AQ 1972 o - F lastSu 2s 0 S -R AQ 1989 1991 - O lastSu 2s 1 D -R AQ 1990 1992 - Mar Su>=1 2s 0 S -R Ho 1992 1993 - O lastSu 2s 1 D -R Ho 1993 1994 - Mar Su>=1 2s 0 S -Z Australia/Brisbane 10:12:8 - LMT 1895 -10 AU AE%sT 1971 -10 AQ AE%sT -Z Australia/Lindeman 9:55:56 - LMT 1895 -10 AU AE%sT 1971 -10 AQ AE%sT 1992 Jul -10 Ho AE%sT -R AS 1971 1985 - O lastSu 2s 1 D -R AS 1986 o - O 19 2s 1 D -R AS 1987 2007 - O lastSu 2s 1 D -R AS 1972 o - F 27 2s 0 S -R AS 1973 1985 - Mar Su>=1 2s 0 S -R AS 1986 1990 - Mar Su>=15 2s 0 S -R AS 1991 o - Mar 3 2s 0 S -R AS 1992 o - Mar 22 2s 0 S -R AS 1993 o - Mar 7 2s 0 S -R AS 1994 o - Mar 20 2s 0 S -R AS 1995 2005 - Mar lastSu 2s 0 S -R AS 2006 o - Ap 2 2s 0 S -R AS 2007 o - Mar lastSu 2s 0 S -R AS 2008 ma - Ap Su>=1 2s 0 S -R AS 2008 ma - O Su>=1 2s 1 D -Z Australia/Adelaide 9:14:20 - LMT 1895 F -9 - ACST 1899 May -9:30 AU AC%sT 1971 -9:30 AS AC%sT -R AT 1916 o - O Su>=1 2s 1 D -R AT 1917 o - Mar lastSu 2s 0 S -R AT 1917 1918 - O Su>=22 2s 1 D -R AT 1918 1919 - Mar Su>=1 2s 0 S -R AT 1967 o - O Su>=1 2s 1 D -R AT 1968 o - Mar Su>=29 2s 0 S -R AT 1968 1985 - O lastSu 2s 1 D -R AT 1969 1971 - Mar Su>=8 2s 0 S -R AT 1972 o - F lastSu 2s 0 S -R AT 1973 1981 - Mar Su>=1 2s 0 S -R AT 1982 1983 - Mar lastSu 2s 0 S -R AT 1984 1986 - Mar Su>=1 2s 0 S -R AT 1986 o - O Su>=15 2s 1 D -R AT 1987 1990 - Mar Su>=15 2s 0 S -R AT 1987 o - O Su>=22 2s 1 D -R AT 1988 1990 - O lastSu 2s 1 D -R AT 1991 1999 - O Su>=1 2s 1 D -R AT 1991 2005 - Mar lastSu 2s 0 S -R AT 2000 o - Au lastSu 2s 1 D -R AT 2001 ma - O Su>=1 2s 1 D -R AT 2006 o - Ap Su>=1 2s 0 S -R AT 2007 o - Mar lastSu 2s 0 S -R AT 2008 ma - Ap Su>=1 2s 0 S -Z Australia/Hobart 9:49:16 - LMT 1895 S -10 AT AE%sT 1919 O 24 -10 AU AE%sT 1967 -10 AT AE%sT -R AV 1971 1985 - O lastSu 2s 1 D -R AV 1972 o - F lastSu 2s 0 S -R AV 1973 1985 - Mar Su>=1 2s 0 S -R AV 1986 1990 - Mar Su>=15 2s 0 S -R AV 1986 1987 - O Su>=15 2s 1 D -R AV 1988 1999 - O lastSu 2s 1 D -R AV 1991 1994 - Mar Su>=1 2s 0 S -R AV 1995 2005 - Mar lastSu 2s 0 S -R AV 2000 o - Au lastSu 2s 1 D -R AV 2001 2007 - O lastSu 2s 1 D -R AV 2006 o - Ap Su>=1 2s 0 S -R AV 2007 o - Mar lastSu 2s 0 S -R AV 2008 ma - Ap Su>=1 2s 0 S -R AV 2008 ma - O Su>=1 2s 1 D -Z Australia/Melbourne 9:39:52 - LMT 1895 F -10 AU AE%sT 1971 -10 AV AE%sT -R AN 1971 1985 - O lastSu 2s 1 D -R AN 1972 o - F 27 2s 0 S -R AN 1973 1981 - Mar Su>=1 2s 0 S -R AN 1982 o - Ap Su>=1 2s 0 S -R AN 1983 1985 - Mar Su>=1 2s 0 S -R AN 1986 1989 - Mar Su>=15 2s 0 S -R AN 1986 o - O 19 2s 1 D -R AN 1987 1999 - O lastSu 2s 1 D -R AN 1990 1995 - Mar Su>=1 2s 0 S -R AN 1996 2005 - Mar lastSu 2s 0 S -R AN 2000 o - Au lastSu 2s 1 D -R AN 2001 2007 - O lastSu 2s 1 D -R AN 2006 o - Ap Su>=1 2s 0 S -R AN 2007 o - Mar lastSu 2s 0 S -R AN 2008 ma - Ap Su>=1 2s 0 S -R AN 2008 ma - O Su>=1 2s 1 D -Z Australia/Sydney 10:4:52 - LMT 1895 F -10 AU AE%sT 1971 -10 AN AE%sT -Z Australia/Broken_Hill 9:25:48 - LMT 1895 F -10 - AEST 1896 Au 23 -9 - ACST 1899 May -9:30 AU AC%sT 1971 -9:30 AN AC%sT 2000 -9:30 AS AC%sT -R LH 1981 1984 - O lastSu 2 1 - -R LH 1982 1985 - Mar Su>=1 2 0 - -R LH 1985 o - O lastSu 2 0:30 - -R LH 1986 1989 - Mar Su>=15 2 0 - -R LH 1986 o - O 19 2 0:30 - -R LH 1987 1999 - O lastSu 2 0:30 - -R LH 1990 1995 - Mar Su>=1 2 0 - -R LH 1996 2005 - Mar lastSu 2 0 - -R LH 2000 o - Au lastSu 2 0:30 - -R LH 2001 2007 - O lastSu 2 0:30 - -R LH 2006 o - Ap Su>=1 2 0 - -R LH 2007 o - Mar lastSu 2 0 - -R LH 2008 ma - Ap Su>=1 2 0 - -R LH 2008 ma - O Su>=1 2 0:30 - -Z Australia/Lord_Howe 10:36:20 - LMT 1895 F -10 - AEST 1981 Mar -10:30 LH +1030/+1130 1985 Jul -10:30 LH +1030/+11 -Z Antarctica/Macquarie 0 - -00 1899 N -10 - AEST 1916 O 1 2 -10 1 AEDT 1917 F -10 AU AE%sT 1919 Ap 1 0s -0 - -00 1948 Mar 25 -10 AU AE%sT 1967 -10 AT AE%sT 2010 -10 1 AEDT 2011 -10 AT AE%sT -Z Indian/Christmas 7:2:52 - LMT 1895 F -7 - +07 -Z Indian/Cocos 6:27:40 - LMT 1900 -6:30 - +0630 -R FJ 1998 1999 - N Su>=1 2 1 - -R FJ 1999 2000 - F lastSu 3 0 - -R FJ 2009 o - N 29 2 1 - -R FJ 2010 o - Mar lastSu 3 0 - -R FJ 2010 2013 - O Su>=21 2 1 - -R FJ 2011 o - Mar Su>=1 3 0 - -R FJ 2012 2013 - Ja Su>=18 3 0 - -R FJ 2014 o - Ja Su>=18 2 0 - -R FJ 2014 2018 - N Su>=1 2 1 - -R FJ 2015 2021 - Ja Su>=12 3 0 - -R FJ 2019 o - N Su>=8 2 1 - -R FJ 2020 o - D 20 2 1 - -R FJ 2022 ma - N Su>=8 2 1 - -R FJ 2023 ma - Ja Su>=12 3 0 - -Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 -12 FJ +12/+13 -Z Pacific/Gambier -8:59:48 - LMT 1912 O --9 - -09 -Z Pacific/Marquesas -9:18 - LMT 1912 O --9:30 - -0930 -Z Pacific/Tahiti -9:58:16 - LMT 1912 O --10 - -10 -R Gu 1959 o - Jun 27 2 1 D -R Gu 1961 o - Ja 29 2 0 S -R Gu 1967 o - S 1 2 1 D -R Gu 1969 o - Ja 26 0:1 0 S -R Gu 1969 o - Jun 22 2 1 D -R Gu 1969 o - Au 31 2 0 S -R Gu 1970 1971 - Ap lastSu 2 1 D -R Gu 1970 1971 - S Su>=1 2 0 S -R Gu 1973 o - D 16 2 1 D -R Gu 1974 o - F 24 2 0 S -R Gu 1976 o - May 26 2 1 D -R Gu 1976 o - Au 22 2:1 0 S -R Gu 1977 o - Ap 24 2 1 D -R Gu 1977 o - Au 28 2 0 S -Z Pacific/Guam -14:21 - LMT 1844 D 31 -9:39 - LMT 1901 -10 - GST 1941 D 10 -9 - +09 1944 Jul 31 -10 Gu G%sT 2000 D 23 -10 - ChST -L Pacific/Guam Pacific/Saipan -Z Pacific/Tarawa 11:32:4 - LMT 1901 -12 - +12 -Z Pacific/Kanton 0 - -00 1937 Au 31 --12 - -12 1979 O --11 - -11 1994 D 31 -13 - +13 -Z Pacific/Kiritimati -10:29:20 - LMT 1901 --10:40 - -1040 1979 O --10 - -10 1994 D 31 -14 - +14 -Z Pacific/Majuro 11:24:48 - LMT 1901 -11 - +11 1914 O -9 - +09 1919 F -11 - +11 1937 -10 - +10 1941 Ap -9 - +09 1944 Ja 30 -11 - +11 1969 O -12 - +12 -Z Pacific/Kwajalein 11:9:20 - LMT 1901 -11 - +11 1937 -10 - +10 1941 Ap -9 - +09 1944 F 6 -11 - +11 1969 O --12 - -12 1993 Au 20 24 -12 - +12 -Z Pacific/Chuuk -13:52:52 - LMT 1844 D 31 -10:7:8 - LMT 1901 -10 - +10 1914 O -9 - +09 1919 F -10 - +10 1941 Ap -9 - +09 1945 Au -10 - +10 -Z Pacific/Pohnpei -13:27:8 - LMT 1844 D 31 -10:32:52 - LMT 1901 -11 - +11 1914 O -9 - +09 1919 F -11 - +11 1937 -10 - +10 1941 Ap -9 - +09 1945 Au -11 - +11 -Z Pacific/Kosrae -13:8:4 - LMT 1844 D 31 -10:51:56 - LMT 1901 -11 - +11 1914 O -9 - +09 1919 F -11 - +11 1937 -10 - +10 1941 Ap -9 - +09 1945 Au -11 - +11 1969 O -12 - +12 1999 -11 - +11 -Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 -11:30 - +1130 1942 Au 29 -9 - +09 1945 S 8 -11:30 - +1130 1979 F 10 2 -12 - +12 -R NC 1977 1978 - D Su>=1 0 1 - -R NC 1978 1979 - F 27 0 0 - -R NC 1996 o - D 1 2s 1 - -R NC 1997 o - Mar 2 2s 0 - -Z Pacific/Noumea 11:5:48 - LMT 1912 Ja 13 -11 NC +11/+12 -R NZ 1927 o - N 6 2 1 S -R NZ 1928 o - Mar 4 2 0 M -R NZ 1928 1933 - O Su>=8 2 0:30 S -R NZ 1929 1933 - Mar Su>=15 2 0 M -R NZ 1934 1940 - Ap lastSu 2 0 M -R NZ 1934 1940 - S lastSu 2 0:30 S -R NZ 1946 o - Ja 1 0 0 S -R NZ 1974 o - N Su>=1 2s 1 D -R k 1974 o - N Su>=1 2:45s 1 - -R NZ 1975 o - F lastSu 2s 0 S -R k 1975 o - F lastSu 2:45s 0 - -R NZ 1975 1988 - O lastSu 2s 1 D -R k 1975 1988 - O lastSu 2:45s 1 - -R NZ 1976 1989 - Mar Su>=1 2s 0 S -R k 1976 1989 - Mar Su>=1 2:45s 0 - -R NZ 1989 o - O Su>=8 2s 1 D -R k 1989 o - O Su>=8 2:45s 1 - -R NZ 1990 2006 - O Su>=1 2s 1 D -R k 1990 2006 - O Su>=1 2:45s 1 - -R NZ 1990 2007 - Mar Su>=15 2s 0 S -R k 1990 2007 - Mar Su>=15 2:45s 0 - -R NZ 2007 ma - S lastSu 2s 1 D -R k 2007 ma - S lastSu 2:45s 1 - -R NZ 2008 ma - Ap Su>=1 2s 0 S -R k 2008 ma - Ap Su>=1 2:45s 0 - -Z Pacific/Auckland 11:39:4 - LMT 1868 N 2 -11:30 NZ NZ%sT 1946 -12 NZ NZ%sT -Z Pacific/Chatham 12:13:48 - LMT 1868 N 2 -12:15 - +1215 1946 -12:45 k +1245/+1345 -L Pacific/Auckland Antarctica/McMurdo -R CK 1978 o - N 12 0 0:30 - -R CK 1979 1991 - Mar Su>=1 0 0 - -R CK 1979 1990 - O lastSu 0 0:30 - -Z Pacific/Rarotonga 13:20:56 - LMT 1899 D 26 --10:39:4 - LMT 1952 O 16 --10:30 - -1030 1978 N 12 --10 CK -10/-0930 -Z Pacific/Niue -11:19:40 - LMT 1952 O 16 --11:20 - -1120 1964 Jul --11 - -11 -Z Pacific/Norfolk 11:11:52 - LMT 1901 -11:12 - +1112 1951 -11:30 - +1130 1974 O 27 2s -11:30 1 +1230 1975 Mar 2 2s -11:30 - +1130 2015 O 4 2s -11 - +11 2019 Jul -11 AN +11/+12 -Z Pacific/Palau -15:2:4 - LMT 1844 D 31 -8:57:56 - LMT 1901 -9 - +09 -Z Pacific/Port_Moresby 9:48:40 - LMT 1880 -9:48:32 - PMMT 1895 -10 - +10 -L Pacific/Port_Moresby Antarctica/DumontDUrville -Z Pacific/Bougainville 10:22:16 - LMT 1880 -9:48:32 - PMMT 1895 -10 - +10 1942 Jul -9 - +09 1945 Au 21 -10 - +10 2014 D 28 2 -11 - +11 -Z Pacific/Pitcairn -8:40:20 - LMT 1901 --8:30 - -0830 1998 Ap 27 --8 - -08 -Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 --11:22:48 - LMT 1911 --11 - SST -L Pacific/Pago_Pago Pacific/Midway -R WS 2010 o - S lastSu 0 1 - -R WS 2011 o - Ap Sa>=1 4 0 - -R WS 2011 o - S lastSa 3 1 - -R WS 2012 2021 - Ap Su>=1 4 0 - -R WS 2012 2020 - S lastSu 3 1 - -Z Pacific/Apia 12:33:4 - LMT 1892 Jul 5 --11:26:56 - LMT 1911 --11:30 - -1130 1950 --11 WS -11/-10 2011 D 29 24 -13 WS +13/+14 -Z Pacific/Guadalcanal 10:39:48 - LMT 1912 O -11 - +11 -Z Pacific/Fakaofo -11:24:56 - LMT 1901 --11 - -11 2011 D 30 -13 - +13 -R TO 1999 o - O 7 2s 1 - -R TO 2000 o - Mar 19 2s 0 - -R TO 2000 2001 - N Su>=1 2 1 - -R TO 2001 2002 - Ja lastSu 2 0 - -R TO 2016 o - N Su>=1 2 1 - -R TO 2017 o - Ja Su>=15 3 0 - -Z Pacific/Tongatapu 12:19:12 - LMT 1945 S 10 -12:20 - +1220 1961 -13 - +13 1999 -13 TO +13/+14 -Z Pacific/Funafuti 11:56:52 - LMT 1901 -12 - +12 -Z Pacific/Wake 11:6:28 - LMT 1901 -12 - +12 -R VU 1973 o - D 22 12u 1 - -R VU 1974 o - Mar 30 12u 0 - -R VU 1983 1991 - S Sa>=22 24 1 - -R VU 1984 1991 - Mar Sa>=22 24 0 - -R VU 1992 1993 - Ja Sa>=22 24 0 - -R VU 1992 o - O Sa>=22 24 1 - -Z Pacific/Efate 11:13:16 - LMT 1912 Ja 13 -11 VU +11/+12 -Z Pacific/Wallis 12:15:20 - LMT 1901 -12 - +12 -R G 1916 o - May 21 2s 1 BST -R G 1916 o - O 1 2s 0 GMT -R G 1917 o - Ap 8 2s 1 BST -R G 1917 o - S 17 2s 0 GMT -R G 1918 o - Mar 24 2s 1 BST -R G 1918 o - S 30 2s 0 GMT -R G 1919 o - Mar 30 2s 1 BST -R G 1919 o - S 29 2s 0 GMT -R G 1920 o - Mar 28 2s 1 BST -R G 1920 o - O 25 2s 0 GMT -R G 1921 o - Ap 3 2s 1 BST -R G 1921 o - O 3 2s 0 GMT -R G 1922 o - Mar 26 2s 1 BST -R G 1922 o - O 8 2s 0 GMT -R G 1923 o - Ap Su>=16 2s 1 BST -R G 1923 1924 - S Su>=16 2s 0 GMT -R G 1924 o - Ap Su>=9 2s 1 BST -R G 1925 1926 - Ap Su>=16 2s 1 BST -R G 1925 1938 - O Su>=2 2s 0 GMT -R G 1927 o - Ap Su>=9 2s 1 BST -R G 1928 1929 - Ap Su>=16 2s 1 BST -R G 1930 o - Ap Su>=9 2s 1 BST -R G 1931 1932 - Ap Su>=16 2s 1 BST -R G 1933 o - Ap Su>=9 2s 1 BST -R G 1934 o - Ap Su>=16 2s 1 BST -R G 1935 o - Ap Su>=9 2s 1 BST -R G 1936 1937 - Ap Su>=16 2s 1 BST -R G 1938 o - Ap Su>=9 2s 1 BST -R G 1939 o - Ap Su>=16 2s 1 BST -R G 1939 o - N Su>=16 2s 0 GMT -R G 1940 o - F Su>=23 2s 1 BST -R G 1941 o - May Su>=2 1s 2 BDST -R G 1941 1943 - Au Su>=9 1s 1 BST -R G 1942 1944 - Ap Su>=2 1s 2 BDST -R G 1944 o - S Su>=16 1s 1 BST -R G 1945 o - Ap M>=2 1s 2 BDST -R G 1945 o - Jul Su>=9 1s 1 BST -R G 1945 1946 - O Su>=2 2s 0 GMT -R G 1946 o - Ap Su>=9 2s 1 BST -R G 1947 o - Mar 16 2s 1 BST -R G 1947 o - Ap 13 1s 2 BDST -R G 1947 o - Au 10 1s 1 BST -R G 1947 o - N 2 2s 0 GMT -R G 1948 o - Mar 14 2s 1 BST -R G 1948 o - O 31 2s 0 GMT -R G 1949 o - Ap 3 2s 1 BST -R G 1949 o - O 30 2s 0 GMT -R G 1950 1952 - Ap Su>=14 2s 1 BST -R G 1950 1952 - O Su>=21 2s 0 GMT -R G 1953 o - Ap Su>=16 2s 1 BST -R G 1953 1960 - O Su>=2 2s 0 GMT -R G 1954 o - Ap Su>=9 2s 1 BST -R G 1955 1956 - Ap Su>=16 2s 1 BST -R G 1957 o - Ap Su>=9 2s 1 BST -R G 1958 1959 - Ap Su>=16 2s 1 BST -R G 1960 o - Ap Su>=9 2s 1 BST -R G 1961 1963 - Mar lastSu 2s 1 BST -R G 1961 1968 - O Su>=23 2s 0 GMT -R G 1964 1967 - Mar Su>=19 2s 1 BST -R G 1968 o - F 18 2s 1 BST -R G 1972 1980 - Mar Su>=16 2s 1 BST -R G 1972 1980 - O Su>=23 2s 0 GMT -R G 1981 1995 - Mar lastSu 1u 1 BST -R G 1981 1989 - O Su>=23 1u 0 GMT -R G 1990 1995 - O Su>=22 1u 0 GMT -Z Europe/London -0:1:15 - LMT 1847 D 1 0s -0 G %s 1968 O 27 -1 - BST 1971 O 31 2u -0 G %s 1996 -0 E GMT/BST -L Europe/London Europe/Jersey -L Europe/London Europe/Guernsey -L Europe/London Europe/Isle_of_Man -R IE 1971 o - O 31 2u -1 - -R IE 1972 1980 - Mar Su>=16 2u 0 - -R IE 1972 1980 - O Su>=23 2u -1 - -R IE 1981 ma - Mar lastSu 1u 0 - -R IE 1981 1989 - O Su>=23 1u -1 - -R IE 1990 1995 - O Su>=22 1u -1 - -R IE 1996 ma - O lastSu 1u -1 - -Z Europe/Dublin -0:25 - LMT 1880 Au 2 --0:25:21 - DMT 1916 May 21 2s --0:25:21 1 IST 1916 O 1 2s -0 G %s 1921 D 6 -0 G GMT/IST 1940 F 25 2s -0 1 IST 1946 O 6 2s -0 - GMT 1947 Mar 16 2s -0 1 IST 1947 N 2 2s -0 - GMT 1948 Ap 18 2s -0 G GMT/IST 1968 O 27 -1 IE IST/GMT -R E 1977 1980 - Ap Su>=1 1u 1 S -R E 1977 o - S lastSu 1u 0 - -R E 1978 o - O 1 1u 0 - -R E 1979 1995 - S lastSu 1u 0 - -R E 1981 ma - Mar lastSu 1u 1 S -R E 1996 ma - O lastSu 1u 0 - -R W- 1977 1980 - Ap Su>=1 1s 1 S -R W- 1977 o - S lastSu 1s 0 - -R W- 1978 o - O 1 1s 0 - -R W- 1979 1995 - S lastSu 1s 0 - -R W- 1981 ma - Mar lastSu 1s 1 S -R W- 1996 ma - O lastSu 1s 0 - -R c 1916 o - Ap 30 23 1 S -R c 1916 o - O 1 1 0 - -R c 1917 1918 - Ap M>=15 2s 1 S -R c 1917 1918 - S M>=15 2s 0 - -R c 1940 o - Ap 1 2s 1 S -R c 1942 o - N 2 2s 0 - -R c 1943 o - Mar 29 2s 1 S -R c 1943 o - O 4 2s 0 - -R c 1944 1945 - Ap M>=1 2s 1 S -R c 1944 o - O 2 2s 0 - -R c 1945 o - S 16 2s 0 - -R c 1977 1980 - Ap Su>=1 2s 1 S -R c 1977 o - S lastSu 2s 0 - -R c 1978 o - O 1 2s 0 - -R c 1979 1995 - S lastSu 2s 0 - -R c 1981 ma - Mar lastSu 2s 1 S -R c 1996 ma - O lastSu 2s 0 - -R e 1977 1980 - Ap Su>=1 0 1 S -R e 1977 o - S lastSu 0 0 - -R e 1978 o - O 1 0 0 - -R e 1979 1995 - S lastSu 0 0 - -R e 1981 ma - Mar lastSu 0 1 S -R e 1996 ma - O lastSu 0 0 - -R R 1917 o - Jul 1 23 1 MST -R R 1917 o - D 28 0 0 MMT -R R 1918 o - May 31 22 2 MDST -R R 1918 o - S 16 1 1 MST -R R 1919 o - May 31 23 2 MDST -R R 1919 o - Jul 1 0u 1 MSD -R R 1919 o - Au 16 0 0 MSK -R R 1921 o - F 14 23 1 MSD -R R 1921 o - Mar 20 23 2 +05 -R R 1921 o - S 1 0 1 MSD -R R 1921 o - O 1 0 0 - -R R 1981 1984 - Ap 1 0 1 S -R R 1981 1983 - O 1 0 0 - -R R 1984 1995 - S lastSu 2s 0 - -R R 1985 2010 - Mar lastSu 2s 1 S -R R 1996 2010 - O lastSu 2s 0 - -Z WET 0 E WE%sT -Z CET 1 c CE%sT -Z MET 1 c ME%sT -Z EET 2 E EE%sT -R q 1940 o - Jun 16 0 1 S -R q 1942 o - N 2 3 0 - -R q 1943 o - Mar 29 2 1 S -R q 1943 o - Ap 10 3 0 - -R q 1974 o - May 4 0 1 S -R q 1974 o - O 2 0 0 - -R q 1975 o - May 1 0 1 S -R q 1975 o - O 2 0 0 - -R q 1976 o - May 2 0 1 S -R q 1976 o - O 3 0 0 - -R q 1977 o - May 8 0 1 S -R q 1977 o - O 2 0 0 - -R q 1978 o - May 6 0 1 S -R q 1978 o - O 1 0 0 - -R q 1979 o - May 5 0 1 S -R q 1979 o - S 30 0 0 - -R q 1980 o - May 3 0 1 S -R q 1980 o - O 4 0 0 - -R q 1981 o - Ap 26 0 1 S -R q 1981 o - S 27 0 0 - -R q 1982 o - May 2 0 1 S -R q 1982 o - O 3 0 0 - -R q 1983 o - Ap 18 0 1 S -R q 1983 o - O 1 0 0 - -R q 1984 o - Ap 1 0 1 S -Z Europe/Tirane 1:19:20 - LMT 1914 -1 - CET 1940 Jun 16 -1 q CE%sT 1984 Jul -1 E CE%sT -Z Europe/Andorra 0:6:4 - LMT 1901 -0 - WET 1946 S 30 -1 - CET 1985 Mar 31 2 -1 E CE%sT -R a 1920 o - Ap 5 2s 1 S -R a 1920 o - S 13 2s 0 - -R a 1946 o - Ap 14 2s 1 S -R a 1946 o - O 7 2s 0 - -R a 1947 1948 - O Su>=1 2s 0 - -R a 1947 o - Ap 6 2s 1 S -R a 1948 o - Ap 18 2s 1 S -R a 1980 o - Ap 6 0 1 S -R a 1980 o - S 28 0 0 - -Z Europe/Vienna 1:5:21 - LMT 1893 Ap -1 c CE%sT 1920 -1 a CE%sT 1940 Ap 1 2s -1 c CE%sT 1945 Ap 2 2s -1 1 CEST 1945 Ap 12 2s -1 - CET 1946 -1 a CE%sT 1981 -1 E CE%sT -Z Europe/Minsk 1:50:16 - LMT 1880 -1:50 - MMT 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 Jun 28 -1 c CE%sT 1944 Jul 3 -3 R MSK/MSD 1990 -3 - MSK 1991 Mar 31 2s -2 R EE%sT 2011 Mar 27 2s -3 - +03 -R b 1918 o - Mar 9 0s 1 S -R b 1918 1919 - O Sa>=1 23s 0 - -R b 1919 o - Mar 1 23s 1 S -R b 1920 o - F 14 23s 1 S -R b 1920 o - O 23 23s 0 - -R b 1921 o - Mar 14 23s 1 S -R b 1921 o - O 25 23s 0 - -R b 1922 o - Mar 25 23s 1 S -R b 1922 1927 - O Sa>=1 23s 0 - -R b 1923 o - Ap 21 23s 1 S -R b 1924 o - Mar 29 23s 1 S -R b 1925 o - Ap 4 23s 1 S -R b 1926 o - Ap 17 23s 1 S -R b 1927 o - Ap 9 23s 1 S -R b 1928 o - Ap 14 23s 1 S -R b 1928 1938 - O Su>=2 2s 0 - -R b 1929 o - Ap 21 2s 1 S -R b 1930 o - Ap 13 2s 1 S -R b 1931 o - Ap 19 2s 1 S -R b 1932 o - Ap 3 2s 1 S -R b 1933 o - Mar 26 2s 1 S -R b 1934 o - Ap 8 2s 1 S -R b 1935 o - Mar 31 2s 1 S -R b 1936 o - Ap 19 2s 1 S -R b 1937 o - Ap 4 2s 1 S -R b 1938 o - Mar 27 2s 1 S -R b 1939 o - Ap 16 2s 1 S -R b 1939 o - N 19 2s 0 - -R b 1940 o - F 25 2s 1 S -R b 1944 o - S 17 2s 0 - -R b 1945 o - Ap 2 2s 1 S -R b 1945 o - S 16 2s 0 - -R b 1946 o - May 19 2s 1 S -R b 1946 o - O 7 2s 0 - -Z Europe/Brussels 0:17:30 - LMT 1880 -0:17:30 - BMT 1892 May 1 0:17:30 -0 - WET 1914 N 8 -1 - CET 1916 May -1 c CE%sT 1918 N 11 11u -0 b WE%sT 1940 May 20 2s -1 c CE%sT 1944 S 3 -1 b CE%sT 1977 -1 E CE%sT -R BG 1979 o - Mar 31 23 1 S -R BG 1979 o - O 1 1 0 - -R BG 1980 1982 - Ap Sa>=1 23 1 S -R BG 1980 o - S 29 1 0 - -R BG 1981 o - S 27 2 0 - -Z Europe/Sofia 1:33:16 - LMT 1880 -1:56:56 - IMT 1894 N 30 -2 - EET 1942 N 2 3 -1 c CE%sT 1945 -1 - CET 1945 Ap 2 3 -2 - EET 1979 Mar 31 23 -2 BG EE%sT 1982 S 26 3 -2 c EE%sT 1991 -2 e EE%sT 1997 -2 E EE%sT -R CZ 1945 o - Ap M>=1 2s 1 S -R CZ 1945 o - O 1 2s 0 - -R CZ 1946 o - May 6 2s 1 S -R CZ 1946 1949 - O Su>=1 2s 0 - -R CZ 1947 1948 - Ap Su>=15 2s 1 S -R CZ 1949 o - Ap 9 2s 1 S -Z Europe/Prague 0:57:44 - LMT 1850 -0:57:44 - PMT 1891 O -1 c CE%sT 1945 May 9 -1 CZ CE%sT 1946 D 1 3 -1 -1 GMT 1947 F 23 2 -1 CZ CE%sT 1979 -1 E CE%sT -R D 1916 o - May 14 23 1 S -R D 1916 o - S 30 23 0 - -R D 1940 o - May 15 0 1 S -R D 1945 o - Ap 2 2s 1 S -R D 1945 o - Au 15 2s 0 - -R D 1946 o - May 1 2s 1 S -R D 1946 o - S 1 2s 0 - -R D 1947 o - May 4 2s 1 S -R D 1947 o - Au 10 2s 0 - -R D 1948 o - May 9 2s 1 S -R D 1948 o - Au 8 2s 0 - -Z Europe/Copenhagen 0:50:20 - LMT 1890 -0:50:20 - CMT 1894 -1 D CE%sT 1942 N 2 2s -1 c CE%sT 1945 Ap 2 2 -1 D CE%sT 1980 -1 E CE%sT -Z Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 -0 - WET 1981 -0 E WE%sT -R Th 1991 1992 - Mar lastSu 2 1 D -R Th 1991 1992 - S lastSu 2 0 S -R Th 1993 2006 - Ap Su>=1 2 1 D -R Th 1993 2006 - O lastSu 2 0 S -R Th 2007 ma - Mar Su>=8 2 1 D -R Th 2007 ma - N Su>=1 2 0 S -Z America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 --3 - -03 1980 Ap 6 2 --3 E -03/-02 1996 -0 - GMT -Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 --2 - -02 1980 Ap 6 2 --2 c -02/-01 1981 Mar 29 --1 E -01/+00 -Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 --3 - -03 1980 Ap 6 2 --3 E -03/-02 -Z America/Thule -4:35:8 - LMT 1916 Jul 28 --4 Th A%sT -Z Europe/Tallinn 1:39 - LMT 1880 -1:39 - TMT 1918 F -1 c CE%sT 1919 Jul -1:39 - TMT 1921 May -2 - EET 1940 Au 6 -3 - MSK 1941 S 15 -1 c CE%sT 1944 S 22 -3 R MSK/MSD 1989 Mar 26 2s -2 1 EEST 1989 S 24 2s -2 c EE%sT 1998 S 22 -2 E EE%sT 1999 O 31 4 -2 - EET 2002 F 21 -2 E EE%sT -R FI 1942 o - Ap 2 24 1 S -R FI 1942 o - O 4 1 0 - -R FI 1981 1982 - Mar lastSu 2 1 S -R FI 1981 1982 - S lastSu 3 0 - -Z Europe/Helsinki 1:39:49 - LMT 1878 May 31 -1:39:49 - HMT 1921 May -2 FI EE%sT 1983 -2 E EE%sT -L Europe/Helsinki Europe/Mariehamn -R F 1916 o - Jun 14 23s 1 S -R F 1916 1919 - O Su>=1 23s 0 - -R F 1917 o - Mar 24 23s 1 S -R F 1918 o - Mar 9 23s 1 S -R F 1919 o - Mar 1 23s 1 S -R F 1920 o - F 14 23s 1 S -R F 1920 o - O 23 23s 0 - -R F 1921 o - Mar 14 23s 1 S -R F 1921 o - O 25 23s 0 - -R F 1922 o - Mar 25 23s 1 S -R F 1922 1938 - O Sa>=1 23s 0 - -R F 1923 o - May 26 23s 1 S -R F 1924 o - Mar 29 23s 1 S -R F 1925 o - Ap 4 23s 1 S -R F 1926 o - Ap 17 23s 1 S -R F 1927 o - Ap 9 23s 1 S -R F 1928 o - Ap 14 23s 1 S -R F 1929 o - Ap 20 23s 1 S -R F 1930 o - Ap 12 23s 1 S -R F 1931 o - Ap 18 23s 1 S -R F 1932 o - Ap 2 23s 1 S -R F 1933 o - Mar 25 23s 1 S -R F 1934 o - Ap 7 23s 1 S -R F 1935 o - Mar 30 23s 1 S -R F 1936 o - Ap 18 23s 1 S -R F 1937 o - Ap 3 23s 1 S -R F 1938 o - Mar 26 23s 1 S -R F 1939 o - Ap 15 23s 1 S -R F 1939 o - N 18 23s 0 - -R F 1940 o - F 25 2 1 S -R F 1941 o - May 5 0 2 M -R F 1941 o - O 6 0 1 S -R F 1942 o - Mar 9 0 2 M -R F 1942 o - N 2 3 1 S -R F 1943 o - Mar 29 2 2 M -R F 1943 o - O 4 3 1 S -R F 1944 o - Ap 3 2 2 M -R F 1944 o - O 8 1 1 S -R F 1945 o - Ap 2 2 2 M -R F 1945 o - S 16 3 0 - -R F 1976 o - Mar 28 1 1 S -R F 1976 o - S 26 1 0 - -Z Europe/Paris 0:9:21 - LMT 1891 Mar 16 -0:9:21 - PMT 1911 Mar 11 -0 F WE%sT 1940 Jun 14 23 -1 c CE%sT 1944 Au 25 -0 F WE%sT 1945 S 16 3 -1 F CE%sT 1977 -1 E CE%sT -R DE 1946 o - Ap 14 2s 1 S -R DE 1946 o - O 7 2s 0 - -R DE 1947 1949 - O Su>=1 2s 0 - -R DE 1947 o - Ap 6 3s 1 S -R DE 1947 o - May 11 2s 2 M -R DE 1947 o - Jun 29 3 1 S -R DE 1948 o - Ap 18 2s 1 S -R DE 1949 o - Ap 10 2s 1 S -R So 1945 o - May 24 2 2 M -R So 1945 o - S 24 3 1 S -R So 1945 o - N 18 2s 0 - -Z Europe/Berlin 0:53:28 - LMT 1893 Ap -1 c CE%sT 1945 May 24 2 -1 So CE%sT 1946 -1 DE CE%sT 1980 -1 E CE%sT -L Europe/Zurich Europe/Busingen -Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0s -0 G %s 1957 Ap 14 2 -1 - CET 1982 -1 E CE%sT -R g 1932 o - Jul 7 0 1 S -R g 1932 o - S 1 0 0 - -R g 1941 o - Ap 7 0 1 S -R g 1942 o - N 2 3 0 - -R g 1943 o - Mar 30 0 1 S -R g 1943 o - O 4 0 0 - -R g 1952 o - Jul 1 0 1 S -R g 1952 o - N 2 0 0 - -R g 1975 o - Ap 12 0s 1 S -R g 1975 o - N 26 0s 0 - -R g 1976 o - Ap 11 2s 1 S -R g 1976 o - O 10 2s 0 - -R g 1977 1978 - Ap Su>=1 2s 1 S -R g 1977 o - S 26 2s 0 - -R g 1978 o - S 24 4 0 - -R g 1979 o - Ap 1 9 1 S -R g 1979 o - S 29 2 0 - -R g 1980 o - Ap 1 0 1 S -R g 1980 o - S 28 0 0 - -Z Europe/Athens 1:34:52 - LMT 1895 S 14 -1:34:52 - AMT 1916 Jul 28 0:1 -2 g EE%sT 1941 Ap 30 -1 g CE%sT 1944 Ap 4 -2 g EE%sT 1981 -2 E EE%sT -R h 1918 1919 - Ap 15 2 1 S -R h 1918 1920 - S M>=15 3 0 - -R h 1920 o - Ap 5 2 1 S -R h 1945 o - May 1 23 1 S -R h 1945 o - N 1 1 0 - -R h 1946 o - Mar 31 2s 1 S -R h 1946 o - O 7 2 0 - -R h 1947 1949 - Ap Su>=4 2s 1 S -R h 1947 1949 - O Su>=1 2s 0 - -R h 1954 o - May 23 0 1 S -R h 1954 o - O 3 0 0 - -R h 1955 o - May 22 2 1 S -R h 1955 o - O 2 3 0 - -R h 1956 1957 - Jun Su>=1 2 1 S -R h 1956 1957 - S lastSu 3 0 - -R h 1980 o - Ap 6 0 1 S -R h 1980 o - S 28 1 0 - -R h 1981 1983 - Mar lastSu 0 1 S -R h 1981 1983 - S lastSu 1 0 - -Z Europe/Budapest 1:16:20 - LMT 1890 N -1 c CE%sT 1918 -1 h CE%sT 1941 Ap 7 23 -1 c CE%sT 1945 -1 h CE%sT 1984 -1 E CE%sT -R w 1917 1919 - F 19 23 1 - -R w 1917 o - O 21 1 0 - -R w 1918 1919 - N 16 1 0 - -R w 1921 o - Mar 19 23 1 - -R w 1921 o - Jun 23 1 0 - -R w 1939 o - Ap 29 23 1 - -R w 1939 o - O 29 2 0 - -R w 1940 o - F 25 2 1 - -R w 1940 1941 - N Su>=2 1s 0 - -R w 1941 1942 - Mar Su>=2 1s 1 - -R w 1943 1946 - Mar Su>=1 1s 1 - -R w 1942 1948 - O Su>=22 1s 0 - -R w 1947 1967 - Ap Su>=1 1s 1 - -R w 1949 o - O 30 1s 0 - -R w 1950 1966 - O Su>=22 1s 0 - -R w 1967 o - O 29 1s 0 - -Z Atlantic/Reykjavik -1:28 - LMT 1908 --1 w -01/+00 1968 Ap 7 1s -0 - GMT -R I 1916 o - Jun 3 24 1 S -R I 1916 1917 - S 30 24 0 - -R I 1917 o - Mar 31 24 1 S -R I 1918 o - Mar 9 24 1 S -R I 1918 o - O 6 24 0 - -R I 1919 o - Mar 1 24 1 S -R I 1919 o - O 4 24 0 - -R I 1920 o - Mar 20 24 1 S -R I 1920 o - S 18 24 0 - -R I 1940 o - Jun 14 24 1 S -R I 1942 o - N 2 2s 0 - -R I 1943 o - Mar 29 2s 1 S -R I 1943 o - O 4 2s 0 - -R I 1944 o - Ap 2 2s 1 S -R I 1944 o - S 17 2s 0 - -R I 1945 o - Ap 2 2 1 S -R I 1945 o - S 15 1 0 - -R I 1946 o - Mar 17 2s 1 S -R I 1946 o - O 6 2s 0 - -R I 1947 o - Mar 16 0s 1 S -R I 1947 o - O 5 0s 0 - -R I 1948 o - F 29 2s 1 S -R I 1948 o - O 3 2s 0 - -R I 1966 1968 - May Su>=22 0s 1 S -R I 1966 o - S 24 24 0 - -R I 1967 1969 - S Su>=22 0s 0 - -R I 1969 o - Jun 1 0s 1 S -R I 1970 o - May 31 0s 1 S -R I 1970 o - S lastSu 0s 0 - -R I 1971 1972 - May Su>=22 0s 1 S -R I 1971 o - S lastSu 0s 0 - -R I 1972 o - O 1 0s 0 - -R I 1973 o - Jun 3 0s 1 S -R I 1973 1974 - S lastSu 0s 0 - -R I 1974 o - May 26 0s 1 S -R I 1975 o - Jun 1 0s 1 S -R I 1975 1977 - S lastSu 0s 0 - -R I 1976 o - May 30 0s 1 S -R I 1977 1979 - May Su>=22 0s 1 S -R I 1978 o - O 1 0s 0 - -R I 1979 o - S 30 0s 0 - -Z Europe/Rome 0:49:56 - LMT 1866 D 12 -0:49:56 - RMT 1893 O 31 23:49:56 -1 I CE%sT 1943 S 10 -1 c CE%sT 1944 Jun 4 -1 I CE%sT 1980 -1 E CE%sT -L Europe/Rome Europe/Vatican -L Europe/Rome Europe/San_Marino -R LV 1989 1996 - Mar lastSu 2s 1 S -R LV 1989 1996 - S lastSu 2s 0 - -Z Europe/Riga 1:36:34 - LMT 1880 -1:36:34 - RMT 1918 Ap 15 2 -1:36:34 1 LST 1918 S 16 3 -1:36:34 - RMT 1919 Ap 1 2 -1:36:34 1 LST 1919 May 22 3 -1:36:34 - RMT 1926 May 11 -2 - EET 1940 Au 5 -3 - MSK 1941 Jul -1 c CE%sT 1944 O 13 -3 R MSK/MSD 1989 Mar lastSu 2s -2 1 EEST 1989 S lastSu 2s -2 LV EE%sT 1997 Ja 21 -2 E EE%sT 2000 F 29 -2 - EET 2001 Ja 2 -2 E EE%sT -L Europe/Zurich Europe/Vaduz -Z Europe/Vilnius 1:41:16 - LMT 1880 -1:24 - WMT 1917 -1:35:36 - KMT 1919 O 10 -1 - CET 1920 Jul 12 -2 - EET 1920 O 9 -1 - CET 1940 Au 3 -3 - MSK 1941 Jun 24 -1 c CE%sT 1944 Au -3 R MSK/MSD 1989 Mar 26 2s -2 R EE%sT 1991 S 29 2s -2 c EE%sT 1998 -2 - EET 1998 Mar 29 1u -1 E CE%sT 1999 O 31 1u -2 - EET 2003 -2 E EE%sT -R LX 1916 o - May 14 23 1 S -R LX 1916 o - O 1 1 0 - -R LX 1917 o - Ap 28 23 1 S -R LX 1917 o - S 17 1 0 - -R LX 1918 o - Ap M>=15 2s 1 S -R LX 1918 o - S M>=15 2s 0 - -R LX 1919 o - Mar 1 23 1 S -R LX 1919 o - O 5 3 0 - -R LX 1920 o - F 14 23 1 S -R LX 1920 o - O 24 2 0 - -R LX 1921 o - Mar 14 23 1 S -R LX 1921 o - O 26 2 0 - -R LX 1922 o - Mar 25 23 1 S -R LX 1922 o - O Su>=2 1 0 - -R LX 1923 o - Ap 21 23 1 S -R LX 1923 o - O Su>=2 2 0 - -R LX 1924 o - Mar 29 23 1 S -R LX 1924 1928 - O Su>=2 1 0 - -R LX 1925 o - Ap 5 23 1 S -R LX 1926 o - Ap 17 23 1 S -R LX 1927 o - Ap 9 23 1 S -R LX 1928 o - Ap 14 23 1 S -R LX 1929 o - Ap 20 23 1 S -Z Europe/Luxembourg 0:24:36 - LMT 1904 Jun -1 LX CE%sT 1918 N 25 -0 LX WE%sT 1929 O 6 2s -0 b WE%sT 1940 May 14 3 -1 c WE%sT 1944 S 18 3 -1 b CE%sT 1977 -1 E CE%sT -R MT 1973 o - Mar 31 0s 1 S -R MT 1973 o - S 29 0s 0 - -R MT 1974 o - Ap 21 0s 1 S -R MT 1974 o - S 16 0s 0 - -R MT 1975 1979 - Ap Su>=15 2 1 S -R MT 1975 1980 - S Su>=15 2 0 - -R MT 1980 o - Mar 31 2 1 S -Z Europe/Malta 0:58:4 - LMT 1893 N 2 0s -1 I CE%sT 1973 Mar 31 -1 MT CE%sT 1981 -1 E CE%sT -R MD 1997 ma - Mar lastSu 2 1 S -R MD 1997 ma - O lastSu 3 0 - -Z Europe/Chisinau 1:55:20 - LMT 1880 -1:55 - CMT 1918 F 15 -1:44:24 - BMT 1931 Jul 24 -2 z EE%sT 1940 Au 15 -2 1 EEST 1941 Jul 17 -1 c CE%sT 1944 Au 24 -3 R MSK/MSD 1990 May 6 2 -2 R EE%sT 1992 -2 e EE%sT 1997 -2 MD EE%sT -Z Europe/Monaco 0:29:32 - LMT 1892 Jun -0:9:21 - PMT 1911 Mar 29 -0 F WE%sT 1945 S 16 3 -1 F CE%sT 1977 -1 E CE%sT -R N 1916 o - May 1 0 1 NST -R N 1916 o - O 1 0 0 AMT -R N 1917 o - Ap 16 2s 1 NST -R N 1917 o - S 17 2s 0 AMT -R N 1918 1921 - Ap M>=1 2s 1 NST -R N 1918 1921 - S lastM 2s 0 AMT -R N 1922 o - Mar lastSu 2s 1 NST -R N 1922 1936 - O Su>=2 2s 0 AMT -R N 1923 o - Jun F>=1 2s 1 NST -R N 1924 o - Mar lastSu 2s 1 NST -R N 1925 o - Jun F>=1 2s 1 NST -R N 1926 1931 - May 15 2s 1 NST -R N 1932 o - May 22 2s 1 NST -R N 1933 1936 - May 15 2s 1 NST -R N 1937 o - May 22 2s 1 NST -R N 1937 o - Jul 1 0 1 S -R N 1937 1939 - O Su>=2 2s 0 - -R N 1938 1939 - May 15 2s 1 S -R N 1945 o - Ap 2 2s 1 S -R N 1945 o - S 16 2s 0 - -Z Europe/Amsterdam 0:19:32 - LMT 1835 -0:19:32 N %s 1937 Jul -0:20 N +0020/+0120 1940 May 16 -1 c CE%sT 1945 Ap 2 2 -1 N CE%sT 1977 -1 E CE%sT -R NO 1916 o - May 22 1 1 S -R NO 1916 o - S 30 0 0 - -R NO 1945 o - Ap 2 2s 1 S -R NO 1945 o - O 1 2s 0 - -R NO 1959 1964 - Mar Su>=15 2s 1 S -R NO 1959 1965 - S Su>=15 2s 0 - -R NO 1965 o - Ap 25 2s 1 S -Z Europe/Oslo 0:43 - LMT 1895 -1 NO CE%sT 1940 Au 10 23 -1 c CE%sT 1945 Ap 2 2 -1 NO CE%sT 1980 -1 E CE%sT -L Europe/Oslo Arctic/Longyearbyen -R O 1918 1919 - S 16 2s 0 - -R O 1919 o - Ap 15 2s 1 S -R O 1944 o - Ap 3 2s 1 S -R O 1944 o - O 4 2 0 - -R O 1945 o - Ap 29 0 1 S -R O 1945 o - N 1 0 0 - -R O 1946 o - Ap 14 0s 1 S -R O 1946 o - O 7 2s 0 - -R O 1947 o - May 4 2s 1 S -R O 1947 1949 - O Su>=1 2s 0 - -R O 1948 o - Ap 18 2s 1 S -R O 1949 o - Ap 10 2s 1 S -R O 1957 o - Jun 2 1s 1 S -R O 1957 1958 - S lastSu 1s 0 - -R O 1958 o - Mar 30 1s 1 S -R O 1959 o - May 31 1s 1 S -R O 1959 1961 - O Su>=1 1s 0 - -R O 1960 o - Ap 3 1s 1 S -R O 1961 1964 - May lastSu 1s 1 S -R O 1962 1964 - S lastSu 1s 0 - -Z Europe/Warsaw 1:24 - LMT 1880 -1:24 - WMT 1915 Au 5 -1 c CE%sT 1918 S 16 3 -2 O EE%sT 1922 Jun -1 O CE%sT 1940 Jun 23 2 -1 c CE%sT 1944 O -1 O CE%sT 1977 -1 W- CE%sT 1988 -1 E CE%sT -R p 1916 o - Jun 17 23 1 S -R p 1916 o - N 1 1 0 - -R p 1917 o - F 28 23s 1 S -R p 1917 1921 - O 14 23s 0 - -R p 1918 o - Mar 1 23s 1 S -R p 1919 o - F 28 23s 1 S -R p 1920 o - F 29 23s 1 S -R p 1921 o - F 28 23s 1 S -R p 1924 o - Ap 16 23s 1 S -R p 1924 o - O 14 23s 0 - -R p 1926 o - Ap 17 23s 1 S -R p 1926 1929 - O Sa>=1 23s 0 - -R p 1927 o - Ap 9 23s 1 S -R p 1928 o - Ap 14 23s 1 S -R p 1929 o - Ap 20 23s 1 S -R p 1931 o - Ap 18 23s 1 S -R p 1931 1932 - O Sa>=1 23s 0 - -R p 1932 o - Ap 2 23s 1 S -R p 1934 o - Ap 7 23s 1 S -R p 1934 1938 - O Sa>=1 23s 0 - -R p 1935 o - Mar 30 23s 1 S -R p 1936 o - Ap 18 23s 1 S -R p 1937 o - Ap 3 23s 1 S -R p 1938 o - Mar 26 23s 1 S -R p 1939 o - Ap 15 23s 1 S -R p 1939 o - N 18 23s 0 - -R p 1940 o - F 24 23s 1 S -R p 1940 1941 - O 5 23s 0 - -R p 1941 o - Ap 5 23s 1 S -R p 1942 1945 - Mar Sa>=8 23s 1 S -R p 1942 o - Ap 25 22s 2 M -R p 1942 o - Au 15 22s 1 S -R p 1942 1945 - O Sa>=24 23s 0 - -R p 1943 o - Ap 17 22s 2 M -R p 1943 1945 - Au Sa>=25 22s 1 S -R p 1944 1945 - Ap Sa>=21 22s 2 M -R p 1946 o - Ap Sa>=1 23s 1 S -R p 1946 o - O Sa>=1 23s 0 - -R p 1947 1965 - Ap Su>=1 2s 1 S -R p 1947 1965 - O Su>=1 2s 0 - -R p 1977 o - Mar 27 0s 1 S -R p 1977 o - S 25 0s 0 - -R p 1978 1979 - Ap Su>=1 0s 1 S -R p 1978 o - O 1 0s 0 - -R p 1979 1982 - S lastSu 1s 0 - -R p 1980 o - Mar lastSu 0s 1 S -R p 1981 1982 - Mar lastSu 1s 1 S -R p 1983 o - Mar lastSu 2s 1 S -Z Europe/Lisbon -0:36:45 - LMT 1884 --0:36:45 - LMT 1912 Ja 1 0u -0 p WE%sT 1966 Ap 3 2 -1 - CET 1976 S 26 1 -0 p WE%sT 1983 S 25 1s -0 W- WE%sT 1992 S 27 1s -1 E CE%sT 1996 Mar 31 1u -0 E WE%sT -Z Atlantic/Azores -1:42:40 - LMT 1884 --1:54:32 - HMT 1912 Ja 1 2u --2 p -02/-01 1942 Ap 25 22s --2 p +00 1942 Au 15 22s --2 p -02/-01 1943 Ap 17 22s --2 p +00 1943 Au 28 22s --2 p -02/-01 1944 Ap 22 22s --2 p +00 1944 Au 26 22s --2 p -02/-01 1945 Ap 21 22s --2 p +00 1945 Au 25 22s --2 p -02/-01 1966 Ap 3 2 --1 p -01/+00 1983 S 25 1s --1 W- -01/+00 1992 S 27 1s -0 E WE%sT 1993 Mar 28 1u --1 E -01/+00 -Z Atlantic/Madeira -1:7:36 - LMT 1884 --1:7:36 - FMT 1912 Ja 1 1u --1 p -01/+00 1942 Ap 25 22s --1 p +01 1942 Au 15 22s --1 p -01/+00 1943 Ap 17 22s --1 p +01 1943 Au 28 22s --1 p -01/+00 1944 Ap 22 22s --1 p +01 1944 Au 26 22s --1 p -01/+00 1945 Ap 21 22s --1 p +01 1945 Au 25 22s --1 p -01/+00 1966 Ap 3 2 -0 p WE%sT 1983 S 25 1s -0 E WE%sT -R z 1932 o - May 21 0s 1 S -R z 1932 1939 - O Su>=1 0s 0 - -R z 1933 1939 - Ap Su>=2 0s 1 S -R z 1979 o - May 27 0 1 S -R z 1979 o - S lastSu 0 0 - -R z 1980 o - Ap 5 23 1 S -R z 1980 o - S lastSu 1 0 - -R z 1991 1993 - Mar lastSu 0s 1 S -R z 1991 1993 - S lastSu 0s 0 - -Z Europe/Bucharest 1:44:24 - LMT 1891 O -1:44:24 - BMT 1931 Jul 24 -2 z EE%sT 1981 Mar 29 2s -2 c EE%sT 1991 -2 z EE%sT 1994 -2 e EE%sT 1997 -2 E EE%sT -Z Europe/Kaliningrad 1:22 - LMT 1893 Ap -1 c CE%sT 1945 Ap 10 -2 O EE%sT 1946 Ap 7 -3 R MSK/MSD 1989 Mar 26 2s -2 R EE%sT 2011 Mar 27 2s -3 - +03 2014 O 26 2s -2 - EET -Z Europe/Moscow 2:30:17 - LMT 1880 -2:30:17 - MMT 1916 Jul 3 -2:31:19 R %s 1919 Jul 1 0u -3 R %s 1921 O -3 R MSK/MSD 1922 O -2 - EET 1930 Jun 21 -3 R MSK/MSD 1991 Mar 31 2s -2 R EE%sT 1992 Ja 19 2s -3 R MSK/MSD 2011 Mar 27 2s -4 - MSK 2014 O 26 2s -3 - MSK -Z Europe/Simferopol 2:16:24 - LMT 1880 -2:16 - SMT 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 N -1 c CE%sT 1944 Ap 13 -3 R MSK/MSD 1990 -3 - MSK 1990 Jul 1 2 -2 - EET 1992 Mar 20 -2 c EE%sT 1994 May -3 e MSK/MSD 1996 Mar 31 0s -3 1 MSD 1996 O 27 3s -3 R MSK/MSD 1997 -3 - MSK 1997 Mar lastSu 1u -2 E EE%sT 2014 Mar 30 2 -4 - MSK 2014 O 26 2s -3 - MSK -Z Europe/Astrakhan 3:12:12 - LMT 1924 May -3 - +03 1930 Jun 21 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2016 Mar 27 2s -4 - +04 -Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 -3 - +03 1930 Jun 21 -4 - +04 1961 N 11 -4 R +04/+05 1988 Mar 27 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2018 O 28 2s -4 - +04 2020 D 27 2s -3 - +03 -Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 R +04/+05 1988 Mar 27 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2016 D 4 2s -4 - +04 -Z Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 -Z Europe/Samara 3:20:20 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 - +04 1935 Ja 27 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -2 R +02/+03 1991 S 29 2s -3 - +03 1991 O 20 3 -4 R +04/+05 2010 Mar 28 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 -Z Europe/Ulyanovsk 3:13:36 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -2 R +02/+03 1992 Ja 19 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2016 Mar 27 2s -4 - +04 -Z Asia/Yekaterinburg 4:2:33 - LMT 1916 Jul 3 -3:45:5 - PMT 1919 Jul 15 4 -4 - +04 1930 Jun 21 -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 2011 Mar 27 2s -6 - +06 2014 O 26 2s -5 - +05 -Z Asia/Omsk 4:53:30 - LMT 1919 N 14 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 R +05/+06 1992 Ja 19 2s -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 -Z Asia/Barnaul 5:35 - LMT 1919 D 10 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 1995 May 28 -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 2016 Mar 27 2s -7 - +07 -Z Asia/Novosibirsk 5:31:40 - LMT 1919 D 14 6 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 1993 May 23 -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 2016 Jul 24 2s -7 - +07 -Z Asia/Tomsk 5:39:51 - LMT 1919 D 22 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 2002 May 1 3 -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 2016 May 29 2s -7 - +07 -Z Asia/Novokuznetsk 5:48:48 - LMT 1924 May -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 2010 Mar 28 2s -6 R +06/+07 2011 Mar 27 2s -7 - +07 -Z Asia/Krasnoyarsk 6:11:26 - LMT 1920 Ja 6 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 2011 Mar 27 2s -8 - +08 2014 O 26 2s -7 - +07 -Z Asia/Irkutsk 6:57:5 - LMT 1880 -6:57:5 - IMT 1920 Ja 25 -7 - +07 1930 Jun 21 -8 R +08/+09 1991 Mar 31 2s -7 R +07/+08 1992 Ja 19 2s -8 R +08/+09 2011 Mar 27 2s -9 - +09 2014 O 26 2s -8 - +08 -Z Asia/Chita 7:33:52 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1991 Mar 31 2s -8 R +08/+09 1992 Ja 19 2s -9 R +09/+10 2011 Mar 27 2s -10 - +10 2014 O 26 2s -8 - +08 2016 Mar 27 2 -9 - +09 -Z Asia/Yakutsk 8:38:58 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1991 Mar 31 2s -8 R +08/+09 1992 Ja 19 2s -9 R +09/+10 2011 Mar 27 2s -10 - +10 2014 O 26 2s -9 - +09 -Z Asia/Vladivostok 8:47:31 - LMT 1922 N 15 -9 - +09 1930 Jun 21 -10 R +10/+11 1991 Mar 31 2s -9 R +09/+10 1992 Ja 19 2s -10 R +10/+11 2011 Mar 27 2s -11 - +11 2014 O 26 2s -10 - +10 -Z Asia/Khandyga 9:2:13 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1991 Mar 31 2s -8 R +08/+09 1992 Ja 19 2s -9 R +09/+10 2004 -10 R +10/+11 2011 Mar 27 2s -11 - +11 2011 S 13 0s -10 - +10 2014 O 26 2s -9 - +09 -Z Asia/Sakhalin 9:30:48 - LMT 1905 Au 23 -9 - +09 1945 Au 25 -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 1997 Mar lastSu 2s -10 R +10/+11 2011 Mar 27 2s -11 - +11 2014 O 26 2s -10 - +10 2016 Mar 27 2s -11 - +11 -Z Asia/Magadan 10:3:12 - LMT 1924 May 2 -10 - +10 1930 Jun 21 -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 2014 O 26 2s -10 - +10 2016 Ap 24 2s -11 - +11 -Z Asia/Srednekolymsk 10:14:52 - LMT 1924 May 2 -10 - +10 1930 Jun 21 -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 2014 O 26 2s -11 - +11 -Z Asia/Ust-Nera 9:32:54 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1981 Ap -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 2011 S 13 0s -11 - +11 2014 O 26 2s -10 - +10 -Z Asia/Kamchatka 10:34:36 - LMT 1922 N 10 -11 - +11 1930 Jun 21 -12 R +12/+13 1991 Mar 31 2s -11 R +11/+12 1992 Ja 19 2s -12 R +12/+13 2010 Mar 28 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 -Z Asia/Anadyr 11:49:56 - LMT 1924 May 2 -12 - +12 1930 Jun 21 -13 R +13/+14 1982 Ap 1 0s -12 R +12/+13 1991 Mar 31 2s -11 R +11/+12 1992 Ja 19 2s -12 R +12/+13 2010 Mar 28 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 -Z Europe/Belgrade 1:22 - LMT 1884 -1 - CET 1941 Ap 18 23 -1 c CE%sT 1945 -1 - CET 1945 May 8 2s -1 1 CEST 1945 S 16 2s -1 - CET 1982 N 27 -1 E CE%sT -L Europe/Belgrade Europe/Ljubljana -L Europe/Belgrade Europe/Podgorica -L Europe/Belgrade Europe/Sarajevo -L Europe/Belgrade Europe/Skopje -L Europe/Belgrade Europe/Zagreb -L Europe/Prague Europe/Bratislava -R s 1918 o - Ap 15 23 1 S -R s 1918 1919 - O 6 24s 0 - -R s 1919 o - Ap 6 23 1 S -R s 1924 o - Ap 16 23 1 S -R s 1924 o - O 4 24s 0 - -R s 1926 o - Ap 17 23 1 S -R s 1926 1929 - O Sa>=1 24s 0 - -R s 1927 o - Ap 9 23 1 S -R s 1928 o - Ap 15 0 1 S -R s 1929 o - Ap 20 23 1 S -R s 1937 o - Jun 16 23 1 S -R s 1937 o - O 2 24s 0 - -R s 1938 o - Ap 2 23 1 S -R s 1938 o - Ap 30 23 2 M -R s 1938 o - O 2 24 1 S -R s 1939 o - O 7 24s 0 - -R s 1942 o - May 2 23 1 S -R s 1942 o - S 1 1 0 - -R s 1943 1946 - Ap Sa>=13 23 1 S -R s 1943 1944 - O Su>=1 1 0 - -R s 1945 1946 - S lastSu 1 0 - -R s 1949 o - Ap 30 23 1 S -R s 1949 o - O 2 1 0 - -R s 1974 1975 - Ap Sa>=12 23 1 S -R s 1974 1975 - O Su>=1 1 0 - -R s 1976 o - Mar 27 23 1 S -R s 1976 1977 - S lastSu 1 0 - -R s 1977 o - Ap 2 23 1 S -R s 1978 o - Ap 2 2s 1 S -R s 1978 o - O 1 2s 0 - -R Sp 1967 o - Jun 3 12 1 S -R Sp 1967 o - O 1 0 0 - -R Sp 1974 o - Jun 24 0 1 S -R Sp 1974 o - S 1 0 0 - -R Sp 1976 1977 - May 1 0 1 S -R Sp 1976 o - Au 1 0 0 - -R Sp 1977 o - S 28 0 0 - -R Sp 1978 o - Jun 1 0 1 S -R Sp 1978 o - Au 4 0 0 - -Z Europe/Madrid -0:14:44 - LMT 1900 D 31 23:45:16 -0 s WE%sT 1940 Mar 16 23 -1 s CE%sT 1979 -1 E CE%sT -Z Africa/Ceuta -0:21:16 - LMT 1900 D 31 23:38:44 -0 - WET 1918 May 6 23 -0 1 WEST 1918 O 7 23 -0 - WET 1924 -0 s WE%sT 1929 -0 - WET 1967 -0 Sp WE%sT 1984 Mar 16 -1 - CET 1986 -1 E CE%sT -Z Atlantic/Canary -1:1:36 - LMT 1922 Mar --1 - -01 1946 S 30 1 -0 - WET 1980 Ap 6 0s -0 1 WEST 1980 S 28 1u -0 E WE%sT -Z Europe/Stockholm 1:12:12 - LMT 1879 -1:0:14 - SET 1900 -1 - CET 1916 May 14 23 -1 1 CEST 1916 O 1 1 -1 - CET 1980 -1 E CE%sT -R CH 1941 1942 - May M>=1 1 1 S -R CH 1941 1942 - O M>=1 2 0 - -Z Europe/Zurich 0:34:8 - LMT 1853 Jul 16 -0:29:46 - BMT 1894 Jun -1 CH CE%sT 1981 -1 E CE%sT -R T 1916 o - May 1 0 1 S -R T 1916 o - O 1 0 0 - -R T 1920 o - Mar 28 0 1 S -R T 1920 o - O 25 0 0 - -R T 1921 o - Ap 3 0 1 S -R T 1921 o - O 3 0 0 - -R T 1922 o - Mar 26 0 1 S -R T 1922 o - O 8 0 0 - -R T 1924 o - May 13 0 1 S -R T 1924 1925 - O 1 0 0 - -R T 1925 o - May 1 0 1 S -R T 1940 o - Jul 1 0 1 S -R T 1940 o - O 6 0 0 - -R T 1940 o - D 1 0 1 S -R T 1941 o - S 21 0 0 - -R T 1942 o - Ap 1 0 1 S -R T 1945 o - O 8 0 0 - -R T 1946 o - Jun 1 0 1 S -R T 1946 o - O 1 0 0 - -R T 1947 1948 - Ap Su>=16 0 1 S -R T 1947 1951 - O Su>=2 0 0 - -R T 1949 o - Ap 10 0 1 S -R T 1950 o - Ap 16 0 1 S -R T 1951 o - Ap 22 0 1 S -R T 1962 o - Jul 15 0 1 S -R T 1963 o - O 30 0 0 - -R T 1964 o - May 15 0 1 S -R T 1964 o - O 1 0 0 - -R T 1973 o - Jun 3 1 1 S -R T 1973 1976 - O Su>=31 2 0 - -R T 1974 o - Mar 31 2 1 S -R T 1975 o - Mar 22 2 1 S -R T 1976 o - Mar 21 2 1 S -R T 1977 1978 - Ap Su>=1 2 1 S -R T 1977 1978 - O Su>=15 2 0 - -R T 1978 o - Jun 29 0 0 - -R T 1983 o - Jul 31 2 1 S -R T 1983 o - O 2 2 0 - -R T 1985 o - Ap 20 1s 1 S -R T 1985 o - S 28 1s 0 - -R T 1986 1993 - Mar lastSu 1s 1 S -R T 1986 1995 - S lastSu 1s 0 - -R T 1994 o - Mar 20 1s 1 S -R T 1995 2006 - Mar lastSu 1s 1 S -R T 1996 2006 - O lastSu 1s 0 - -Z Europe/Istanbul 1:55:52 - LMT 1880 -1:56:56 - IMT 1910 O -2 T EE%sT 1978 Jun 29 -3 T +03/+04 1984 N 1 2 -2 T EE%sT 2007 -2 E EE%sT 2011 Mar 27 1u -2 - EET 2011 Mar 28 1u -2 E EE%sT 2014 Mar 30 1u -2 - EET 2014 Mar 31 1u -2 E EE%sT 2015 O 25 1u -2 1 EEST 2015 N 8 1u -2 E EE%sT 2016 S 7 -3 - +03 -L Europe/Istanbul Asia/Istanbul -Z Europe/Kiev 2:2:4 - LMT 1880 -2:2:4 - KMT 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 S 20 -1 c CE%sT 1943 N 6 -3 R MSK/MSD 1990 Jul 1 2 -2 1 EEST 1991 S 29 3 -2 c EE%sT 1996 May 13 -2 E EE%sT -Z Europe/Uzhgorod 1:29:12 - LMT 1890 O -1 - CET 1940 -1 c CE%sT 1944 O -1 1 CEST 1944 O 26 -1 - CET 1945 Jun 29 -3 R MSK/MSD 1990 -3 - MSK 1990 Jul 1 2 -1 - CET 1991 Mar 31 3 -2 - EET 1992 Mar 20 -2 c EE%sT 1996 May 13 -2 E EE%sT -Z Europe/Zaporozhye 2:20:40 - LMT 1880 -2:20 - +0220 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 Au 25 -1 c CE%sT 1943 O 25 -3 R MSK/MSD 1991 Mar 31 2 -2 e EE%sT 1992 Mar 20 -2 c EE%sT 1996 May 13 -2 E EE%sT -R u 1918 1919 - Mar lastSu 2 1 D -R u 1918 1919 - O lastSu 2 0 S -R u 1942 o - F 9 2 1 W -R u 1945 o - Au 14 23u 1 P -R u 1945 o - S 30 2 0 S -R u 1967 2006 - O lastSu 2 0 S -R u 1967 1973 - Ap lastSu 2 1 D -R u 1974 o - Ja 6 2 1 D -R u 1975 o - F lastSu 2 1 D -R u 1976 1986 - Ap lastSu 2 1 D -R u 1987 2006 - Ap Su>=1 2 1 D -R u 2007 ma - Mar Su>=8 2 1 D -R u 2007 ma - N Su>=1 2 0 S -Z EST -5 - EST -Z MST -7 - MST -Z HST -10 - HST -Z EST5EDT -5 u E%sT -Z CST6CDT -6 u C%sT -Z MST7MDT -7 u M%sT -Z PST8PDT -8 u P%sT -R NY 1920 o - Mar lastSu 2 1 D -R NY 1920 o - O lastSu 2 0 S -R NY 1921 1966 - Ap lastSu 2 1 D -R NY 1921 1954 - S lastSu 2 0 S -R NY 1955 1966 - O lastSu 2 0 S -Z America/New_York -4:56:2 - LMT 1883 N 18 12:3:58 --5 u E%sT 1920 --5 NY E%sT 1942 --5 u E%sT 1946 --5 NY E%sT 1967 --5 u E%sT -R Ch 1920 o - Jun 13 2 1 D -R Ch 1920 1921 - O lastSu 2 0 S -R Ch 1921 o - Mar lastSu 2 1 D -R Ch 1922 1966 - Ap lastSu 2 1 D -R Ch 1922 1954 - S lastSu 2 0 S -R Ch 1955 1966 - O lastSu 2 0 S -Z America/Chicago -5:50:36 - LMT 1883 N 18 12:9:24 --6 u C%sT 1920 --6 Ch C%sT 1936 Mar 1 2 --5 - EST 1936 N 15 2 --6 Ch C%sT 1942 --6 u C%sT 1946 --6 Ch C%sT 1967 --6 u C%sT -Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 12:14:48 --7 u M%sT 1992 O 25 2 --6 u C%sT -Z America/North_Dakota/New_Salem -6:45:39 - LMT 1883 N 18 12:14:21 --7 u M%sT 2003 O 26 2 --6 u C%sT -Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 12:12:53 --7 u M%sT 2010 N 7 2 --6 u C%sT -R De 1920 1921 - Mar lastSu 2 1 D -R De 1920 o - O lastSu 2 0 S -R De 1921 o - May 22 2 0 S -R De 1965 1966 - Ap lastSu 2 1 D -R De 1965 1966 - O lastSu 2 0 S -Z America/Denver -6:59:56 - LMT 1883 N 18 12:0:4 --7 u M%sT 1920 --7 De M%sT 1942 --7 u M%sT 1946 --7 De M%sT 1967 --7 u M%sT -R CA 1948 o - Mar 14 2:1 1 D -R CA 1949 o - Ja 1 2 0 S -R CA 1950 1966 - Ap lastSu 1 1 D -R CA 1950 1961 - S lastSu 2 0 S -R CA 1962 1966 - O lastSu 2 0 S -Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 12:7:2 --8 u P%sT 1946 --8 CA P%sT 1967 --8 u P%sT -Z America/Juneau 15:2:19 - LMT 1867 O 19 15:33:32 --8:57:41 - LMT 1900 Au 20 12 --8 - PST 1942 --8 u P%sT 1946 --8 - PST 1969 --8 u P%sT 1980 Ap 27 2 --9 u Y%sT 1980 O 26 2 --8 u P%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Sitka 14:58:47 - LMT 1867 O 19 15:30 --9:1:13 - LMT 1900 Au 20 12 --8 - PST 1942 --8 u P%sT 1946 --8 - PST 1969 --8 u P%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Metlakatla 15:13:42 - LMT 1867 O 19 15:44:55 --8:46:18 - LMT 1900 Au 20 12 --8 - PST 1942 --8 u P%sT 1946 --8 - PST 1969 --8 u P%sT 1983 O 30 2 --8 - PST 2015 N 1 2 --9 u AK%sT 2018 N 4 2 --8 - PST 2019 Ja 20 2 --9 u AK%sT -Z America/Yakutat 14:41:5 - LMT 1867 O 19 15:12:18 --9:18:55 - LMT 1900 Au 20 12 --9 - YST 1942 --9 u Y%sT 1946 --9 - YST 1969 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Anchorage 14:0:24 - LMT 1867 O 19 14:31:37 --9:59:36 - LMT 1900 Au 20 12 --10 - AST 1942 --10 u A%sT 1967 Ap --10 - AHST 1969 --10 u AH%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Nome 12:58:22 - LMT 1867 O 19 13:29:35 --11:1:38 - LMT 1900 Au 20 12 --11 - NST 1942 --11 u N%sT 1946 --11 - NST 1967 Ap --11 - BST 1969 --11 u B%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Adak 12:13:22 - LMT 1867 O 19 12:44:35 --11:46:38 - LMT 1900 Au 20 12 --11 - NST 1942 --11 u N%sT 1946 --11 - NST 1967 Ap --11 - BST 1969 --11 u B%sT 1983 O 30 2 --10 u AH%sT 1983 N 30 --10 u H%sT -Z Pacific/Honolulu -10:31:26 - LMT 1896 Ja 13 12 --10:30 - HST 1933 Ap 30 2 --10:30 1 HDT 1933 May 21 12 --10:30 u H%sT 1947 Jun 8 2 --10 - HST -Z America/Phoenix -7:28:18 - LMT 1883 N 18 11:31:42 --7 u M%sT 1944 Ja 1 0:1 --7 - MST 1944 Ap 1 0:1 --7 u M%sT 1944 O 1 0:1 --7 - MST 1967 --7 u M%sT 1968 Mar 21 --7 - MST -L America/Phoenix America/Creston -Z America/Boise -7:44:49 - LMT 1883 N 18 12:15:11 --8 u P%sT 1923 May 13 2 --7 u M%sT 1974 --7 - MST 1974 F 3 2 --7 u M%sT -R In 1941 o - Jun 22 2 1 D -R In 1941 1954 - S lastSu 2 0 S -R In 1946 1954 - Ap lastSu 2 1 D -Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 12:15:22 --6 u C%sT 1920 --6 In C%sT 1942 --6 u C%sT 1946 --6 In C%sT 1955 Ap 24 2 --5 - EST 1957 S 29 2 --6 - CST 1958 Ap 27 2 --5 - EST 1969 --5 u E%sT 1971 --5 - EST 2006 --5 u E%sT -R Ma 1951 o - Ap lastSu 2 1 D -R Ma 1951 o - S lastSu 2 0 S -R Ma 1954 1960 - Ap lastSu 2 1 D -R Ma 1954 1960 - S lastSu 2 0 S -Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 12:14:37 --6 u C%sT 1951 --6 Ma C%sT 1961 Ap 30 2 --5 - EST 1969 --5 u E%sT 1974 Ja 6 2 --6 1 CDT 1974 O 27 2 --5 u E%sT 1976 --5 - EST 2006 --5 u E%sT -R V 1946 o - Ap lastSu 2 1 D -R V 1946 o - S lastSu 2 0 S -R V 1953 1954 - Ap lastSu 2 1 D -R V 1953 1959 - S lastSu 2 0 S -R V 1955 o - May 1 0 1 D -R V 1956 1963 - Ap lastSu 2 1 D -R V 1960 o - O lastSu 2 0 S -R V 1961 o - S lastSu 2 0 S -R V 1962 1963 - O lastSu 2 0 S -Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 12:9:53 --6 u C%sT 1946 --6 V C%sT 1964 Ap 26 2 --5 - EST 1969 --5 u E%sT 1971 --5 - EST 2006 Ap 2 2 --6 u C%sT 2007 N 4 2 --5 u E%sT -R Pe 1955 o - May 1 0 1 D -R Pe 1955 1960 - S lastSu 2 0 S -R Pe 1956 1963 - Ap lastSu 2 1 D -R Pe 1961 1963 - O lastSu 2 0 S -Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 12:12:57 --6 u C%sT 1946 --6 Pe C%sT 1964 Ap 26 2 --5 - EST 1967 O 29 2 --6 u C%sT 1969 Ap 27 2 --5 u E%sT 1971 --5 - EST 2006 Ap 2 2 --6 u C%sT -R Pi 1955 o - May 1 0 1 D -R Pi 1955 1960 - S lastSu 2 0 S -R Pi 1956 1964 - Ap lastSu 2 1 D -R Pi 1961 1964 - O lastSu 2 0 S -Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 12:10:53 --6 u C%sT 1955 --6 Pi C%sT 1965 Ap 25 2 --5 - EST 1966 O 30 2 --6 u C%sT 1977 O 30 2 --5 - EST 2006 Ap 2 2 --6 u C%sT 2007 N 4 2 --5 u E%sT -R St 1947 1961 - Ap lastSu 2 1 D -R St 1947 1954 - S lastSu 2 0 S -R St 1955 1956 - O lastSu 2 0 S -R St 1957 1958 - S lastSu 2 0 S -R St 1959 1961 - O lastSu 2 0 S -Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 12:13:30 --6 u C%sT 1947 --6 St C%sT 1962 Ap 29 2 --5 - EST 1963 O 27 2 --6 u C%sT 1991 O 27 2 --5 - EST 2006 Ap 2 2 --6 u C%sT -R Pu 1946 1960 - Ap lastSu 2 1 D -R Pu 1946 1954 - S lastSu 2 0 S -R Pu 1955 1956 - O lastSu 2 0 S -R Pu 1957 1960 - S lastSu 2 0 S -Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 12:13:35 --6 u C%sT 1946 --6 Pu C%sT 1961 Ap 30 2 --5 - EST 1969 --5 u E%sT 1971 --5 - EST 2006 Ap 2 2 --6 u C%sT 2007 Mar 11 2 --5 u E%sT -Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 12:19:44 --6 u C%sT 1954 Ap 25 2 --5 - EST 1969 --5 u E%sT 1973 --5 - EST 2006 --5 u E%sT -R v 1921 o - May 1 2 1 D -R v 1921 o - S 1 2 0 S -R v 1941 o - Ap lastSu 2 1 D -R v 1941 o - S lastSu 2 0 S -R v 1946 o - Ap lastSu 0:1 1 D -R v 1946 o - Jun 2 2 0 S -R v 1950 1961 - Ap lastSu 2 1 D -R v 1950 1955 - S lastSu 2 0 S -R v 1956 1961 - O lastSu 2 0 S -Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 12:16:58 --6 u C%sT 1921 --6 v C%sT 1942 --6 u C%sT 1946 --6 v C%sT 1961 Jul 23 2 --5 - EST 1968 --5 u E%sT 1974 Ja 6 2 --6 1 CDT 1974 O 27 2 --5 u E%sT -Z America/Kentucky/Monticello -5:39:24 - LMT 1883 N 18 12:20:36 --6 u C%sT 1946 --6 - CST 1968 --6 u C%sT 2000 O 29 2 --5 u E%sT -R Dt 1948 o - Ap lastSu 2 1 D -R Dt 1948 o - S lastSu 2 0 S -Z America/Detroit -5:32:11 - LMT 1905 --6 - CST 1915 May 15 2 --5 - EST 1942 --5 u E%sT 1946 --5 Dt E%sT 1967 Jun 14 0:1 --5 u E%sT 1969 --5 - EST 1973 --5 u E%sT 1975 --5 - EST 1975 Ap 27 2 --5 u E%sT -R Me 1946 o - Ap lastSu 2 1 D -R Me 1946 o - S lastSu 2 0 S -R Me 1966 o - Ap lastSu 2 1 D -R Me 1966 o - O lastSu 2 0 S -Z America/Menominee -5:50:27 - LMT 1885 S 18 12 --6 u C%sT 1946 --6 Me C%sT 1969 Ap 27 2 --5 - EST 1973 Ap 29 2 --6 u C%sT -R C 1918 o - Ap 14 2 1 D -R C 1918 o - O 27 2 0 S -R C 1942 o - F 9 2 1 W -R C 1945 o - Au 14 23u 1 P -R C 1945 o - S 30 2 0 S -R C 1974 1986 - Ap lastSu 2 1 D -R C 1974 2006 - O lastSu 2 0 S -R C 1987 2006 - Ap Su>=1 2 1 D -R C 2007 ma - Mar Su>=8 2 1 D -R C 2007 ma - N Su>=1 2 0 S -R j 1917 o - Ap 8 2 1 D -R j 1917 o - S 17 2 0 S -R j 1919 o - May 5 23 1 D -R j 1919 o - Au 12 23 0 S -R j 1920 1935 - May Su>=1 23 1 D -R j 1920 1935 - O lastSu 23 0 S -R j 1936 1941 - May M>=9 0 1 D -R j 1936 1941 - O M>=2 0 0 S -R j 1946 1950 - May Su>=8 2 1 D -R j 1946 1950 - O Su>=2 2 0 S -R j 1951 1986 - Ap lastSu 2 1 D -R j 1951 1959 - S lastSu 2 0 S -R j 1960 1986 - O lastSu 2 0 S -R j 1987 o - Ap Su>=1 0:1 1 D -R j 1987 2006 - O lastSu 0:1 0 S -R j 1988 o - Ap Su>=1 0:1 2 DD -R j 1989 2006 - Ap Su>=1 0:1 1 D -R j 2007 2011 - Mar Su>=8 0:1 1 D -R j 2007 2010 - N Su>=1 0:1 0 S -Z America/St_Johns -3:30:52 - LMT 1884 --3:30:52 j N%sT 1918 --3:30:52 C N%sT 1919 --3:30:52 j N%sT 1935 Mar 30 --3:30 j N%sT 1942 May 11 --3:30 C N%sT 1946 --3:30 j N%sT 2011 N --3:30 C N%sT -Z America/Goose_Bay -4:1:40 - LMT 1884 --3:30:52 - NST 1918 --3:30:52 C N%sT 1919 --3:30:52 - NST 1935 Mar 30 --3:30 - NST 1936 --3:30 j N%sT 1942 May 11 --3:30 C N%sT 1946 --3:30 j N%sT 1966 Mar 15 2 --4 j A%sT 2011 N --4 C A%sT -R H 1916 o - Ap 1 0 1 D -R H 1916 o - O 1 0 0 S -R H 1920 o - May 9 0 1 D -R H 1920 o - Au 29 0 0 S -R H 1921 o - May 6 0 1 D -R H 1921 1922 - S 5 0 0 S -R H 1922 o - Ap 30 0 1 D -R H 1923 1925 - May Su>=1 0 1 D -R H 1923 o - S 4 0 0 S -R H 1924 o - S 15 0 0 S -R H 1925 o - S 28 0 0 S -R H 1926 o - May 16 0 1 D -R H 1926 o - S 13 0 0 S -R H 1927 o - May 1 0 1 D -R H 1927 o - S 26 0 0 S -R H 1928 1931 - May Su>=8 0 1 D -R H 1928 o - S 9 0 0 S -R H 1929 o - S 3 0 0 S -R H 1930 o - S 15 0 0 S -R H 1931 1932 - S M>=24 0 0 S -R H 1932 o - May 1 0 1 D -R H 1933 o - Ap 30 0 1 D -R H 1933 o - O 2 0 0 S -R H 1934 o - May 20 0 1 D -R H 1934 o - S 16 0 0 S -R H 1935 o - Jun 2 0 1 D -R H 1935 o - S 30 0 0 S -R H 1936 o - Jun 1 0 1 D -R H 1936 o - S 14 0 0 S -R H 1937 1938 - May Su>=1 0 1 D -R H 1937 1941 - S M>=24 0 0 S -R H 1939 o - May 28 0 1 D -R H 1940 1941 - May Su>=1 0 1 D -R H 1946 1949 - Ap lastSu 2 1 D -R H 1946 1949 - S lastSu 2 0 S -R H 1951 1954 - Ap lastSu 2 1 D -R H 1951 1954 - S lastSu 2 0 S -R H 1956 1959 - Ap lastSu 2 1 D -R H 1956 1959 - S lastSu 2 0 S -R H 1962 1973 - Ap lastSu 2 1 D -R H 1962 1973 - O lastSu 2 0 S -Z America/Halifax -4:14:24 - LMT 1902 Jun 15 --4 H A%sT 1918 --4 C A%sT 1919 --4 H A%sT 1942 F 9 2s --4 C A%sT 1946 --4 H A%sT 1974 --4 C A%sT -Z America/Glace_Bay -3:59:48 - LMT 1902 Jun 15 --4 C A%sT 1953 --4 H A%sT 1954 --4 - AST 1972 --4 H A%sT 1974 --4 C A%sT -R o 1933 1935 - Jun Su>=8 1 1 D -R o 1933 1935 - S Su>=8 1 0 S -R o 1936 1938 - Jun Su>=1 1 1 D -R o 1936 1938 - S Su>=1 1 0 S -R o 1939 o - May 27 1 1 D -R o 1939 1941 - S Sa>=21 1 0 S -R o 1940 o - May 19 1 1 D -R o 1941 o - May 4 1 1 D -R o 1946 1972 - Ap lastSu 2 1 D -R o 1946 1956 - S lastSu 2 0 S -R o 1957 1972 - O lastSu 2 0 S -R o 1993 2006 - Ap Su>=1 0:1 1 D -R o 1993 2006 - O lastSu 0:1 0 S -Z America/Moncton -4:19:8 - LMT 1883 D 9 --5 - EST 1902 Jun 15 --4 C A%sT 1933 --4 o A%sT 1942 --4 C A%sT 1946 --4 o A%sT 1973 --4 C A%sT 1993 --4 o A%sT 2007 --4 C A%sT -R t 1919 o - Mar 30 23:30 1 D -R t 1919 o - O 26 0 0 S -R t 1920 o - May 2 2 1 D -R t 1920 o - S 26 0 0 S -R t 1921 o - May 15 2 1 D -R t 1921 o - S 15 2 0 S -R t 1922 1923 - May Su>=8 2 1 D -R t 1922 1926 - S Su>=15 2 0 S -R t 1924 1927 - May Su>=1 2 1 D -R t 1927 1937 - S Su>=25 2 0 S -R t 1928 1937 - Ap Su>=25 2 1 D -R t 1938 1940 - Ap lastSu 2 1 D -R t 1938 1939 - S lastSu 2 0 S -R t 1945 1946 - S lastSu 2 0 S -R t 1946 o - Ap lastSu 2 1 D -R t 1947 1949 - Ap lastSu 0 1 D -R t 1947 1948 - S lastSu 0 0 S -R t 1949 o - N lastSu 0 0 S -R t 1950 1973 - Ap lastSu 2 1 D -R t 1950 o - N lastSu 2 0 S -R t 1951 1956 - S lastSu 2 0 S -R t 1957 1973 - O lastSu 2 0 S -Z America/Toronto -5:17:32 - LMT 1895 --5 C E%sT 1919 --5 t E%sT 1942 F 9 2s --5 C E%sT 1946 --5 t E%sT 1974 --5 C E%sT -L America/Toronto America/Nassau -Z America/Thunder_Bay -5:57 - LMT 1895 --6 - CST 1910 --5 - EST 1942 --5 C E%sT 1970 --5 t E%sT 1973 --5 - EST 1974 --5 C E%sT -Z America/Nipigon -5:53:4 - LMT 1895 --5 C E%sT 1940 S 29 --5 1 EDT 1942 F 9 2s --5 C E%sT -Z America/Rainy_River -6:18:16 - LMT 1895 --6 C C%sT 1940 S 29 --6 1 CDT 1942 F 9 2s --6 C C%sT -R W 1916 o - Ap 23 0 1 D -R W 1916 o - S 17 0 0 S -R W 1918 o - Ap 14 2 1 D -R W 1918 o - O 27 2 0 S -R W 1937 o - May 16 2 1 D -R W 1937 o - S 26 2 0 S -R W 1942 o - F 9 2 1 W -R W 1945 o - Au 14 23u 1 P -R W 1945 o - S lastSu 2 0 S -R W 1946 o - May 12 2 1 D -R W 1946 o - O 13 2 0 S -R W 1947 1949 - Ap lastSu 2 1 D -R W 1947 1949 - S lastSu 2 0 S -R W 1950 o - May 1 2 1 D -R W 1950 o - S 30 2 0 S -R W 1951 1960 - Ap lastSu 2 1 D -R W 1951 1958 - S lastSu 2 0 S -R W 1959 o - O lastSu 2 0 S -R W 1960 o - S lastSu 2 0 S -R W 1963 o - Ap lastSu 2 1 D -R W 1963 o - S 22 2 0 S -R W 1966 1986 - Ap lastSu 2s 1 D -R W 1966 2005 - O lastSu 2s 0 S -R W 1987 2005 - Ap Su>=1 2s 1 D -Z America/Winnipeg -6:28:36 - LMT 1887 Jul 16 --6 W C%sT 2006 --6 C C%sT -R r 1918 o - Ap 14 2 1 D -R r 1918 o - O 27 2 0 S -R r 1930 1934 - May Su>=1 0 1 D -R r 1930 1934 - O Su>=1 0 0 S -R r 1937 1941 - Ap Su>=8 0 1 D -R r 1937 o - O Su>=8 0 0 S -R r 1938 o - O Su>=1 0 0 S -R r 1939 1941 - O Su>=8 0 0 S -R r 1942 o - F 9 2 1 W -R r 1945 o - Au 14 23u 1 P -R r 1945 o - S lastSu 2 0 S -R r 1946 o - Ap Su>=8 2 1 D -R r 1946 o - O Su>=8 2 0 S -R r 1947 1957 - Ap lastSu 2 1 D -R r 1947 1957 - S lastSu 2 0 S -R r 1959 o - Ap lastSu 2 1 D -R r 1959 o - O lastSu 2 0 S -R Sw 1957 o - Ap lastSu 2 1 D -R Sw 1957 o - O lastSu 2 0 S -R Sw 1959 1961 - Ap lastSu 2 1 D -R Sw 1959 o - O lastSu 2 0 S -R Sw 1960 1961 - S lastSu 2 0 S -Z America/Regina -6:58:36 - LMT 1905 S --7 r M%sT 1960 Ap lastSu 2 --6 - CST -Z America/Swift_Current -7:11:20 - LMT 1905 S --7 C M%sT 1946 Ap lastSu 2 --7 r M%sT 1950 --7 Sw M%sT 1972 Ap lastSu 2 --6 - CST -R Ed 1918 1919 - Ap Su>=8 2 1 D -R Ed 1918 o - O 27 2 0 S -R Ed 1919 o - May 27 2 0 S -R Ed 1920 1923 - Ap lastSu 2 1 D -R Ed 1920 o - O lastSu 2 0 S -R Ed 1921 1923 - S lastSu 2 0 S -R Ed 1942 o - F 9 2 1 W -R Ed 1945 o - Au 14 23u 1 P -R Ed 1945 o - S lastSu 2 0 S -R Ed 1947 o - Ap lastSu 2 1 D -R Ed 1947 o - S lastSu 2 0 S -R Ed 1972 1986 - Ap lastSu 2 1 D -R Ed 1972 2006 - O lastSu 2 0 S -Z America/Edmonton -7:33:52 - LMT 1906 S --7 Ed M%sT 1987 --7 C M%sT -R Va 1918 o - Ap 14 2 1 D -R Va 1918 o - O 27 2 0 S -R Va 1942 o - F 9 2 1 W -R Va 1945 o - Au 14 23u 1 P -R Va 1945 o - S 30 2 0 S -R Va 1946 1986 - Ap lastSu 2 1 D -R Va 1946 o - S 29 2 0 S -R Va 1947 1961 - S lastSu 2 0 S -R Va 1962 2006 - O lastSu 2 0 S -Z America/Vancouver -8:12:28 - LMT 1884 --8 Va P%sT 1987 --8 C P%sT -Z America/Dawson_Creek -8:0:56 - LMT 1884 --8 C P%sT 1947 --8 Va P%sT 1972 Au 30 2 --7 - MST -Z America/Fort_Nelson -8:10:47 - LMT 1884 --8 Va P%sT 1946 --8 - PST 1947 --8 Va P%sT 1987 --8 C P%sT 2015 Mar 8 2 --7 - MST -R Y 1918 o - Ap 14 2 1 D -R Y 1918 o - O 27 2 0 S -R Y 1919 o - May 25 2 1 D -R Y 1919 o - N 1 0 0 S -R Y 1942 o - F 9 2 1 W -R Y 1945 o - Au 14 23u 1 P -R Y 1945 o - S 30 2 0 S -R Y 1965 o - Ap lastSu 0 2 DD -R Y 1965 o - O lastSu 2 0 S -R Y 1980 1986 - Ap lastSu 2 1 D -R Y 1980 2006 - O lastSu 2 0 S -R Y 1987 2006 - Ap Su>=1 2 1 D -Z America/Pangnirtung 0 - -00 1921 --4 Y A%sT 1995 Ap Su>=1 2 --5 C E%sT 1999 O 31 2 --6 C C%sT 2000 O 29 2 --5 C E%sT -Z America/Iqaluit 0 - -00 1942 Au --5 Y E%sT 1999 O 31 2 --6 C C%sT 2000 O 29 2 --5 C E%sT -Z America/Resolute 0 - -00 1947 Au 31 --6 Y C%sT 2000 O 29 2 --5 - EST 2001 Ap 1 3 --6 C C%sT 2006 O 29 2 --5 - EST 2007 Mar 11 3 --6 C C%sT -Z America/Rankin_Inlet 0 - -00 1957 --6 Y C%sT 2000 O 29 2 --5 - EST 2001 Ap 1 3 --6 C C%sT -Z America/Cambridge_Bay 0 - -00 1920 --7 Y M%sT 1999 O 31 2 --6 C C%sT 2000 O 29 2 --5 - EST 2000 N 5 --6 - CST 2001 Ap 1 3 --7 C M%sT -Z America/Yellowknife 0 - -00 1935 --7 Y M%sT 1980 --7 C M%sT -Z America/Inuvik 0 - -00 1953 --8 Y P%sT 1979 Ap lastSu 2 --7 Y M%sT 1980 --7 C M%sT -Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 --9 Y Y%sT 1967 May 28 --8 Y P%sT 1980 --8 C P%sT 2020 N --7 - MST -Z America/Dawson -9:17:40 - LMT 1900 Au 20 --9 Y Y%sT 1973 O 28 --8 Y P%sT 1980 --8 C P%sT 2020 N --7 - MST -R m 1939 o - F 5 0 1 D -R m 1939 o - Jun 25 0 0 S -R m 1940 o - D 9 0 1 D -R m 1941 o - Ap 1 0 0 S -R m 1943 o - D 16 0 1 W -R m 1944 o - May 1 0 0 S -R m 1950 o - F 12 0 1 D -R m 1950 o - Jul 30 0 0 S -R m 1996 2000 - Ap Su>=1 2 1 D -R m 1996 2000 - O lastSu 2 0 S -R m 2001 o - May Su>=1 2 1 D -R m 2001 o - S lastSu 2 0 S -R m 2002 ma - Ap Su>=1 2 1 D -R m 2002 ma - O lastSu 2 0 S -Z America/Cancun -5:47:4 - LMT 1922 Ja 1 0:12:56 --6 - CST 1981 D 23 --5 m E%sT 1998 Au 2 2 --6 m C%sT 2015 F 1 2 --5 - EST -Z America/Merida -5:58:28 - LMT 1922 Ja 1 0:1:32 --6 - CST 1981 D 23 --5 - EST 1982 D 2 --6 m C%sT -Z America/Matamoros -6:40 - LMT 1921 D 31 23:20 --6 - CST 1988 --6 u C%sT 1989 --6 m C%sT 2010 --6 u C%sT -Z America/Monterrey -6:41:16 - LMT 1921 D 31 23:18:44 --6 - CST 1988 --6 u C%sT 1989 --6 m C%sT -Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 0:23:24 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 m C%sT 2001 S 30 2 --6 - CST 2002 F 20 --6 m C%sT -Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 0:2:20 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1996 --6 m C%sT 1998 --6 - CST 1998 Ap Su>=1 3 --7 m M%sT 2010 --7 u M%sT -Z America/Chihuahua -7:4:20 - LMT 1921 D 31 23:55:40 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1996 --6 m C%sT 1998 --6 - CST 1998 Ap Su>=1 3 --7 m M%sT -Z America/Hermosillo -7:23:52 - LMT 1921 D 31 23:36:8 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1942 Ap 24 --7 - MST 1949 Ja 14 --8 - PST 1970 --7 m M%sT 1999 --7 - MST -Z America/Mazatlan -7:5:40 - LMT 1921 D 31 23:54:20 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1942 Ap 24 --7 - MST 1949 Ja 14 --8 - PST 1970 --7 m M%sT -Z America/Bahia_Banderas -7:1 - LMT 1921 D 31 23:59 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1942 Ap 24 --7 - MST 1949 Ja 14 --8 - PST 1970 --7 m M%sT 2010 Ap 4 2 --6 m C%sT -Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 --7 - MST 1924 --8 - PST 1927 Jun 10 23 --7 - MST 1930 N 15 --8 - PST 1931 Ap --8 1 PDT 1931 S 30 --8 - PST 1942 Ap 24 --8 1 PWT 1945 Au 14 23u --8 1 PPT 1945 N 12 --8 - PST 1948 Ap 5 --8 1 PDT 1949 Ja 14 --8 - PST 1954 --8 CA P%sT 1961 --8 - PST 1976 --8 u P%sT 1996 --8 m P%sT 2001 --8 u P%sT 2002 F 20 --8 m P%sT 2010 --8 u P%sT -R BB 1942 o - Ap 19 5u 1 D -R BB 1942 o - Au 31 6u 0 S -R BB 1943 o - May 2 5u 1 D -R BB 1943 o - S 5 6u 0 S -R BB 1944 o - Ap 10 5u 0:30 - -R BB 1944 o - S 10 6u 0 S -R BB 1977 o - Jun 12 2 1 D -R BB 1977 1978 - O Su>=1 2 0 S -R BB 1978 1980 - Ap Su>=15 2 1 D -R BB 1979 o - S 30 2 0 S -R BB 1980 o - S 25 2 0 S -Z America/Barbados -3:58:29 - LMT 1911 Au 28 --4 BB A%sT 1944 --4 BB AST/-0330 1945 --4 BB A%sT -R BZ 1918 1941 - O Sa>=1 24 0:30 -0530 -R BZ 1919 1942 - F Sa>=8 24 0 CST -R BZ 1942 o - Jun 27 24 1 CWT -R BZ 1945 o - Au 14 23u 1 CPT -R BZ 1945 o - D 15 24 0 CST -R BZ 1947 1967 - O Sa>=1 24 0:30 -0530 -R BZ 1948 1968 - F Sa>=8 24 0 CST -R BZ 1973 o - D 5 0 1 CDT -R BZ 1974 o - F 9 0 0 CST -R BZ 1982 o - D 18 0 1 CDT -R BZ 1983 o - F 12 0 0 CST -Z America/Belize -5:52:48 - LMT 1912 Ap --6 BZ %s -R Be 1917 o - Ap 5 24 1 - -R Be 1917 o - S 30 24 0 - -R Be 1918 o - Ap 13 24 1 - -R Be 1918 o - S 15 24 0 S -R Be 1942 o - Ja 11 2 1 D -R Be 1942 o - O 18 2 0 S -R Be 1943 o - Mar 21 2 1 D -R Be 1943 o - O 31 2 0 S -R Be 1944 1945 - Mar Su>=8 2 1 D -R Be 1944 1945 - N Su>=1 2 0 S -R Be 1947 o - May Su>=15 2 1 D -R Be 1947 o - S Su>=8 2 0 S -R Be 1948 1952 - May Su>=22 2 1 D -R Be 1948 1952 - S Su>=1 2 0 S -R Be 1956 o - May Su>=22 2 1 D -R Be 1956 o - O lastSu 2 0 S -Z Atlantic/Bermuda -4:19:18 - LMT 1890 --4:19:18 Be BMT/BST 1930 Ja 1 2 --4 Be A%sT 1974 Ap 28 2 --4 C A%sT 1976 --4 u A%sT -R CR 1979 1980 - F lastSu 0 1 D -R CR 1979 1980 - Jun Su>=1 0 0 S -R CR 1991 1992 - Ja Sa>=15 0 1 D -R CR 1991 o - Jul 1 0 0 S -R CR 1992 o - Mar 15 0 0 S -Z America/Costa_Rica -5:36:13 - LMT 1890 --5:36:13 - SJMT 1921 Ja 15 --6 CR C%sT -R Q 1928 o - Jun 10 0 1 D -R Q 1928 o - O 10 0 0 S -R Q 1940 1942 - Jun Su>=1 0 1 D -R Q 1940 1942 - S Su>=1 0 0 S -R Q 1945 1946 - Jun Su>=1 0 1 D -R Q 1945 1946 - S Su>=1 0 0 S -R Q 1965 o - Jun 1 0 1 D -R Q 1965 o - S 30 0 0 S -R Q 1966 o - May 29 0 1 D -R Q 1966 o - O 2 0 0 S -R Q 1967 o - Ap 8 0 1 D -R Q 1967 1968 - S Su>=8 0 0 S -R Q 1968 o - Ap 14 0 1 D -R Q 1969 1977 - Ap lastSu 0 1 D -R Q 1969 1971 - O lastSu 0 0 S -R Q 1972 1974 - O 8 0 0 S -R Q 1975 1977 - O lastSu 0 0 S -R Q 1978 o - May 7 0 1 D -R Q 1978 1990 - O Su>=8 0 0 S -R Q 1979 1980 - Mar Su>=15 0 1 D -R Q 1981 1985 - May Su>=5 0 1 D -R Q 1986 1989 - Mar Su>=14 0 1 D -R Q 1990 1997 - Ap Su>=1 0 1 D -R Q 1991 1995 - O Su>=8 0s 0 S -R Q 1996 o - O 6 0s 0 S -R Q 1997 o - O 12 0s 0 S -R Q 1998 1999 - Mar lastSu 0s 1 D -R Q 1998 2003 - O lastSu 0s 0 S -R Q 2000 2003 - Ap Su>=1 0s 1 D -R Q 2004 o - Mar lastSu 0s 1 D -R Q 2006 2010 - O lastSu 0s 0 S -R Q 2007 o - Mar Su>=8 0s 1 D -R Q 2008 o - Mar Su>=15 0s 1 D -R Q 2009 2010 - Mar Su>=8 0s 1 D -R Q 2011 o - Mar Su>=15 0s 1 D -R Q 2011 o - N 13 0s 0 S -R Q 2012 o - Ap 1 0s 1 D -R Q 2012 ma - N Su>=1 0s 0 S -R Q 2013 ma - Mar Su>=8 0s 1 D -Z America/Havana -5:29:28 - LMT 1890 --5:29:36 - HMT 1925 Jul 19 12 --5 Q C%sT -R DO 1966 o - O 30 0 1 EDT -R DO 1967 o - F 28 0 0 EST -R DO 1969 1973 - O lastSu 0 0:30 -0430 -R DO 1970 o - F 21 0 0 EST -R DO 1971 o - Ja 20 0 0 EST -R DO 1972 1974 - Ja 21 0 0 EST -Z America/Santo_Domingo -4:39:36 - LMT 1890 --4:40 - SDMT 1933 Ap 1 12 --5 DO %s 1974 O 27 --4 - AST 2000 O 29 2 --5 u E%sT 2000 D 3 1 --4 - AST -R SV 1987 1988 - May Su>=1 0 1 D -R SV 1987 1988 - S lastSu 0 0 S -Z America/El_Salvador -5:56:48 - LMT 1921 --6 SV C%sT -R GT 1973 o - N 25 0 1 D -R GT 1974 o - F 24 0 0 S -R GT 1983 o - May 21 0 1 D -R GT 1983 o - S 22 0 0 S -R GT 1991 o - Mar 23 0 1 D -R GT 1991 o - S 7 0 0 S -R GT 2006 o - Ap 30 0 1 D -R GT 2006 o - O 1 0 0 S -Z America/Guatemala -6:2:4 - LMT 1918 O 5 --6 GT C%sT -R HT 1983 o - May 8 0 1 D -R HT 1984 1987 - Ap lastSu 0 1 D -R HT 1983 1987 - O lastSu 0 0 S -R HT 1988 1997 - Ap Su>=1 1s 1 D -R HT 1988 1997 - O lastSu 1s 0 S -R HT 2005 2006 - Ap Su>=1 0 1 D -R HT 2005 2006 - O lastSu 0 0 S -R HT 2012 2015 - Mar Su>=8 2 1 D -R HT 2012 2015 - N Su>=1 2 0 S -R HT 2017 ma - Mar Su>=8 2 1 D -R HT 2017 ma - N Su>=1 2 0 S -Z America/Port-au-Prince -4:49:20 - LMT 1890 --4:49 - PPMT 1917 Ja 24 12 --5 HT E%sT -R HN 1987 1988 - May Su>=1 0 1 D -R HN 1987 1988 - S lastSu 0 0 S -R HN 2006 o - May Su>=1 0 1 D -R HN 2006 o - Au M>=1 0 0 S -Z America/Tegucigalpa -5:48:52 - LMT 1921 Ap --6 HN C%sT -Z America/Jamaica -5:7:10 - LMT 1890 --5:7:10 - KMT 1912 F --5 - EST 1974 --5 u E%sT 1984 --5 - EST -Z America/Martinique -4:4:20 - LMT 1890 --4:4:20 - FFMT 1911 May --4 - AST 1980 Ap 6 --4 1 ADT 1980 S 28 --4 - AST -R NI 1979 1980 - Mar Su>=16 0 1 D -R NI 1979 1980 - Jun M>=23 0 0 S -R NI 2005 o - Ap 10 0 1 D -R NI 2005 o - O Su>=1 0 0 S -R NI 2006 o - Ap 30 2 1 D -R NI 2006 o - O Su>=1 1 0 S -Z America/Managua -5:45:8 - LMT 1890 --5:45:12 - MMT 1934 Jun 23 --6 - CST 1973 May --5 - EST 1975 F 16 --6 NI C%sT 1992 Ja 1 4 --5 - EST 1992 S 24 --6 - CST 1993 --5 - EST 1997 --6 NI C%sT -Z America/Panama -5:18:8 - LMT 1890 --5:19:36 - CMT 1908 Ap 22 --5 - EST -L America/Panama America/Atikokan -L America/Panama America/Cayman -Z America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12 --4 - AST 1942 May 3 --4 u A%sT 1946 --4 - AST -L America/Puerto_Rico America/Anguilla -L America/Puerto_Rico America/Antigua -L America/Puerto_Rico America/Aruba -L America/Puerto_Rico America/Curacao -L America/Puerto_Rico America/Blanc-Sablon -L America/Puerto_Rico America/Dominica -L America/Puerto_Rico America/Grenada -L America/Puerto_Rico America/Guadeloupe -L America/Puerto_Rico America/Kralendijk -L America/Puerto_Rico America/Lower_Princes -L America/Puerto_Rico America/Marigot -L America/Puerto_Rico America/Montserrat -L America/Puerto_Rico America/Port_of_Spain -L America/Puerto_Rico America/St_Barthelemy -L America/Puerto_Rico America/St_Kitts -L America/Puerto_Rico America/St_Lucia -L America/Puerto_Rico America/St_Thomas -L America/Puerto_Rico America/St_Vincent -L America/Puerto_Rico America/Tortola -Z America/Miquelon -3:44:40 - LMT 1911 May 15 --4 - AST 1980 May --3 - -03 1987 --3 C -03/-02 -Z America/Grand_Turk -4:44:32 - LMT 1890 --5:7:10 - KMT 1912 F --5 - EST 1979 --5 u E%sT 2015 Mar 8 2 --4 - AST 2018 Mar 11 3 --5 u E%sT -R A 1930 o - D 1 0 1 - -R A 1931 o - Ap 1 0 0 - -R A 1931 o - O 15 0 1 - -R A 1932 1940 - Mar 1 0 0 - -R A 1932 1939 - N 1 0 1 - -R A 1940 o - Jul 1 0 1 - -R A 1941 o - Jun 15 0 0 - -R A 1941 o - O 15 0 1 - -R A 1943 o - Au 1 0 0 - -R A 1943 o - O 15 0 1 - -R A 1946 o - Mar 1 0 0 - -R A 1946 o - O 1 0 1 - -R A 1963 o - O 1 0 0 - -R A 1963 o - D 15 0 1 - -R A 1964 1966 - Mar 1 0 0 - -R A 1964 1966 - O 15 0 1 - -R A 1967 o - Ap 2 0 0 - -R A 1967 1968 - O Su>=1 0 1 - -R A 1968 1969 - Ap Su>=1 0 0 - -R A 1974 o - Ja 23 0 1 - -R A 1974 o - May 1 0 0 - -R A 1988 o - D 1 0 1 - -R A 1989 1993 - Mar Su>=1 0 0 - -R A 1989 1992 - O Su>=15 0 1 - -R A 1999 o - O Su>=1 0 1 - -R A 2000 o - Mar 3 0 0 - -R A 2007 o - D 30 0 1 - -R A 2008 2009 - Mar Su>=15 0 0 - -R A 2008 o - O Su>=15 0 1 - -Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 -Z America/Argentina/Cordoba -4:16:48 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 -Z America/Argentina/Salta -4:21:40 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Tucuman -4:20:52 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 13 --3 A -03/-02 -Z America/Argentina/La_Rioja -4:27:24 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar --4 - -04 1991 May 7 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/San_Juan -4:34:4 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar --4 - -04 1991 May 7 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 May 31 --4 - -04 2004 Jul 25 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Jujuy -4:21:12 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1990 Mar 4 --4 - -04 1990 O 28 --4 1 -03 1991 Mar 17 --4 - -04 1991 O 6 --3 1 -02 1992 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Catamarca -4:23:8 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Mendoza -4:35:16 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1990 Mar 4 --4 - -04 1990 O 15 --4 1 -03 1991 Mar --4 - -04 1991 O 15 --4 1 -03 1992 Mar --4 - -04 1992 O 18 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 May 23 --4 - -04 2004 S 26 --3 A -03/-02 2008 O 18 --3 - -03 -R Sa 2008 2009 - Mar Su>=8 0 0 - -R Sa 2007 2008 - O Su>=8 0 1 - -Z America/Argentina/San_Luis -4:25:24 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1990 --3 1 -02 1990 Mar 14 --4 - -04 1990 O 15 --4 1 -03 1991 Mar --4 - -04 1991 Jun --3 - -03 1999 O 3 --4 1 -03 2000 Mar 3 --3 - -03 2004 May 31 --4 - -04 2004 Jul 25 --3 A -03/-02 2008 Ja 21 --4 Sa -04/-03 2009 O 11 --3 - -03 -Z America/Argentina/Rio_Gallegos -4:36:52 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Ushuaia -4:33:12 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 May 30 --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/La_Paz -4:32:36 - LMT 1890 --4:32:36 - CMT 1931 O 15 --4:32:36 1 BST 1932 Mar 21 --4 - -04 -R B 1931 o - O 3 11 1 - -R B 1932 1933 - Ap 1 0 0 - -R B 1932 o - O 3 0 1 - -R B 1949 1952 - D 1 0 1 - -R B 1950 o - Ap 16 1 0 - -R B 1951 1952 - Ap 1 0 0 - -R B 1953 o - Mar 1 0 0 - -R B 1963 o - D 9 0 1 - -R B 1964 o - Mar 1 0 0 - -R B 1965 o - Ja 31 0 1 - -R B 1965 o - Mar 31 0 0 - -R B 1965 o - D 1 0 1 - -R B 1966 1968 - Mar 1 0 0 - -R B 1966 1967 - N 1 0 1 - -R B 1985 o - N 2 0 1 - -R B 1986 o - Mar 15 0 0 - -R B 1986 o - O 25 0 1 - -R B 1987 o - F 14 0 0 - -R B 1987 o - O 25 0 1 - -R B 1988 o - F 7 0 0 - -R B 1988 o - O 16 0 1 - -R B 1989 o - Ja 29 0 0 - -R B 1989 o - O 15 0 1 - -R B 1990 o - F 11 0 0 - -R B 1990 o - O 21 0 1 - -R B 1991 o - F 17 0 0 - -R B 1991 o - O 20 0 1 - -R B 1992 o - F 9 0 0 - -R B 1992 o - O 25 0 1 - -R B 1993 o - Ja 31 0 0 - -R B 1993 1995 - O Su>=11 0 1 - -R B 1994 1995 - F Su>=15 0 0 - -R B 1996 o - F 11 0 0 - -R B 1996 o - O 6 0 1 - -R B 1997 o - F 16 0 0 - -R B 1997 o - O 6 0 1 - -R B 1998 o - Mar 1 0 0 - -R B 1998 o - O 11 0 1 - -R B 1999 o - F 21 0 0 - -R B 1999 o - O 3 0 1 - -R B 2000 o - F 27 0 0 - -R B 2000 2001 - O Su>=8 0 1 - -R B 2001 2006 - F Su>=15 0 0 - -R B 2002 o - N 3 0 1 - -R B 2003 o - O 19 0 1 - -R B 2004 o - N 2 0 1 - -R B 2005 o - O 16 0 1 - -R B 2006 o - N 5 0 1 - -R B 2007 o - F 25 0 0 - -R B 2007 o - O Su>=8 0 1 - -R B 2008 2017 - O Su>=15 0 1 - -R B 2008 2011 - F Su>=15 0 0 - -R B 2012 o - F Su>=22 0 0 - -R B 2013 2014 - F Su>=15 0 0 - -R B 2015 o - F Su>=22 0 0 - -R B 2016 2019 - F Su>=15 0 0 - -R B 2018 o - N Su>=1 0 1 - -Z America/Noronha -2:9:40 - LMT 1914 --2 B -02/-01 1990 S 17 --2 - -02 1999 S 30 --2 B -02/-01 2000 O 15 --2 - -02 2001 S 13 --2 B -02/-01 2002 O --2 - -02 -Z America/Belem -3:13:56 - LMT 1914 --3 B -03/-02 1988 S 12 --3 - -03 -Z America/Santarem -3:38:48 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 2008 Jun 24 --3 - -03 -Z America/Fortaleza -2:34 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1999 S 30 --3 B -03/-02 2000 O 22 --3 - -03 2001 S 13 --3 B -03/-02 2002 O --3 - -03 -Z America/Recife -2:19:36 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1999 S 30 --3 B -03/-02 2000 O 15 --3 - -03 2001 S 13 --3 B -03/-02 2002 O --3 - -03 -Z America/Araguaina -3:12:48 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1995 S 14 --3 B -03/-02 2003 S 24 --3 - -03 2012 O 21 --3 B -03/-02 2013 S --3 - -03 -Z America/Maceio -2:22:52 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1995 O 13 --3 B -03/-02 1996 S 4 --3 - -03 1999 S 30 --3 B -03/-02 2000 O 22 --3 - -03 2001 S 13 --3 B -03/-02 2002 O --3 - -03 -Z America/Bahia -2:34:4 - LMT 1914 --3 B -03/-02 2003 S 24 --3 - -03 2011 O 16 --3 B -03/-02 2012 O 21 --3 - -03 -Z America/Sao_Paulo -3:6:28 - LMT 1914 --3 B -03/-02 1963 O 23 --3 1 -02 1964 --3 B -03/-02 -Z America/Campo_Grande -3:38:28 - LMT 1914 --4 B -04/-03 -Z America/Cuiaba -3:44:20 - LMT 1914 --4 B -04/-03 2003 S 24 --4 - -04 2004 O --4 B -04/-03 -Z America/Porto_Velho -4:15:36 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 -Z America/Boa_Vista -4:2:40 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 1999 S 30 --4 B -04/-03 2000 O 15 --4 - -04 -Z America/Manaus -4:0:4 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 1993 S 28 --4 B -04/-03 1994 S 22 --4 - -04 -Z America/Eirunepe -4:39:28 - LMT 1914 --5 B -05/-04 1988 S 12 --5 - -05 1993 S 28 --5 B -05/-04 1994 S 22 --5 - -05 2008 Jun 24 --4 - -04 2013 N 10 --5 - -05 -Z America/Rio_Branco -4:31:12 - LMT 1914 --5 B -05/-04 1988 S 12 --5 - -05 2008 Jun 24 --4 - -04 2013 N 10 --5 - -05 -R x 1927 1931 - S 1 0 1 - -R x 1928 1932 - Ap 1 0 0 - -R x 1968 o - N 3 4u 1 - -R x 1969 o - Mar 30 3u 0 - -R x 1969 o - N 23 4u 1 - -R x 1970 o - Mar 29 3u 0 - -R x 1971 o - Mar 14 3u 0 - -R x 1970 1972 - O Su>=9 4u 1 - -R x 1972 1986 - Mar Su>=9 3u 0 - -R x 1973 o - S 30 4u 1 - -R x 1974 1987 - O Su>=9 4u 1 - -R x 1987 o - Ap 12 3u 0 - -R x 1988 1990 - Mar Su>=9 3u 0 - -R x 1988 1989 - O Su>=9 4u 1 - -R x 1990 o - S 16 4u 1 - -R x 1991 1996 - Mar Su>=9 3u 0 - -R x 1991 1997 - O Su>=9 4u 1 - -R x 1997 o - Mar 30 3u 0 - -R x 1998 o - Mar Su>=9 3u 0 - -R x 1998 o - S 27 4u 1 - -R x 1999 o - Ap 4 3u 0 - -R x 1999 2010 - O Su>=9 4u 1 - -R x 2000 2007 - Mar Su>=9 3u 0 - -R x 2008 o - Mar 30 3u 0 - -R x 2009 o - Mar Su>=9 3u 0 - -R x 2010 o - Ap Su>=1 3u 0 - -R x 2011 o - May Su>=2 3u 0 - -R x 2011 o - Au Su>=16 4u 1 - -R x 2012 2014 - Ap Su>=23 3u 0 - -R x 2012 2014 - S Su>=2 4u 1 - -R x 2016 2018 - May Su>=9 3u 0 - -R x 2016 2018 - Au Su>=9 4u 1 - -R x 2019 ma - Ap Su>=2 3u 0 - -R x 2019 ma - S Su>=2 4u 1 - -Z America/Santiago -4:42:45 - LMT 1890 --4:42:45 - SMT 1910 Ja 10 --5 - -05 1916 Jul --4:42:45 - SMT 1918 S 10 --4 - -04 1919 Jul --4:42:45 - SMT 1927 S --5 x -05/-04 1932 S --4 - -04 1942 Jun --5 - -05 1942 Au --4 - -04 1946 Jul 15 --4 1 -03 1946 S --4 - -04 1947 Ap --5 - -05 1947 May 21 23 --4 x -04/-03 -Z America/Punta_Arenas -4:43:40 - LMT 1890 --4:42:45 - SMT 1910 Ja 10 --5 - -05 1916 Jul --4:42:45 - SMT 1918 S 10 --4 - -04 1919 Jul --4:42:45 - SMT 1927 S --5 x -05/-04 1932 S --4 - -04 1942 Jun --5 - -05 1942 Au --4 - -04 1947 Ap --5 - -05 1947 May 21 23 --4 x -04/-03 2016 D 4 --3 - -03 -Z Pacific/Easter -7:17:28 - LMT 1890 --7:17:28 - EMT 1932 S --7 x -07/-06 1982 Mar 14 3u --6 x -06/-05 -Z Antarctica/Palmer 0 - -00 1965 --4 A -04/-03 1969 O 5 --3 A -03/-02 1982 May --4 x -04/-03 2016 D 4 --3 - -03 -R CO 1992 o - May 3 0 1 - -R CO 1993 o - Ap 4 0 0 - -Z America/Bogota -4:56:16 - LMT 1884 Mar 13 --4:56:16 - BMT 1914 N 23 --5 CO -05/-04 -R EC 1992 o - N 28 0 1 - -R EC 1993 o - F 5 0 0 - -Z America/Guayaquil -5:19:20 - LMT 1890 --5:14 - QMT 1931 --5 EC -05/-04 -Z Pacific/Galapagos -5:58:24 - LMT 1931 --5 - -05 1986 --6 EC -06/-05 -R FK 1937 1938 - S lastSu 0 1 - -R FK 1938 1942 - Mar Su>=19 0 0 - -R FK 1939 o - O 1 0 1 - -R FK 1940 1942 - S lastSu 0 1 - -R FK 1943 o - Ja 1 0 0 - -R FK 1983 o - S lastSu 0 1 - -R FK 1984 1985 - Ap lastSu 0 0 - -R FK 1984 o - S 16 0 1 - -R FK 1985 2000 - S Su>=9 0 1 - -R FK 1986 2000 - Ap Su>=16 0 0 - -R FK 2001 2010 - Ap Su>=15 2 0 - -R FK 2001 2010 - S Su>=1 2 1 - -Z Atlantic/Stanley -3:51:24 - LMT 1890 --3:51:24 - SMT 1912 Mar 12 --4 FK -04/-03 1983 May --3 FK -03/-02 1985 S 15 --4 FK -04/-03 2010 S 5 2 --3 - -03 -Z America/Cayenne -3:29:20 - LMT 1911 Jul --4 - -04 1967 O --3 - -03 -Z America/Guyana -3:52:39 - LMT 1911 Au --4 - -04 1915 Mar --3:45 - -0345 1975 Au --3 - -03 1992 Mar 29 1 --4 - -04 -R y 1975 1988 - O 1 0 1 - -R y 1975 1978 - Mar 1 0 0 - -R y 1979 1991 - Ap 1 0 0 - -R y 1989 o - O 22 0 1 - -R y 1990 o - O 1 0 1 - -R y 1991 o - O 6 0 1 - -R y 1992 o - Mar 1 0 0 - -R y 1992 o - O 5 0 1 - -R y 1993 o - Mar 31 0 0 - -R y 1993 1995 - O 1 0 1 - -R y 1994 1995 - F lastSu 0 0 - -R y 1996 o - Mar 1 0 0 - -R y 1996 2001 - O Su>=1 0 1 - -R y 1997 o - F lastSu 0 0 - -R y 1998 2001 - Mar Su>=1 0 0 - -R y 2002 2004 - Ap Su>=1 0 0 - -R y 2002 2003 - S Su>=1 0 1 - -R y 2004 2009 - O Su>=15 0 1 - -R y 2005 2009 - Mar Su>=8 0 0 - -R y 2010 ma - O Su>=1 0 1 - -R y 2010 2012 - Ap Su>=8 0 0 - -R y 2013 ma - Mar Su>=22 0 0 - -Z America/Asuncion -3:50:40 - LMT 1890 --3:50:40 - AMT 1931 O 10 --4 - -04 1972 O --3 - -03 1974 Ap --4 y -04/-03 -R PE 1938 o - Ja 1 0 1 - -R PE 1938 o - Ap 1 0 0 - -R PE 1938 1939 - S lastSu 0 1 - -R PE 1939 1940 - Mar Su>=24 0 0 - -R PE 1986 1987 - Ja 1 0 1 - -R PE 1986 1987 - Ap 1 0 0 - -R PE 1990 o - Ja 1 0 1 - -R PE 1990 o - Ap 1 0 0 - -R PE 1994 o - Ja 1 0 1 - -R PE 1994 o - Ap 1 0 0 - -Z America/Lima -5:8:12 - LMT 1890 --5:8:36 - LMT 1908 Jul 28 --5 PE -05/-04 -Z Atlantic/South_Georgia -2:26:8 - LMT 1890 --2 - -02 -Z America/Paramaribo -3:40:40 - LMT 1911 --3:40:52 - PMT 1935 --3:40:36 - PMT 1945 O --3:30 - -0330 1984 O --3 - -03 -R U 1923 1925 - O 1 0 0:30 - -R U 1924 1926 - Ap 1 0 0 - -R U 1933 1938 - O lastSu 0 0:30 - -R U 1934 1941 - Mar lastSa 24 0 - -R U 1939 o - O 1 0 0:30 - -R U 1940 o - O 27 0 0:30 - -R U 1941 o - Au 1 0 0:30 - -R U 1942 o - D 14 0 0:30 - -R U 1943 o - Mar 14 0 0 - -R U 1959 o - May 24 0 0:30 - -R U 1959 o - N 15 0 0 - -R U 1960 o - Ja 17 0 1 - -R U 1960 o - Mar 6 0 0 - -R U 1965 o - Ap 4 0 1 - -R U 1965 o - S 26 0 0 - -R U 1968 o - May 27 0 0:30 - -R U 1968 o - D 1 0 0 - -R U 1970 o - Ap 25 0 1 - -R U 1970 o - Jun 14 0 0 - -R U 1972 o - Ap 23 0 1 - -R U 1972 o - Jul 16 0 0 - -R U 1974 o - Ja 13 0 1:30 - -R U 1974 o - Mar 10 0 0:30 - -R U 1974 o - S 1 0 0 - -R U 1974 o - D 22 0 1 - -R U 1975 o - Mar 30 0 0 - -R U 1976 o - D 19 0 1 - -R U 1977 o - Mar 6 0 0 - -R U 1977 o - D 4 0 1 - -R U 1978 1979 - Mar Su>=1 0 0 - -R U 1978 o - D 17 0 1 - -R U 1979 o - Ap 29 0 1 - -R U 1980 o - Mar 16 0 0 - -R U 1987 o - D 14 0 1 - -R U 1988 o - F 28 0 0 - -R U 1988 o - D 11 0 1 - -R U 1989 o - Mar 5 0 0 - -R U 1989 o - O 29 0 1 - -R U 1990 o - F 25 0 0 - -R U 1990 1991 - O Su>=21 0 1 - -R U 1991 1992 - Mar Su>=1 0 0 - -R U 1992 o - O 18 0 1 - -R U 1993 o - F 28 0 0 - -R U 2004 o - S 19 0 1 - -R U 2005 o - Mar 27 2 0 - -R U 2005 o - O 9 2 1 - -R U 2006 2015 - Mar Su>=8 2 0 - -R U 2006 2014 - O Su>=1 2 1 - -Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 --3:44:51 - MMT 1920 May --4 - -04 1923 O --3:30 U -0330/-03 1942 D 14 --3 U -03/-0230 1960 --3 U -03/-02 1968 --3 U -03/-0230 1970 --3 U -03/-02 1974 --3 U -03/-0130 1974 Mar 10 --3 U -03/-0230 1974 D 22 --3 U -03/-02 -Z America/Caracas -4:27:44 - LMT 1890 --4:27:40 - CMT 1912 F 12 --4:30 - -0430 1965 --4 - -04 2007 D 9 3 --4:30 - -0430 2016 May 1 2:30 --4 - -04 -Z Etc/GMT 0 - GMT -Z Etc/UTC 0 - UTC -L Etc/GMT GMT -L Etc/UTC Etc/Universal -L Etc/UTC Etc/Zulu -L Etc/GMT Etc/Greenwich -L Etc/GMT Etc/GMT-0 -L Etc/GMT Etc/GMT+0 -L Etc/GMT Etc/GMT0 -Z Etc/GMT-14 14 - +14 -Z Etc/GMT-13 13 - +13 -Z Etc/GMT-12 12 - +12 -Z Etc/GMT-11 11 - +11 -Z Etc/GMT-10 10 - +10 -Z Etc/GMT-9 9 - +09 -Z Etc/GMT-8 8 - +08 -Z Etc/GMT-7 7 - +07 -Z Etc/GMT-6 6 - +06 -Z Etc/GMT-5 5 - +05 -Z Etc/GMT-4 4 - +04 -Z Etc/GMT-3 3 - +03 -Z Etc/GMT-2 2 - +02 -Z Etc/GMT-1 1 - +01 -Z Etc/GMT+1 -1 - -01 -Z Etc/GMT+2 -2 - -02 -Z Etc/GMT+3 -3 - -03 -Z Etc/GMT+4 -4 - -04 -Z Etc/GMT+5 -5 - -05 -Z Etc/GMT+6 -6 - -06 -Z Etc/GMT+7 -7 - -07 -Z Etc/GMT+8 -8 - -08 -Z Etc/GMT+9 -9 - -09 -Z Etc/GMT+10 -10 - -10 -Z Etc/GMT+11 -11 - -11 -Z Etc/GMT+12 -12 - -12 -Z Factory 0 - -00 -L Africa/Nairobi Africa/Asmera -L Africa/Abidjan Africa/Timbuktu -L America/Argentina/Catamarca America/Argentina/ComodRivadavia -L America/Adak America/Atka -L America/Argentina/Buenos_Aires America/Buenos_Aires -L America/Argentina/Catamarca America/Catamarca -L America/Panama America/Coral_Harbour -L America/Argentina/Cordoba America/Cordoba -L America/Tijuana America/Ensenada -L America/Indiana/Indianapolis America/Fort_Wayne -L America/Nuuk America/Godthab -L America/Indiana/Indianapolis America/Indianapolis -L America/Argentina/Jujuy America/Jujuy -L America/Indiana/Knox America/Knox_IN -L America/Kentucky/Louisville America/Louisville -L America/Argentina/Mendoza America/Mendoza -L America/Toronto America/Montreal -L America/Rio_Branco America/Porto_Acre -L America/Argentina/Cordoba America/Rosario -L America/Tijuana America/Santa_Isabel -L America/Denver America/Shiprock -L America/Puerto_Rico America/Virgin -L Pacific/Auckland Antarctica/South_Pole -L Asia/Ashgabat Asia/Ashkhabad -L Asia/Kolkata Asia/Calcutta -L Asia/Shanghai Asia/Chongqing -L Asia/Shanghai Asia/Chungking -L Asia/Dhaka Asia/Dacca -L Asia/Shanghai Asia/Harbin -L Asia/Urumqi Asia/Kashgar -L Asia/Kathmandu Asia/Katmandu -L Asia/Macau Asia/Macao -L Asia/Yangon Asia/Rangoon -L Asia/Ho_Chi_Minh Asia/Saigon -L Asia/Jerusalem Asia/Tel_Aviv -L Asia/Thimphu Asia/Thimbu -L Asia/Makassar Asia/Ujung_Pandang -L Asia/Ulaanbaatar Asia/Ulan_Bator -L Atlantic/Faroe Atlantic/Faeroe -L Europe/Oslo Atlantic/Jan_Mayen -L Australia/Sydney Australia/ACT -L Australia/Sydney Australia/Canberra -L Australia/Hobart Australia/Currie -L Australia/Lord_Howe Australia/LHI -L Australia/Sydney Australia/NSW -L Australia/Darwin Australia/North -L Australia/Brisbane Australia/Queensland -L Australia/Adelaide Australia/South -L Australia/Hobart Australia/Tasmania -L Australia/Melbourne Australia/Victoria -L Australia/Perth Australia/West -L Australia/Broken_Hill Australia/Yancowinna -L America/Rio_Branco Brazil/Acre -L America/Noronha Brazil/DeNoronha -L America/Sao_Paulo Brazil/East -L America/Manaus Brazil/West -L America/Halifax Canada/Atlantic -L America/Winnipeg Canada/Central -L America/Toronto Canada/Eastern -L America/Edmonton Canada/Mountain -L America/St_Johns Canada/Newfoundland -L America/Vancouver Canada/Pacific -L America/Regina Canada/Saskatchewan -L America/Whitehorse Canada/Yukon -L America/Santiago Chile/Continental -L Pacific/Easter Chile/EasterIsland -L America/Havana Cuba -L Africa/Cairo Egypt -L Europe/Dublin Eire -L Etc/UTC Etc/UCT -L Europe/London Europe/Belfast -L Europe/Chisinau Europe/Tiraspol -L Europe/London GB -L Europe/London GB-Eire -L Etc/GMT GMT+0 -L Etc/GMT GMT-0 -L Etc/GMT GMT0 -L Etc/GMT Greenwich -L Asia/Hong_Kong Hongkong -L Atlantic/Reykjavik Iceland -L Asia/Tehran Iran -L Asia/Jerusalem Israel -L America/Jamaica Jamaica -L Asia/Tokyo Japan -L Pacific/Kwajalein Kwajalein -L Africa/Tripoli Libya -L America/Tijuana Mexico/BajaNorte -L America/Mazatlan Mexico/BajaSur -L America/Mexico_City Mexico/General -L Pacific/Auckland NZ -L Pacific/Chatham NZ-CHAT -L America/Denver Navajo -L Asia/Shanghai PRC -L Pacific/Kanton Pacific/Enderbury -L Pacific/Honolulu Pacific/Johnston -L Pacific/Pohnpei Pacific/Ponape -L Pacific/Pago_Pago Pacific/Samoa -L Pacific/Chuuk Pacific/Truk -L Pacific/Chuuk Pacific/Yap -L Europe/Warsaw Poland -L Europe/Lisbon Portugal -L Asia/Taipei ROC -L Asia/Seoul ROK -L Asia/Singapore Singapore -L Europe/Istanbul Turkey -L Etc/UTC UCT -L America/Anchorage US/Alaska -L America/Adak US/Aleutian -L America/Phoenix US/Arizona -L America/Chicago US/Central -L America/Indiana/Indianapolis US/East-Indiana -L America/New_York US/Eastern -L Pacific/Honolulu US/Hawaii -L America/Indiana/Knox US/Indiana-Starke -L America/Detroit US/Michigan -L America/Denver US/Mountain -L America/Los_Angeles US/Pacific -L Pacific/Pago_Pago US/Samoa -L Etc/UTC UTC -L Etc/UTC Universal -L Europe/Moscow W-SU -L Etc/UTC Zulu diff --git a/telegramer/include/pytz/zoneinfo/zone.tab b/telegramer/include/pytz/zoneinfo/zone.tab deleted file mode 100644 index 086458f..0000000 --- a/telegramer/include/pytz/zoneinfo/zone.tab +++ /dev/null @@ -1,454 +0,0 @@ -# tzdb timezone descriptions (deprecated version) -# -# This file is in the public domain, so clarified as of -# 2009-05-17 by Arthur David Olson. -# -# From Paul Eggert (2021-09-20): -# This file is intended as a backward-compatibility aid for older programs. -# New programs should use zone1970.tab. This file is like zone1970.tab (see -# zone1970.tab's comments), but with the following additional restrictions: -# -# 1. This file contains only ASCII characters. -# 2. The first data column contains exactly one country code. -# -# Because of (2), each row stands for an area that is the intersection -# of a region identified by a country code and of a timezone where civil -# clocks have agreed since 1970; this is a narrower definition than -# that of zone1970.tab. -# -# Unlike zone1970.tab, a row's third column can be a Link from -# 'backward' instead of a Zone. -# -# This table is intended as an aid for users, to help them select timezones -# appropriate for their practical needs. It is not intended to take or -# endorse any position on legal or territorial claims. -# -#country- -#code coordinates TZ comments -AD +4230+00131 Europe/Andorra -AE +2518+05518 Asia/Dubai -AF +3431+06912 Asia/Kabul -AG +1703-06148 America/Antigua -AI +1812-06304 America/Anguilla -AL +4120+01950 Europe/Tirane -AM +4011+04430 Asia/Yerevan -AO -0848+01314 Africa/Luanda -AQ -7750+16636 Antarctica/McMurdo New Zealand time - McMurdo, South Pole -AQ -6617+11031 Antarctica/Casey Casey -AQ -6835+07758 Antarctica/Davis Davis -AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville -AQ -6736+06253 Antarctica/Mawson Mawson -AQ -6448-06406 Antarctica/Palmer Palmer -AQ -6734-06808 Antarctica/Rothera Rothera -AQ -690022+0393524 Antarctica/Syowa Syowa -AQ -720041+0023206 Antarctica/Troll Troll -AQ -7824+10654 Antarctica/Vostok Vostok -AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) -AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) -AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) -AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) -AR -2649-06513 America/Argentina/Tucuman Tucuman (TM) -AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) -AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) -AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) -AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) -AR -3319-06621 America/Argentina/San_Luis San Luis (SL) -AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) -AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) -AS -1416-17042 Pacific/Pago_Pago -AT +4813+01620 Europe/Vienna -AU -3133+15905 Australia/Lord_Howe Lord Howe Island -AU -5430+15857 Antarctica/Macquarie Macquarie Island -AU -4253+14719 Australia/Hobart Tasmania -AU -3749+14458 Australia/Melbourne Victoria -AU -3352+15113 Australia/Sydney New South Wales (most areas) -AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) -AU -2728+15302 Australia/Brisbane Queensland (most areas) -AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) -AU -3455+13835 Australia/Adelaide South Australia -AU -1228+13050 Australia/Darwin Northern Territory -AU -3157+11551 Australia/Perth Western Australia (most areas) -AU -3143+12852 Australia/Eucla Western Australia (Eucla) -AW +1230-06958 America/Aruba -AX +6006+01957 Europe/Mariehamn -AZ +4023+04951 Asia/Baku -BA +4352+01825 Europe/Sarajevo -BB +1306-05937 America/Barbados -BD +2343+09025 Asia/Dhaka -BE +5050+00420 Europe/Brussels -BF +1222-00131 Africa/Ouagadougou -BG +4241+02319 Europe/Sofia -BH +2623+05035 Asia/Bahrain -BI -0323+02922 Africa/Bujumbura -BJ +0629+00237 Africa/Porto-Novo -BL +1753-06251 America/St_Barthelemy -BM +3217-06446 Atlantic/Bermuda -BN +0456+11455 Asia/Brunei -BO -1630-06809 America/La_Paz -BQ +120903-0681636 America/Kralendijk -BR -0351-03225 America/Noronha Atlantic islands -BR -0127-04829 America/Belem Para (east); Amapa -BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) -BR -0803-03454 America/Recife Pernambuco -BR -0712-04812 America/Araguaina Tocantins -BR -0940-03543 America/Maceio Alagoas, Sergipe -BR -1259-03831 America/Bahia Bahia -BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) -BR -2027-05437 America/Campo_Grande Mato Grosso do Sul -BR -1535-05605 America/Cuiaba Mato Grosso -BR -0226-05452 America/Santarem Para (west) -BR -0846-06354 America/Porto_Velho Rondonia -BR +0249-06040 America/Boa_Vista Roraima -BR -0308-06001 America/Manaus Amazonas (east) -BR -0640-06952 America/Eirunepe Amazonas (west) -BR -0958-06748 America/Rio_Branco Acre -BS +2505-07721 America/Nassau -BT +2728+08939 Asia/Thimphu -BW -2439+02555 Africa/Gaborone -BY +5354+02734 Europe/Minsk -BZ +1730-08812 America/Belize -CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) -CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE -CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) -CA +4606-06447 America/Moncton Atlantic - New Brunswick -CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) -CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) -CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) -CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) -CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) -CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) -CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) -CA +744144-0944945 America/Resolute Central - NU (Resolute) -CA +624900-0920459 America/Rankin_Inlet Central - NU (central) -CA +5024-10439 America/Regina CST - SK (most areas) -CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) -CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) -CA +682059-1334300 America/Inuvik Mountain - NT (west) -CA +4906-11631 America/Creston MST - BC (Creston) -CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) -CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) -CA +6043-13503 America/Whitehorse MST - Yukon (east) -CA +6404-13925 America/Dawson MST - Yukon (west) -CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CC -1210+09655 Indian/Cocos -CD -0418+01518 Africa/Kinshasa Dem. Rep. of Congo (west) -CD -1140+02728 Africa/Lubumbashi Dem. Rep. of Congo (east) -CF +0422+01835 Africa/Bangui -CG -0416+01517 Africa/Brazzaville -CH +4723+00832 Europe/Zurich -CI +0519-00402 Africa/Abidjan -CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) -CL -5309-07055 America/Punta_Arenas Region of Magallanes -CL -2709-10926 Pacific/Easter Easter Island -CM +0403+00942 Africa/Douala -CN +3114+12128 Asia/Shanghai Beijing Time -CN +4348+08735 Asia/Urumqi Xinjiang Time -CO +0436-07405 America/Bogota -CR +0956-08405 America/Costa_Rica -CU +2308-08222 America/Havana -CV +1455-02331 Atlantic/Cape_Verde -CW +1211-06900 America/Curacao -CX -1025+10543 Indian/Christmas -CY +3510+03322 Asia/Nicosia Cyprus (most areas) -CY +3507+03357 Asia/Famagusta Northern Cyprus -CZ +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) -DE +4742+00841 Europe/Busingen Busingen -DJ +1136+04309 Africa/Djibouti -DK +5540+01235 Europe/Copenhagen -DM +1518-06124 America/Dominica -DO +1828-06954 America/Santo_Domingo -DZ +3647+00303 Africa/Algiers -EC -0210-07950 America/Guayaquil Ecuador (mainland) -EC -0054-08936 Pacific/Galapagos Galapagos Islands -EE +5925+02445 Europe/Tallinn -EG +3003+03115 Africa/Cairo -EH +2709-01312 Africa/El_Aaiun -ER +1520+03853 Africa/Asmara -ES +4024-00341 Europe/Madrid Spain (mainland) -ES +3553-00519 Africa/Ceuta Ceuta, Melilla -ES +2806-01524 Atlantic/Canary Canary Islands -ET +0902+03842 Africa/Addis_Ababa -FI +6010+02458 Europe/Helsinki -FJ -1808+17825 Pacific/Fiji -FK -5142-05751 Atlantic/Stanley -FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap -FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape -FM +0519+16259 Pacific/Kosrae Kosrae -FO +6201-00646 Atlantic/Faroe -FR +4852+00220 Europe/Paris -GA +0023+00927 Africa/Libreville -GB +513030-0000731 Europe/London -GD +1203-06145 America/Grenada -GE +4143+04449 Asia/Tbilisi -GF +0456-05220 America/Cayenne -GG +492717-0023210 Europe/Guernsey -GH +0533-00013 Africa/Accra -GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk Greenland (most areas) -GL +7646-01840 America/Danmarkshavn National Park (east coast) -GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit -GL +7634-06847 America/Thule Thule/Pituffik -GM +1328-01639 Africa/Banjul -GN +0931-01343 Africa/Conakry -GP +1614-06132 America/Guadeloupe -GQ +0345+00847 Africa/Malabo -GR +3758+02343 Europe/Athens -GS -5416-03632 Atlantic/South_Georgia -GT +1438-09031 America/Guatemala -GU +1328+14445 Pacific/Guam -GW +1151-01535 Africa/Bissau -GY +0648-05810 America/Guyana -HK +2217+11409 Asia/Hong_Kong -HN +1406-08713 America/Tegucigalpa -HR +4548+01558 Europe/Zagreb -HT +1832-07220 America/Port-au-Prince -HU +4730+01905 Europe/Budapest -ID -0610+10648 Asia/Jakarta Java, Sumatra -ID -0002+10920 Asia/Pontianak Borneo (west, central) -ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) -ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas -IE +5320-00615 Europe/Dublin -IL +314650+0351326 Asia/Jerusalem -IM +5409-00428 Europe/Isle_of_Man -IN +2232+08822 Asia/Kolkata -IO -0720+07225 Indian/Chagos -IQ +3321+04425 Asia/Baghdad -IR +3540+05126 Asia/Tehran -IS +6409-02151 Atlantic/Reykjavik -IT +4154+01229 Europe/Rome -JE +491101-0020624 Europe/Jersey -JM +175805-0764736 America/Jamaica -JO +3157+03556 Asia/Amman -JP +353916+1394441 Asia/Tokyo -KE -0117+03649 Africa/Nairobi -KG +4254+07436 Asia/Bishkek -KH +1133+10455 Asia/Phnom_Penh -KI +0125+17300 Pacific/Tarawa Gilbert Islands -KI -0247-17143 Pacific/Kanton Phoenix Islands -KI +0152-15720 Pacific/Kiritimati Line Islands -KM -1141+04316 Indian/Comoro -KN +1718-06243 America/St_Kitts -KP +3901+12545 Asia/Pyongyang -KR +3733+12658 Asia/Seoul -KW +2920+04759 Asia/Kuwait -KY +1918-08123 America/Cayman -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) -KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda -KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay -KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe -KZ +4431+05016 Asia/Aqtau Mangghystau/Mankistau -KZ +4707+05156 Asia/Atyrau Atyrau/Atirau/Gur'yev -KZ +5113+05121 Asia/Oral West Kazakhstan -LA +1758+10236 Asia/Vientiane -LB +3353+03530 Asia/Beirut -LC +1401-06100 America/St_Lucia -LI +4709+00931 Europe/Vaduz -LK +0656+07951 Asia/Colombo -LR +0618-01047 Africa/Monrovia -LS -2928+02730 Africa/Maseru -LT +5441+02519 Europe/Vilnius -LU +4936+00609 Europe/Luxembourg -LV +5657+02406 Europe/Riga -LY +3254+01311 Africa/Tripoli -MA +3339-00735 Africa/Casablanca -MC +4342+00723 Europe/Monaco -MD +4700+02850 Europe/Chisinau -ME +4226+01916 Europe/Podgorica -MF +1804-06305 America/Marigot -MG -1855+04731 Indian/Antananarivo -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) -MH +0905+16720 Pacific/Kwajalein Kwajalein -MK +4159+02126 Europe/Skopje -ML +1239-00800 Africa/Bamako -MM +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) -MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan -MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar -MO +221150+1133230 Asia/Macau -MP +1512+14545 Pacific/Saipan -MQ +1436-06105 America/Martinique -MR +1806-01557 Africa/Nouakchott -MS +1643-06213 America/Montserrat -MT +3554+01431 Europe/Malta -MU -2010+05730 Indian/Mauritius -MV +0410+07330 Indian/Maldives -MW -1547+03500 Africa/Blantyre -MX +1924-09909 America/Mexico_City Central Time -MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo -MX +2058-08937 America/Merida Central Time - Campeche, Yucatan -MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas) -MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo Leon, Tamaulipas (US border) -MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa -MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) -MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) -MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora -MX +3232-11701 America/Tijuana Pacific Time US - Baja California -MX +2048-10515 America/Bahia_Banderas Central Time - Bahia de Banderas -MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) -MY +0133+11020 Asia/Kuching Sabah, Sarawak -MZ -2558+03235 Africa/Maputo -NA -2234+01706 Africa/Windhoek -NC -2216+16627 Pacific/Noumea -NE +1331+00207 Africa/Niamey -NF -2903+16758 Pacific/Norfolk -NG +0627+00324 Africa/Lagos -NI +1209-08617 America/Managua -NL +5222+00454 Europe/Amsterdam -NO +5955+01045 Europe/Oslo -NP +2743+08519 Asia/Kathmandu -NR -0031+16655 Pacific/Nauru -NU -1901-16955 Pacific/Niue -NZ -3652+17446 Pacific/Auckland New Zealand (most areas) -NZ -4357-17633 Pacific/Chatham Chatham Islands -OM +2336+05835 Asia/Muscat -PA +0858-07932 America/Panama -PE -1203-07703 America/Lima -PF -1732-14934 Pacific/Tahiti Society Islands -PF -0900-13930 Pacific/Marquesas Marquesas Islands -PF -2308-13457 Pacific/Gambier Gambier Islands -PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) -PG -0613+15534 Pacific/Bougainville Bougainville -PH +1435+12100 Asia/Manila -PK +2452+06703 Asia/Karachi -PL +5215+02100 Europe/Warsaw -PM +4703-05620 America/Miquelon -PN -2504-13005 Pacific/Pitcairn -PR +182806-0660622 America/Puerto_Rico -PS +3130+03428 Asia/Gaza Gaza Strip -PS +313200+0350542 Asia/Hebron West Bank -PT +3843-00908 Europe/Lisbon Portugal (mainland) -PT +3238-01654 Atlantic/Madeira Madeira Islands -PT +3744-02540 Atlantic/Azores Azores -PW +0720+13429 Pacific/Palau -PY -2516-05740 America/Asuncion -QA +2517+05132 Asia/Qatar -RE -2052+05528 Indian/Reunion -RO +4426+02606 Europe/Bucharest -RS +4450+02030 Europe/Belgrade -RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad -RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -# The obsolescent zone.tab format cannot represent Europe/Simferopol well. -# Put it in RU section and list as UA. See "territorial claims" above. -# Programs should use zone1970.tab instead; see above. -UA +4457+03406 Europe/Simferopol Crimea -RU +5836+04939 Europe/Kirov MSK+00 - Kirov -RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd -RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan -RU +5134+04602 Europe/Saratov MSK+01 - Saratov -RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk -RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia -RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals -RU +5500+07324 Asia/Omsk MSK+03 - Omsk -RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk -RU +5322+08345 Asia/Barnaul MSK+04 - Altai -RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk -RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo -RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area -RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia -RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky -RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River -RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky -RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River -RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky -RU +5934+15048 Asia/Magadan MSK+08 - Magadan -RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is -RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka -RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -RW -0157+03004 Africa/Kigali -SA +2438+04643 Asia/Riyadh -SB -0932+16012 Pacific/Guadalcanal -SC -0440+05528 Indian/Mahe -SD +1536+03232 Africa/Khartoum -SE +5920+01803 Europe/Stockholm -SG +0117+10351 Asia/Singapore -SH -1555-00542 Atlantic/St_Helena -SI +4603+01431 Europe/Ljubljana -SJ +7800+01600 Arctic/Longyearbyen -SK +4809+01707 Europe/Bratislava -SL +0830-01315 Africa/Freetown -SM +4355+01228 Europe/San_Marino -SN +1440-01726 Africa/Dakar -SO +0204+04522 Africa/Mogadishu -SR +0550-05510 America/Paramaribo -SS +0451+03137 Africa/Juba -ST +0020+00644 Africa/Sao_Tome -SV +1342-08912 America/El_Salvador -SX +180305-0630250 America/Lower_Princes -SY +3330+03618 Asia/Damascus -SZ -2618+03106 Africa/Mbabane -TC +2128-07108 America/Grand_Turk -TD +1207+01503 Africa/Ndjamena -TF -492110+0701303 Indian/Kerguelen -TG +0608+00113 Africa/Lome -TH +1345+10031 Asia/Bangkok -TJ +3835+06848 Asia/Dushanbe -TK -0922-17114 Pacific/Fakaofo -TL -0833+12535 Asia/Dili -TM +3757+05823 Asia/Ashgabat -TN +3648+01011 Africa/Tunis -TO -210800-1751200 Pacific/Tongatapu -TR +4101+02858 Europe/Istanbul -TT +1039-06131 America/Port_of_Spain -TV -0831+17913 Pacific/Funafuti -TW +2503+12130 Asia/Taipei -TZ -0648+03917 Africa/Dar_es_Salaam -UA +5026+03031 Europe/Kiev Ukraine (most areas) -UA +4837+02218 Europe/Uzhgorod Transcarpathia -UA +4750+03510 Europe/Zaporozhye Zaporozhye and east Lugansk -UG +0019+03225 Africa/Kampala -UM +2813-17722 Pacific/Midway Midway Islands -UM +1917+16637 Pacific/Wake Wake Island -US +404251-0740023 America/New_York Eastern (most areas) -US +421953-0830245 America/Detroit Eastern - MI (most areas) -US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) -US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) -US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) -US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) -US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) -US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) -US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) -US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) -US +415100-0873900 America/Chicago Central (most areas) -US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) -US +411745-0863730 America/Indiana/Knox Central - IN (Starke) -US +450628-0873651 America/Menominee Central - MI (Wisconsin border) -US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) -US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) -US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) -US +394421-1045903 America/Denver Mountain (most areas) -US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) -US +340308-1181434 America/Los_Angeles Pacific -US +611305-1495401 America/Anchorage Alaska (most areas) -US +581807-1342511 America/Juneau Alaska - Juneau area -US +571035-1351807 America/Sitka Alaska - Sitka area -US +550737-1313435 America/Metlakatla Alaska - Annette Island -US +593249-1394338 America/Yakutat Alaska - Yakutat -US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands -US +211825-1575130 Pacific/Honolulu Hawaii -UY -345433-0561245 America/Montevideo -UZ +3940+06648 Asia/Samarkand Uzbekistan (west) -UZ +4120+06918 Asia/Tashkent Uzbekistan (east) -VA +415408+0122711 Europe/Vatican -VC +1309-06114 America/St_Vincent -VE +1030-06656 America/Caracas -VG +1827-06437 America/Tortola -VI +1821-06456 America/St_Thomas -VN +1045+10640 Asia/Ho_Chi_Minh -VU -1740+16825 Pacific/Efate -WF -1318-17610 Pacific/Wallis -WS -1350-17144 Pacific/Apia -YE +1245+04512 Asia/Aden -YT -1247+04514 Indian/Mayotte -ZA -2615+02800 Africa/Johannesburg -ZM -1525+02817 Africa/Lusaka -ZW -1750+03103 Africa/Harare diff --git a/telegramer/include/pytz/zoneinfo/zone1970.tab b/telegramer/include/pytz/zoneinfo/zone1970.tab deleted file mode 100644 index c614be8..0000000 --- a/telegramer/include/pytz/zoneinfo/zone1970.tab +++ /dev/null @@ -1,374 +0,0 @@ -# tzdb timezone descriptions -# -# This file is in the public domain. -# -# From Paul Eggert (2018-06-27): -# This file contains a table where each row stands for a timezone where -# civil timestamps have agreed since 1970. Columns are separated by -# a single tab. Lines beginning with '#' are comments. All text uses -# UTF-8 encoding. The columns of the table are as follows: -# -# 1. The countries that overlap the timezone, as a comma-separated list -# of ISO 3166 2-character country codes. See the file 'iso3166.tab'. -# 2. Latitude and longitude of the timezone's principal location -# in ISO 6709 sign-degrees-minutes-seconds format, -# either ±DDMM±DDDMM or ±DDMMSS±DDDMMSS, -# first latitude (+ is north), then longitude (+ is east). -# 3. Timezone name used in value of TZ environment variable. -# Please see the theory.html file for how these names are chosen. -# If multiple timezones overlap a country, each has a row in the -# table, with each column 1 containing the country code. -# 4. Comments; present if and only if a country has multiple timezones. -# -# If a timezone covers multiple countries, the most-populous city is used, -# and that country is listed first in column 1; any other countries -# are listed alphabetically by country code. The table is sorted -# first by country code, then (if possible) by an order within the -# country that (1) makes some geographical sense, and (2) puts the -# most populous timezones first, where that does not contradict (1). -# -# This table is intended as an aid for users, to help them select timezones -# appropriate for their practical needs. It is not intended to take or -# endorse any position on legal or territorial claims. -# -#country- -#codes coordinates TZ comments -AD +4230+00131 Europe/Andorra -AE,OM +2518+05518 Asia/Dubai -AF +3431+06912 Asia/Kabul -AL +4120+01950 Europe/Tirane -AM +4011+04430 Asia/Yerevan -AQ -6617+11031 Antarctica/Casey Casey -AQ -6835+07758 Antarctica/Davis Davis -AQ -6736+06253 Antarctica/Mawson Mawson -AQ -6448-06406 Antarctica/Palmer Palmer -AQ -6734-06808 Antarctica/Rothera Rothera -AQ -720041+0023206 Antarctica/Troll Troll -AQ -7824+10654 Antarctica/Vostok Vostok -AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) -AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) -AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) -AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) -AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) -AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) -AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) -AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) -AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) -AR -3319-06621 America/Argentina/San_Luis San Luis (SL) -AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) -AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) -AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway -AT +4813+01620 Europe/Vienna -AU -3133+15905 Australia/Lord_Howe Lord Howe Island -AU -5430+15857 Antarctica/Macquarie Macquarie Island -AU -4253+14719 Australia/Hobart Tasmania -AU -3749+14458 Australia/Melbourne Victoria -AU -3352+15113 Australia/Sydney New South Wales (most areas) -AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) -AU -2728+15302 Australia/Brisbane Queensland (most areas) -AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) -AU -3455+13835 Australia/Adelaide South Australia -AU -1228+13050 Australia/Darwin Northern Territory -AU -3157+11551 Australia/Perth Western Australia (most areas) -AU -3143+12852 Australia/Eucla Western Australia (Eucla) -AZ +4023+04951 Asia/Baku -BB +1306-05937 America/Barbados -BD +2343+09025 Asia/Dhaka -BE +5050+00420 Europe/Brussels -BG +4241+02319 Europe/Sofia -BM +3217-06446 Atlantic/Bermuda -BN +0456+11455 Asia/Brunei -BO -1630-06809 America/La_Paz -BR -0351-03225 America/Noronha Atlantic islands -BR -0127-04829 America/Belem Pará (east); Amapá -BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) -BR -0803-03454 America/Recife Pernambuco -BR -0712-04812 America/Araguaina Tocantins -BR -0940-03543 America/Maceio Alagoas, Sergipe -BR -1259-03831 America/Bahia Bahia -BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) -BR -2027-05437 America/Campo_Grande Mato Grosso do Sul -BR -1535-05605 America/Cuiaba Mato Grosso -BR -0226-05452 America/Santarem Pará (west) -BR -0846-06354 America/Porto_Velho Rondônia -BR +0249-06040 America/Boa_Vista Roraima -BR -0308-06001 America/Manaus Amazonas (east) -BR -0640-06952 America/Eirunepe Amazonas (west) -BR -0958-06748 America/Rio_Branco Acre -BT +2728+08939 Asia/Thimphu -BY +5354+02734 Europe/Minsk -BZ +1730-08812 America/Belize -CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) -CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE -CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) -CA +4606-06447 America/Moncton Atlantic - New Brunswick -CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) -CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) -CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) -CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) -CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) -CA +744144-0944945 America/Resolute Central - NU (Resolute) -CA +624900-0920459 America/Rankin_Inlet Central - NU (central) -CA +5024-10439 America/Regina CST - SK (most areas) -CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) -CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) -CA +682059-1334300 America/Inuvik Mountain - NT (west) -CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) -CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) -CA +6043-13503 America/Whitehorse MST - Yukon (east) -CA +6404-13925 America/Dawson MST - Yukon (west) -CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CC -1210+09655 Indian/Cocos -CH,DE,LI +4723+00832 Europe/Zurich Swiss time -CI,BF,GH,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan -CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) -CL -5309-07055 America/Punta_Arenas Region of Magallanes -CL -2709-10926 Pacific/Easter Easter Island -CN +3114+12128 Asia/Shanghai Beijing Time -CN +4348+08735 Asia/Urumqi Xinjiang Time -CO +0436-07405 America/Bogota -CR +0956-08405 America/Costa_Rica -CU +2308-08222 America/Havana -CV +1455-02331 Atlantic/Cape_Verde -CX -1025+10543 Indian/Christmas -CY +3510+03322 Asia/Nicosia Cyprus (most areas) -CY +3507+03357 Asia/Famagusta Northern Cyprus -CZ,SK +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) -DK +5540+01235 Europe/Copenhagen -DO +1828-06954 America/Santo_Domingo -DZ +3647+00303 Africa/Algiers -EC -0210-07950 America/Guayaquil Ecuador (mainland) -EC -0054-08936 Pacific/Galapagos Galápagos Islands -EE +5925+02445 Europe/Tallinn -EG +3003+03115 Africa/Cairo -EH +2709-01312 Africa/El_Aaiun -ES +4024-00341 Europe/Madrid Spain (mainland) -ES +3553-00519 Africa/Ceuta Ceuta, Melilla -ES +2806-01524 Atlantic/Canary Canary Islands -FI,AX +6010+02458 Europe/Helsinki -FJ -1808+17825 Pacific/Fiji -FK -5142-05751 Atlantic/Stanley -FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap -FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape -FM +0519+16259 Pacific/Kosrae Kosrae -FO +6201-00646 Atlantic/Faroe -FR +4852+00220 Europe/Paris -GB,GG,IM,JE +513030-0000731 Europe/London -GE +4143+04449 Asia/Tbilisi -GF +0456-05220 America/Cayenne -GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk Greenland (most areas) -GL +7646-01840 America/Danmarkshavn National Park (east coast) -GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit -GL +7634-06847 America/Thule Thule/Pituffik -GR +3758+02343 Europe/Athens -GS -5416-03632 Atlantic/South_Georgia -GT +1438-09031 America/Guatemala -GU,MP +1328+14445 Pacific/Guam -GW +1151-01535 Africa/Bissau -GY +0648-05810 America/Guyana -HK +2217+11409 Asia/Hong_Kong -HN +1406-08713 America/Tegucigalpa -HT +1832-07220 America/Port-au-Prince -HU +4730+01905 Europe/Budapest -ID -0610+10648 Asia/Jakarta Java, Sumatra -ID -0002+10920 Asia/Pontianak Borneo (west, central) -ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) -ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas -IE +5320-00615 Europe/Dublin -IL +314650+0351326 Asia/Jerusalem -IN +2232+08822 Asia/Kolkata -IO -0720+07225 Indian/Chagos -IQ +3321+04425 Asia/Baghdad -IR +3540+05126 Asia/Tehran -IS +6409-02151 Atlantic/Reykjavik -IT,SM,VA +4154+01229 Europe/Rome -JM +175805-0764736 America/Jamaica -JO +3157+03556 Asia/Amman -JP +353916+1394441 Asia/Tokyo -KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi -KG +4254+07436 Asia/Bishkek -KI +0125+17300 Pacific/Tarawa Gilbert Islands -KI -0247-17143 Pacific/Kanton Phoenix Islands -KI +0152-15720 Pacific/Kiritimati Line Islands -KP +3901+12545 Asia/Pyongyang -KR +3733+12658 Asia/Seoul -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) -KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda -KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay -KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe -KZ +4431+05016 Asia/Aqtau Mangghystaū/Mankistau -KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur'yev -KZ +5113+05121 Asia/Oral West Kazakhstan -LB +3353+03530 Asia/Beirut -LK +0656+07951 Asia/Colombo -LR +0618-01047 Africa/Monrovia -LT +5441+02519 Europe/Vilnius -LU +4936+00609 Europe/Luxembourg -LV +5657+02406 Europe/Riga -LY +3254+01311 Africa/Tripoli -MA +3339-00735 Africa/Casablanca -MC +4342+00723 Europe/Monaco -MD +4700+02850 Europe/Chisinau -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) -MH +0905+16720 Pacific/Kwajalein Kwajalein -MM +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) -MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan -MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar -MO +221150+1133230 Asia/Macau -MQ +1436-06105 America/Martinique -MT +3554+01431 Europe/Malta -MU -2010+05730 Indian/Mauritius -MV +0410+07330 Indian/Maldives -MX +1924-09909 America/Mexico_City Central Time -MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo -MX +2058-08937 America/Merida Central Time - Campeche, Yucatán -MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas) -MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border) -MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa -MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) -MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) -MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora -MX +3232-11701 America/Tijuana Pacific Time US - Baja California -MX +2048-10515 America/Bahia_Banderas Central Time - Bahía de Banderas -MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) -MY +0133+11020 Asia/Kuching Sabah, Sarawak -MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time -NA -2234+01706 Africa/Windhoek -NC -2216+16627 Pacific/Noumea -NF -2903+16758 Pacific/Norfolk -NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time -NI +1209-08617 America/Managua -NL +5222+00454 Europe/Amsterdam -NO,SJ +5955+01045 Europe/Oslo -NP +2743+08519 Asia/Kathmandu -NR -0031+16655 Pacific/Nauru -NU -1901-16955 Pacific/Niue -NZ,AQ -3652+17446 Pacific/Auckland New Zealand time -NZ -4357-17633 Pacific/Chatham Chatham Islands -PA,CA,KY +0858-07932 America/Panama EST - Panama, Cayman, ON (Atikokan), NU (Coral H) -PE -1203-07703 America/Lima -PF -1732-14934 Pacific/Tahiti Society Islands -PF -0900-13930 Pacific/Marquesas Marquesas Islands -PF -2308-13457 Pacific/Gambier Gambier Islands -PG,AQ -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Dumont d'Urville -PG -0613+15534 Pacific/Bougainville Bougainville -PH +1435+12100 Asia/Manila -PK +2452+06703 Asia/Karachi -PL +5215+02100 Europe/Warsaw -PM +4703-05620 America/Miquelon -PN -2504-13005 Pacific/Pitcairn -PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST -PS +3130+03428 Asia/Gaza Gaza Strip -PS +313200+0350542 Asia/Hebron West Bank -PT +3843-00908 Europe/Lisbon Portugal (mainland) -PT +3238-01654 Atlantic/Madeira Madeira Islands -PT +3744-02540 Atlantic/Azores Azores -PW +0720+13429 Pacific/Palau -PY -2516-05740 America/Asuncion -QA,BH +2517+05132 Asia/Qatar -RE,TF -2052+05528 Indian/Reunion Réunion, Crozet, Scattered Islands -RO +4426+02606 Europe/Bucharest -RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade -RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad -RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -# Mention RU and UA alphabetically. See "territorial claims" above. -RU,UA +4457+03406 Europe/Simferopol Crimea -RU +5836+04939 Europe/Kirov MSK+00 - Kirov -RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd -RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan -RU +5134+04602 Europe/Saratov MSK+01 - Saratov -RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk -RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia -RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals -RU +5500+07324 Asia/Omsk MSK+03 - Omsk -RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk -RU +5322+08345 Asia/Barnaul MSK+04 - Altai -RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk -RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo -RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area -RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia -RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky -RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River -RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky -RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River -RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky -RU +5934+15048 Asia/Magadan MSK+08 - Magadan -RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is -RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka -RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -SA,AQ,KW,YE +2438+04643 Asia/Riyadh Arabia, Syowa -SB -0932+16012 Pacific/Guadalcanal -SC -0440+05528 Indian/Mahe -SD +1536+03232 Africa/Khartoum -SE +5920+01803 Europe/Stockholm -SG,MY +0117+10351 Asia/Singapore Singapore, peninsular Malaysia -SR +0550-05510 America/Paramaribo -SS +0451+03137 Africa/Juba -ST +0020+00644 Africa/Sao_Tome -SV +1342-08912 America/El_Salvador -SY +3330+03618 Asia/Damascus -TC +2128-07108 America/Grand_Turk -TD +1207+01503 Africa/Ndjamena -TF -492110+0701303 Indian/Kerguelen Kerguelen, St Paul Island, Amsterdam Island -TH,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) -TJ +3835+06848 Asia/Dushanbe -TK -0922-17114 Pacific/Fakaofo -TL -0833+12535 Asia/Dili -TM +3757+05823 Asia/Ashgabat -TN +3648+01011 Africa/Tunis -TO -210800-1751200 Pacific/Tongatapu -TR +4101+02858 Europe/Istanbul -TV -0831+17913 Pacific/Funafuti -TW +2503+12130 Asia/Taipei -UA +5026+03031 Europe/Kiev Ukraine (most areas) -UA +4837+02218 Europe/Uzhgorod Transcarpathia -UA +4750+03510 Europe/Zaporozhye Zaporozhye and east Lugansk -UM +1917+16637 Pacific/Wake Wake Island -US +404251-0740023 America/New_York Eastern (most areas) -US +421953-0830245 America/Detroit Eastern - MI (most areas) -US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) -US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) -US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) -US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) -US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) -US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) -US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) -US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) -US +415100-0873900 America/Chicago Central (most areas) -US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) -US +411745-0863730 America/Indiana/Knox Central - IN (Starke) -US +450628-0873651 America/Menominee Central - MI (Wisconsin border) -US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) -US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) -US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) -US +394421-1045903 America/Denver Mountain (most areas) -US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US,CA +332654-1120424 America/Phoenix MST - Arizona (except Navajo), Creston BC -US +340308-1181434 America/Los_Angeles Pacific -US +611305-1495401 America/Anchorage Alaska (most areas) -US +581807-1342511 America/Juneau Alaska - Juneau area -US +571035-1351807 America/Sitka Alaska - Sitka area -US +550737-1313435 America/Metlakatla Alaska - Annette Island -US +593249-1394338 America/Yakutat Alaska - Yakutat -US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands -US,UM +211825-1575130 Pacific/Honolulu Hawaii -UY -345433-0561245 America/Montevideo -UZ +3940+06648 Asia/Samarkand Uzbekistan (west) -UZ +4120+06918 Asia/Tashkent Uzbekistan (east) -VE +1030-06656 America/Caracas -VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) -VU -1740+16825 Pacific/Efate -WF -1318-17610 Pacific/Wallis -WS -1350-17144 Pacific/Apia -ZA,LS,SZ -2615+02800 Africa/Johannesburg diff --git a/telegramer/include/telegram/__init__.py b/telegramer/include/telegram/__init__.py deleted file mode 100644 index c0b36de..0000000 --- a/telegramer/include/telegram/__init__.py +++ /dev/null @@ -1,328 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""A library that provides a Python interface to the Telegram Bot API""" - -from .base import TelegramObject -from .botcommand import BotCommand -from .user import User -from .files.chatphoto import ChatPhoto -from .chat import Chat -from .chatlocation import ChatLocation -from .chatinvitelink import ChatInviteLink -from .chatjoinrequest import ChatJoinRequest -from .chatmember import ( - ChatMember, - ChatMemberOwner, - ChatMemberAdministrator, - ChatMemberMember, - ChatMemberRestricted, - ChatMemberLeft, - ChatMemberBanned, -) -from .chatmemberupdated import ChatMemberUpdated -from .chatpermissions import ChatPermissions -from .files.photosize import PhotoSize -from .files.audio import Audio -from .files.voice import Voice -from .files.document import Document -from .files.animation import Animation -from .files.sticker import Sticker, StickerSet, MaskPosition -from .files.video import Video -from .files.contact import Contact -from .files.location import Location -from .files.venue import Venue -from .files.videonote import VideoNote -from .chataction import ChatAction -from .dice import Dice -from .userprofilephotos import UserProfilePhotos -from .keyboardbuttonpolltype import KeyboardButtonPollType -from .keyboardbutton import KeyboardButton -from .replymarkup import ReplyMarkup -from .replykeyboardmarkup import ReplyKeyboardMarkup -from .replykeyboardremove import ReplyKeyboardRemove -from .forcereply import ForceReply -from .error import TelegramError -from .files.inputfile import InputFile -from .files.file import File -from .parsemode import ParseMode -from .messageentity import MessageEntity -from .messageid import MessageId -from .games.game import Game -from .poll import Poll, PollOption, PollAnswer -from .voicechat import ( - VoiceChatStarted, - VoiceChatEnded, - VoiceChatParticipantsInvited, - VoiceChatScheduled, -) -from .loginurl import LoginUrl -from .proximityalerttriggered import ProximityAlertTriggered -from .games.callbackgame import CallbackGame -from .payment.shippingaddress import ShippingAddress -from .payment.orderinfo import OrderInfo -from .payment.successfulpayment import SuccessfulPayment -from .payment.invoice import Invoice -from .passport.credentials import EncryptedCredentials -from .passport.passportfile import PassportFile -from .passport.data import IdDocumentData, PersonalDetails, ResidentialAddress -from .passport.encryptedpassportelement import EncryptedPassportElement -from .passport.passportdata import PassportData -from .inline.inlinekeyboardbutton import InlineKeyboardButton -from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup -from .messageautodeletetimerchanged import MessageAutoDeleteTimerChanged -from .message import Message -from .callbackquery import CallbackQuery -from .choseninlineresult import ChosenInlineResult -from .inline.inputmessagecontent import InputMessageContent -from .inline.inlinequery import InlineQuery -from .inline.inlinequeryresult import InlineQueryResult -from .inline.inlinequeryresultarticle import InlineQueryResultArticle -from .inline.inlinequeryresultaudio import InlineQueryResultAudio -from .inline.inlinequeryresultcachedaudio import InlineQueryResultCachedAudio -from .inline.inlinequeryresultcacheddocument import InlineQueryResultCachedDocument -from .inline.inlinequeryresultcachedgif import InlineQueryResultCachedGif -from .inline.inlinequeryresultcachedmpeg4gif import InlineQueryResultCachedMpeg4Gif -from .inline.inlinequeryresultcachedphoto import InlineQueryResultCachedPhoto -from .inline.inlinequeryresultcachedsticker import InlineQueryResultCachedSticker -from .inline.inlinequeryresultcachedvideo import InlineQueryResultCachedVideo -from .inline.inlinequeryresultcachedvoice import InlineQueryResultCachedVoice -from .inline.inlinequeryresultcontact import InlineQueryResultContact -from .inline.inlinequeryresultdocument import InlineQueryResultDocument -from .inline.inlinequeryresultgif import InlineQueryResultGif -from .inline.inlinequeryresultlocation import InlineQueryResultLocation -from .inline.inlinequeryresultmpeg4gif import InlineQueryResultMpeg4Gif -from .inline.inlinequeryresultphoto import InlineQueryResultPhoto -from .inline.inlinequeryresultvenue import InlineQueryResultVenue -from .inline.inlinequeryresultvideo import InlineQueryResultVideo -from .inline.inlinequeryresultvoice import InlineQueryResultVoice -from .inline.inlinequeryresultgame import InlineQueryResultGame -from .inline.inputtextmessagecontent import InputTextMessageContent -from .inline.inputlocationmessagecontent import InputLocationMessageContent -from .inline.inputvenuemessagecontent import InputVenueMessageContent -from .payment.labeledprice import LabeledPrice -from .inline.inputinvoicemessagecontent import InputInvoiceMessageContent -from .inline.inputcontactmessagecontent import InputContactMessageContent -from .payment.shippingoption import ShippingOption -from .payment.precheckoutquery import PreCheckoutQuery -from .payment.shippingquery import ShippingQuery -from .webhookinfo import WebhookInfo -from .games.gamehighscore import GameHighScore -from .update import Update -from .files.inputmedia import ( - InputMedia, - InputMediaVideo, - InputMediaPhoto, - InputMediaAnimation, - InputMediaAudio, - InputMediaDocument, -) -from .constants import ( - MAX_MESSAGE_LENGTH, - MAX_CAPTION_LENGTH, - SUPPORTED_WEBHOOK_PORTS, - MAX_FILESIZE_DOWNLOAD, - MAX_FILESIZE_UPLOAD, - MAX_MESSAGES_PER_SECOND_PER_CHAT, - MAX_MESSAGES_PER_SECOND, - MAX_MESSAGES_PER_MINUTE_PER_GROUP, -) -from .passport.passportelementerrors import ( - PassportElementError, - PassportElementErrorDataField, - PassportElementErrorFile, - PassportElementErrorFiles, - PassportElementErrorFrontSide, - PassportElementErrorReverseSide, - PassportElementErrorSelfie, - PassportElementErrorTranslationFile, - PassportElementErrorTranslationFiles, - PassportElementErrorUnspecified, -) -from .passport.credentials import ( - Credentials, - DataCredentials, - SecureData, - SecureValue, - FileCredentials, - TelegramDecryptionError, -) -from .botcommandscope import ( - BotCommandScope, - BotCommandScopeDefault, - BotCommandScopeAllPrivateChats, - BotCommandScopeAllGroupChats, - BotCommandScopeAllChatAdministrators, - BotCommandScopeChat, - BotCommandScopeChatAdministrators, - BotCommandScopeChatMember, -) -from .bot import Bot -from .version import __version__, bot_api_version # noqa: F401 - -__author__ = 'devs@python-telegram-bot.org' - -__all__ = ( # Keep this alphabetically ordered - 'Animation', - 'Audio', - 'Bot', - 'BotCommand', - 'BotCommandScope', - 'BotCommandScopeAllChatAdministrators', - 'BotCommandScopeAllGroupChats', - 'BotCommandScopeAllPrivateChats', - 'BotCommandScopeChat', - 'BotCommandScopeChatAdministrators', - 'BotCommandScopeChatMember', - 'BotCommandScopeDefault', - 'CallbackGame', - 'CallbackQuery', - 'Chat', - 'ChatAction', - 'ChatInviteLink', - 'ChatJoinRequest', - 'ChatLocation', - 'ChatMember', - 'ChatMemberOwner', - 'ChatMemberAdministrator', - 'ChatMemberMember', - 'ChatMemberRestricted', - 'ChatMemberLeft', - 'ChatMemberBanned', - 'ChatMemberUpdated', - 'ChatPermissions', - 'ChatPhoto', - 'ChosenInlineResult', - 'Contact', - 'Credentials', - 'DataCredentials', - 'Dice', - 'Document', - 'EncryptedCredentials', - 'EncryptedPassportElement', - 'File', - 'FileCredentials', - 'ForceReply', - 'Game', - 'GameHighScore', - 'IdDocumentData', - 'InlineKeyboardButton', - 'InlineKeyboardMarkup', - 'InlineQuery', - 'InlineQueryResult', - 'InlineQueryResultArticle', - 'InlineQueryResultAudio', - 'InlineQueryResultCachedAudio', - 'InlineQueryResultCachedDocument', - 'InlineQueryResultCachedGif', - 'InlineQueryResultCachedMpeg4Gif', - 'InlineQueryResultCachedPhoto', - 'InlineQueryResultCachedSticker', - 'InlineQueryResultCachedVideo', - 'InlineQueryResultCachedVoice', - 'InlineQueryResultContact', - 'InlineQueryResultDocument', - 'InlineQueryResultGame', - 'InlineQueryResultGif', - 'InlineQueryResultLocation', - 'InlineQueryResultMpeg4Gif', - 'InlineQueryResultPhoto', - 'InlineQueryResultVenue', - 'InlineQueryResultVideo', - 'InlineQueryResultVoice', - 'InputContactMessageContent', - 'InputFile', - 'InputInvoiceMessageContent', - 'InputLocationMessageContent', - 'InputMedia', - 'InputMediaAnimation', - 'InputMediaAudio', - 'InputMediaDocument', - 'InputMediaPhoto', - 'InputMediaVideo', - 'InputMessageContent', - 'InputTextMessageContent', - 'InputVenueMessageContent', - 'Invoice', - 'KeyboardButton', - 'KeyboardButtonPollType', - 'LabeledPrice', - 'Location', - 'LoginUrl', - 'MAX_CAPTION_LENGTH', - 'MAX_FILESIZE_DOWNLOAD', - 'MAX_FILESIZE_UPLOAD', - 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', - 'MAX_MESSAGES_PER_SECOND', - 'MAX_MESSAGES_PER_SECOND_PER_CHAT', - 'MAX_MESSAGE_LENGTH', - 'MaskPosition', - 'Message', - 'MessageAutoDeleteTimerChanged', - 'MessageEntity', - 'MessageId', - 'OrderInfo', - 'ParseMode', - 'PassportData', - 'PassportElementError', - 'PassportElementErrorDataField', - 'PassportElementErrorFile', - 'PassportElementErrorFiles', - 'PassportElementErrorFrontSide', - 'PassportElementErrorReverseSide', - 'PassportElementErrorSelfie', - 'PassportElementErrorTranslationFile', - 'PassportElementErrorTranslationFiles', - 'PassportElementErrorUnspecified', - 'PassportFile', - 'PersonalDetails', - 'PhotoSize', - 'Poll', - 'PollAnswer', - 'PollOption', - 'PreCheckoutQuery', - 'ProximityAlertTriggered', - 'ReplyKeyboardMarkup', - 'ReplyKeyboardRemove', - 'ReplyMarkup', - 'ResidentialAddress', - 'SUPPORTED_WEBHOOK_PORTS', - 'SecureData', - 'SecureValue', - 'ShippingAddress', - 'ShippingOption', - 'ShippingQuery', - 'Sticker', - 'StickerSet', - 'SuccessfulPayment', - 'TelegramDecryptionError', - 'TelegramError', - 'TelegramObject', - 'Update', - 'User', - 'UserProfilePhotos', - 'Venue', - 'Video', - 'VideoNote', - 'Voice', - 'VoiceChatStarted', - 'VoiceChatEnded', - 'VoiceChatScheduled', - 'VoiceChatParticipantsInvited', - 'WebhookInfo', -) diff --git a/telegramer/include/telegram/__main__.py b/telegramer/include/telegram/__main__.py deleted file mode 100644 index 2e7a7de..0000000 --- a/telegramer/include/telegram/__main__.py +++ /dev/null @@ -1,54 +0,0 @@ -# !/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0114 -import subprocess -import sys -from typing import Optional - -import certifi - -from . import __version__ as telegram_ver -from .constants import BOT_API_VERSION - - -def _git_revision() -> Optional[str]: - try: - output = subprocess.check_output( # skipcq: BAN-B607 - ["git", "describe", "--long", "--tags"], stderr=subprocess.STDOUT - ) - except (subprocess.SubprocessError, OSError): - return None - return output.decode().strip() - - -def print_ver_info() -> None: # skipcq: PY-D0003 - git_revision = _git_revision() - print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else '')) - print(f'Bot API {BOT_API_VERSION}') - print(f'certifi {certifi.__version__}') # type: ignore[attr-defined] - sys_version = sys.version.replace('\n', ' ') - print(f'Python {sys_version}') - - -def main() -> None: # skipcq: PY-D0003 - print_ver_info() - - -if __name__ == '__main__': - main() diff --git a/telegramer/include/telegram/base.py b/telegramer/include/telegram/base.py deleted file mode 100644 index f119d96..0000000 --- a/telegramer/include/telegram/base.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""Base class for Telegram Objects.""" -try: - import ujson as json -except ImportError: - import json # type: ignore[no-redef] - -import warnings -from typing import TYPE_CHECKING, List, Optional, Tuple, Type, TypeVar - -from telegram.utils.types import JSONDict -from telegram.utils.deprecate import set_new_attribute_deprecated - -if TYPE_CHECKING: - from telegram import Bot - -TO = TypeVar('TO', bound='TelegramObject', covariant=True) - - -class TelegramObject: - """Base class for most Telegram objects.""" - - _id_attrs: Tuple[object, ...] = () - - # Adding slots reduces memory usage & allows for faster attribute access. - # Only instance variables should be added to __slots__. - # We add __dict__ here for backward compatibility & also to avoid repetition for subclasses. - __slots__ = ('__dict__',) - - def __str__(self) -> str: - return str(self.to_dict()) - - def __getitem__(self, item: str) -> object: - return getattr(self, item, None) - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - @staticmethod - def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]: - return None if data is None else data.copy() - - @classmethod - def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]: - """Converts JSON data to a Telegram object. - - Args: - data (Dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with this object. - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - if data is None: - return None - - if cls == TelegramObject: - return cls() - return cls(bot=bot, **data) # type: ignore[call-arg] - - @classmethod - def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]: - """Converts JSON data to a list of Telegram objects. - - Args: - data (Dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with these objects. - - Returns: - A list of Telegram objects. - - """ - if not data: - return [] - - return [cls.de_json(d, bot) for d in data] - - def to_json(self) -> str: - """Gives a JSON representation of object. - - Returns: - :obj:`str` - """ - return json.dumps(self.to_dict()) - - def to_dict(self) -> JSONDict: - """Gives representation of object as :obj:`dict`. - - Returns: - :obj:`dict` - """ - data = {} - - # We want to get all attributes for the class, using self.__slots__ only includes the - # attributes used by that class itself, and not its superclass(es). Hence we get its MRO - # and then get their attributes. The `[:-2]` slice excludes the `object` class & the - # TelegramObject class itself. - attrs = {attr for cls in self.__class__.__mro__[:-2] for attr in cls.__slots__} - for key in attrs: - if key == 'bot' or key.startswith('_'): - continue - - value = getattr(self, key, None) - if value is not None: - if hasattr(value, 'to_dict'): - data[key] = value.to_dict() - else: - data[key] = value - - if data.get('from_user'): - data['from'] = data.pop('from_user', None) - return data - - def __eq__(self, other: object) -> bool: - if isinstance(other, self.__class__): - if self._id_attrs == (): - warnings.warn( - f"Objects of type {self.__class__.__name__} can not be meaningfully tested for" - " equivalence." - ) - if other._id_attrs == (): - warnings.warn( - f"Objects of type {other.__class__.__name__} can not be meaningfully tested" - " for equivalence." - ) - return self._id_attrs == other._id_attrs - return super().__eq__(other) # pylint: disable=no-member - - def __hash__(self) -> int: - if self._id_attrs: - return hash((self.__class__, self._id_attrs)) # pylint: disable=no-member - return super().__hash__() diff --git a/telegramer/include/telegram/bot.py b/telegramer/include/telegram/bot.py deleted file mode 100644 index cc983d5..0000000 --- a/telegramer/include/telegram/bot.py +++ /dev/null @@ -1,5861 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=E0611,E0213,E1102,E1101,R0913,R0904 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Bot.""" - -import functools -import logging -import warnings -from datetime import datetime - -from typing import ( - TYPE_CHECKING, - Callable, - List, - Optional, - Tuple, - TypeVar, - Union, - no_type_check, - Dict, - cast, - Sequence, -) - -try: - import ujson as json -except ImportError: - import json # type: ignore[no-redef] # noqa: F723 - -try: - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives import serialization - - CRYPTO_INSTALLED = True -except ImportError: - default_backend = None # type: ignore[assignment] - serialization = None # type: ignore[assignment] - CRYPTO_INSTALLED = False - -from telegram import ( - Animation, - Audio, - BotCommand, - BotCommandScope, - Chat, - ChatMember, - ChatPermissions, - ChatPhoto, - Contact, - Document, - File, - GameHighScore, - Location, - MaskPosition, - Message, - MessageId, - PassportElementError, - PhotoSize, - Poll, - ReplyMarkup, - ShippingOption, - Sticker, - StickerSet, - TelegramObject, - Update, - User, - UserProfilePhotos, - Venue, - Video, - VideoNote, - Voice, - WebhookInfo, - InlineKeyboardMarkup, - ChatInviteLink, -) -from telegram.constants import MAX_INLINE_QUERY_RESULTS -from telegram.error import InvalidToken, TelegramError -from telegram.utils.deprecate import TelegramDeprecationWarning -from telegram.utils.helpers import ( - DEFAULT_NONE, - DefaultValue, - to_timestamp, - is_local_file, - parse_file_input, - DEFAULT_20, -) -from telegram.utils.request import Request -from telegram.utils.types import FileInput, JSONDict, ODVInput, DVInput - -if TYPE_CHECKING: - from telegram.ext import Defaults - from telegram import ( - InputMediaAudio, - InputMediaDocument, - InputMediaPhoto, - InputMediaVideo, - InputMedia, - InlineQueryResult, - LabeledPrice, - MessageEntity, - ) - -RT = TypeVar('RT') - - -def log( # skipcq: PY-D0003 - func: Callable[..., RT], *args: object, **kwargs: object # pylint: disable=W0613 -) -> Callable[..., RT]: - logger = logging.getLogger(func.__module__) - - @functools.wraps(func) - def decorator(*args: object, **kwargs: object) -> RT: # pylint: disable=W0613 - logger.debug('Entering: %s', func.__name__) - result = func(*args, **kwargs) - logger.debug(result) - logger.debug('Exiting: %s', func.__name__) - return result - - return decorator - - -class Bot(TelegramObject): - """This object represents a Telegram Bot. - - .. versionadded:: 13.2 - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`bot` is equal. - - Note: - Most bot methods have the argument ``api_kwargs`` which allows to pass arbitrary keywords - to the Telegram API. This can be used to access new features of the API before they were - incorporated into PTB. However, this is not guaranteed to work, i.e. it will fail for - passing files. - - Args: - token (:obj:`str`): Bot's unique authentication. - base_url (:obj:`str`, optional): Telegram Bot API service URL. - base_file_url (:obj:`str`, optional): Telegram Bot API file URL. - request (:obj:`telegram.utils.request.Request`, optional): Pre initialized - :obj:`telegram.utils.request.Request`. - private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data. - private_key_password (:obj:`bytes`, optional): Password for above private key. - defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to - be used if not set explicitly in the bot methods. - - .. deprecated:: 13.6 - Passing :class:`telegram.ext.Defaults` to :class:`telegram.Bot` is deprecated. If - you want to use :class:`telegram.ext.Defaults`, please use - :class:`telegram.ext.ExtBot` instead. - - """ - - __slots__ = ( - 'token', - 'base_url', - 'base_file_url', - 'private_key', - 'defaults', - '_bot', - '_commands', - '_request', - 'logger', - ) - - def __init__( - self, - token: str, - base_url: str = None, - base_file_url: str = None, - request: 'Request' = None, - private_key: bytes = None, - private_key_password: bytes = None, - defaults: 'Defaults' = None, - ): - self.token = self._validate_token(token) - - # Gather default - self.defaults = defaults - - if self.defaults: - warnings.warn( - 'Passing Defaults to telegram.Bot is deprecated. Use telegram.ext.ExtBot instead.', - TelegramDeprecationWarning, - stacklevel=3, - ) - - if base_url is None: - base_url = 'https://api.telegram.org/bot' - - if base_file_url is None: - base_file_url = 'https://api.telegram.org/file/bot' - - self.base_url = str(base_url) + str(self.token) - self.base_file_url = str(base_file_url) + str(self.token) - self._bot: Optional[User] = None - self._commands: Optional[List[BotCommand]] = None - self._request = request or Request() - self.private_key = None - self.logger = logging.getLogger(__name__) - - if private_key: - if not CRYPTO_INSTALLED: - raise RuntimeError( - 'To use Telegram Passports, PTB must be installed via `pip install ' - 'python-telegram-bot[passport]`.' - ) - self.private_key = serialization.load_pem_private_key( - private_key, password=private_key_password, backend=default_backend() - ) - - # The ext_bot argument is a little hack to get warnings handled correctly. - # It's not very clean, but the warnings will be dropped at some point anyway. - def __setattr__(self, key: str, value: object, ext_bot: bool = False) -> None: - if issubclass(self.__class__, Bot) and self.__class__ is not Bot and not ext_bot: - object.__setattr__(self, key, value) - return - super().__setattr__(key, value) - - def _insert_defaults( - self, data: Dict[str, object], timeout: ODVInput[float] - ) -> Optional[float]: - """ - Inserts the defaults values for optional kwargs for which tg.ext.Defaults provides - convenience functionality, i.e. the kwargs with a tg.utils.helpers.DefaultValue default - - data is edited in-place. As timeout is not passed via the kwargs, it needs to be passed - separately and gets returned. - - This can only work, if all kwargs that may have defaults are passed in data! - """ - effective_timeout = DefaultValue.get_value(timeout) - - # If we have no Defaults, we just need to replace DefaultValue instances - # with the actual value - if not self.defaults: - data.update((key, DefaultValue.get_value(value)) for key, value in data.items()) - return effective_timeout - - # if we have Defaults, we replace all DefaultValue instances with the relevant - # Defaults value. If there is none, we fall back to the default value of the bot method - for key, val in data.items(): - if isinstance(val, DefaultValue): - data[key] = self.defaults.api_defaults.get(key, val.value) - - if isinstance(timeout, DefaultValue): - # If we get here, we use Defaults.timeout, unless that's not set, which is the - # case if isinstance(self.defaults.timeout, DefaultValue) - return ( - self.defaults.timeout - if not isinstance(self.defaults.timeout, DefaultValue) - else effective_timeout - ) - return effective_timeout - - def _post( - self, - endpoint: str, - data: JSONDict = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[bool, JSONDict, None]: - if data is None: - data = {} - - if api_kwargs: - if data: - data.update(api_kwargs) - else: - data = api_kwargs - - # Insert is in-place, so no return value for data - if endpoint != 'getUpdates': - effective_timeout = self._insert_defaults(data, timeout) - else: - effective_timeout = cast(float, timeout) - # Drop any None values because Telegram doesn't handle them well - data = {key: value for key, value in data.items() if value is not None} - - return self.request.post( - f'{self.base_url}/{endpoint}', data=data, timeout=effective_timeout - ) - - def _message( - self, - endpoint: str, - data: JSONDict, - reply_to_message_id: int = None, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_markup: ReplyMarkup = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> Union[bool, Message]: - if reply_to_message_id is not None: - data['reply_to_message_id'] = reply_to_message_id - - if protect_content: - data['protect_content'] = protect_content - - # We don't check if (DEFAULT_)None here, so that _put is able to insert the defaults - # correctly, if necessary - data['disable_notification'] = disable_notification - data['allow_sending_without_reply'] = allow_sending_without_reply - - if reply_markup is not None: - if isinstance(reply_markup, ReplyMarkup): - # We need to_json() instead of to_dict() here, because reply_markups may be - # attached to media messages, which aren't json dumped by utils.request - data['reply_markup'] = reply_markup.to_json() - else: - data['reply_markup'] = reply_markup - - if data.get('media') and (data['media'].parse_mode == DEFAULT_NONE): - if self.defaults: - data['media'].parse_mode = DefaultValue.get_value(self.defaults.parse_mode) - else: - data['media'].parse_mode = None - - result = self._post(endpoint, data, timeout=timeout, api_kwargs=api_kwargs) - - if result is True: - return result - - return Message.de_json(result, self) # type: ignore[return-value, arg-type] - - @property - def request(self) -> Request: # skip-cq: PY-D0003 - return self._request - - @staticmethod - def _validate_token(token: str) -> str: - """A very basic validation on token.""" - if any(x.isspace() for x in token): - raise InvalidToken() - - left, sep, _right = token.partition(':') - if (not sep) or (not left.isdigit()) or (len(left) < 3): - raise InvalidToken() - - return token - - @property - def bot(self) -> User: - """:class:`telegram.User`: User instance for the bot as returned by :meth:`get_me`.""" - if self._bot is None: - self._bot = self.get_me() - return self._bot - - @property - def id(self) -> int: # pylint: disable=C0103 - """:obj:`int`: Unique identifier for this bot.""" - return self.bot.id - - @property - def first_name(self) -> str: - """:obj:`str`: Bot's first name.""" - return self.bot.first_name - - @property - def last_name(self) -> str: - """:obj:`str`: Optional. Bot's last name.""" - return self.bot.last_name # type: ignore - - @property - def username(self) -> str: - """:obj:`str`: Bot's username.""" - return self.bot.username # type: ignore - - @property - def link(self) -> str: - """:obj:`str`: Convenience property. Returns the t.me link of the bot.""" - return f"https://t.me/{self.username}" - - @property - def can_join_groups(self) -> bool: - """:obj:`bool`: Bot's :attr:`telegram.User.can_join_groups` attribute.""" - return self.bot.can_join_groups # type: ignore - - @property - def can_read_all_group_messages(self) -> bool: - """:obj:`bool`: Bot's :attr:`telegram.User.can_read_all_group_messages` attribute.""" - return self.bot.can_read_all_group_messages # type: ignore - - @property - def supports_inline_queries(self) -> bool: - """:obj:`bool`: Bot's :attr:`telegram.User.supports_inline_queries` attribute.""" - return self.bot.supports_inline_queries # type: ignore - - @property - def commands(self) -> List[BotCommand]: - """ - List[:class:`BotCommand`]: Bot's commands as available in the default scope. - - .. deprecated:: 13.7 - This property has been deprecated since there can be different commands available for - different scopes. - """ - warnings.warn( - "Bot.commands has been deprecated since there can be different command " - "lists for different scopes.", - TelegramDeprecationWarning, - stacklevel=2, - ) - - if self._commands is None: - self._commands = self.get_my_commands() - return self._commands - - @property - def name(self) -> str: - """:obj:`str`: Bot's @username.""" - return f'@{self.username}' - - @log - def get_me(self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None) -> User: - """A simple method for testing your bot's auth token. Requires no parameters. - - Args: - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.User`: A :class:`telegram.User` instance representing that bot if the - credentials are valid, :obj:`None` otherwise. - - Raises: - :class:`telegram.error.TelegramError` - - """ - result = self._post('getMe', timeout=timeout, api_kwargs=api_kwargs) - - self._bot = User.de_json(result, self) # type: ignore[return-value, arg-type] - - return self._bot # type: ignore[return-value] - - @log - def send_message( - self, - chat_id: Union[int, str], - text: str, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> Message: - """Use this method to send text messages. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - text (:obj:`str`): Text of the message to be sent. Max 4096 characters after entities - parsing. Also found as :attr:`telegram.constants.MAX_MESSAGE_LENGTH`. - parse_mode (:obj:`str`): Send Markdown or HTML, if you want Telegram apps to show bold, - italic, fixed-width text or inline URLs in your bot's message. See the constants in - :class:`telegram.ParseMode` for the available modes. - entities (List[:class:`telegram.MessageEntity`], optional): List of special entities - that appear in message text, which can be specified instead of :attr:`parse_mode`. - disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in - this message. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of sent messages from - forwarding and saving. - - .. versionadded:: 13.10 - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. - A JSON-serialized object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'text': text, - 'parse_mode': parse_mode, - 'disable_web_page_preview': disable_web_page_preview, - } - - if entities: - data['entities'] = [me.to_dict() for me in entities] - - return self._message( # type: ignore[return-value] - 'sendMessage', - data, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def delete_message( - self, - chat_id: Union[str, int], - message_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to delete a message, including service messages, with the following - limitations: - - - A message can only be deleted if it was sent less than 48 hours ago. - - A dice message in a private chat can only be deleted if it was sent more than 24 - hours ago. - - Bots can delete outgoing messages in private chats, groups, and supergroups. - - Bots can delete incoming messages in private chats. - - Bots granted :attr:`telegram.ChatMember.can_post_messages` permissions can delete - outgoing messages in channels. - - If the bot is an administrator of a group, it can delete any message there. - - If the bot has :attr:`telegram.ChatMember.can_delete_messages` permission in a - supergroup or a channel, it can delete any message there. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - message_id (:obj:`int`): Identifier of the message to delete. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'message_id': message_id} - - result = self._post('deleteMessage', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def forward_message( - self, - chat_id: Union[int, str], - from_chat_id: Union[str, int], - message_id: int, - disable_notification: DVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> Message: - """Use this method to forward messages of any kind. Service messages can't be forwarded. - - Note: - Since the release of Bot API 5.5 it can be impossible to forward messages from - some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and - :attr:`telegram.Chat.has_protected_content` to check this. - - As a workaround, it is still possible to use :meth:`copy_message`. However, this - behaviour is undocumented and might be changed by Telegram. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - from_chat_id (:obj:`int` | :obj:`str`): Unique identifier for the chat where the - original message was sent (or channel username in the format ``@channelusername``). - message_id (:obj:`int`): Message identifier in the chat specified in from_chat_id. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {} - - if chat_id: - data['chat_id'] = chat_id - if from_chat_id: - data['from_chat_id'] = from_chat_id - if message_id: - data['message_id'] = message_id - return self._message( # type: ignore[return-value] - 'forwardMessage', - data, - disable_notification=disable_notification, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_photo( - self, - chat_id: Union[int, str], - photo: Union[FileInput, 'PhotoSize'], - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> Message: - """Use this method to send photos. - - Note: - The photo argument can be either a file_id, an URL or a file from disk - ``open(filename, 'rb')`` - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - photo (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.PhotoSize`): Photo to send. - Pass a file_id as String to send a photo that exists on the Telegram servers - (recommended), pass an HTTP URL as a String for Telegram to get a photo from the - Internet, or upload a new photo using multipart/form-data. Lastly you can pass - an existing :class:`telegram.PhotoSize` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the photo, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - caption (:obj:`str`, optional): Photo caption (may also be used when resending photos - by file_id), 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in the media caption. See the - constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'photo': parse_file_input(photo, PhotoSize, filename=filename), - 'parse_mode': parse_mode, - } - - if caption: - data['caption'] = caption - - if caption_entities: - data['caption_entities'] = [me.to_dict() for me in caption_entities] - - return self._message( # type: ignore[return-value] - 'sendPhoto', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_audio( - self, - chat_id: Union[int, str], - audio: Union[FileInput, 'Audio'], - duration: int = None, - performer: str = None, - title: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send audio files, if you want Telegram clients to display them in the - music player. Your audio must be in the .mp3 or .m4a format. - - Bots can currently send audio files of up to 50 MB in size, this limit may be changed in - the future. - - For sending voice messages, use the :meth:`send_voice` method instead. - - Note: - The audio argument can be either a file_id, an URL or a file from disk - ``open(filename, 'rb')`` - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - audio (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Audio`): Audio file to send. - Pass a file_id as String to send an audio file that exists on the Telegram servers - (recommended), pass an HTTP URL as a String for Telegram to get an audio file from - the Internet, or upload a new one using multipart/form-data. Lastly you can pass - an existing :class:`telegram.Audio` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the audio, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - caption (:obj:`str`, optional): Audio caption, 0-1024 characters after entities - parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in the media caption. See the - constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - duration (:obj:`int`, optional): Duration of sent audio in seconds. - performer (:obj:`str`, optional): Performer. - title (:obj:`str`, optional): Track name. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail - of the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'audio': parse_file_input(audio, Audio, filename=filename), - 'parse_mode': parse_mode, - } - - if duration: - data['duration'] = duration - if performer: - data['performer'] = performer - if title: - data['title'] = title - if caption: - data['caption'] = caption - - if caption_entities: - data['caption_entities'] = [me.to_dict() for me in caption_entities] - if thumb: - data['thumb'] = parse_file_input(thumb, attach=True) - - return self._message( # type: ignore[return-value] - 'sendAudio', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_document( - self, - chat_id: Union[int, str], - document: Union[FileInput, 'Document'], - filename: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - disable_content_type_detection: bool = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send general files. - - Bots can currently send files of any type of up to 50 MB in size, this limit may be - changed in the future. - - Note: - The document argument can be either a file_id, an URL or a file from disk - ``open(filename, 'rb')`` - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - document (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Document`): File to send. - Pass a file_id as String to send a file that exists on the Telegram servers - (recommended), pass an HTTP URL as a String for Telegram to get a file from the - Internet, or upload a new one using multipart/form-data. Lastly you can pass - an existing :class:`telegram.Document` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the document, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - caption (:obj:`str`, optional): Document caption (may also be used when resending - documents by file_id), 0-1024 characters after entities parsing. - disable_content_type_detection (:obj:`bool`, optional): Disables automatic server-side - content type detection for files uploaded using multipart/form-data. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in the media caption. See the - constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail - of the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'document': parse_file_input(document, Document, filename=filename), - 'parse_mode': parse_mode, - } - - if caption: - data['caption'] = caption - - if caption_entities: - data['caption_entities'] = [me.to_dict() for me in caption_entities] - if disable_content_type_detection is not None: - data['disable_content_type_detection'] = disable_content_type_detection - if thumb: - data['thumb'] = parse_file_input(thumb, attach=True) - - return self._message( # type: ignore[return-value] - 'sendDocument', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_sticker( - self, - chat_id: Union[int, str], - sticker: Union[FileInput, 'Sticker'], - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send static ``.WEBP``, animated ``.TGS``, or video ``.WEBM`` stickers. - - Note: - The sticker argument can be either a file_id, an URL or a file from disk - ``open(filename, 'rb')`` - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - sticker (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Sticker`): Sticker to send. - Pass a file_id as String to send a file that exists on the Telegram servers - (recommended), pass an HTTP URL as a String for Telegram to get a .webp file from - the Internet, or upload a new one using multipart/form-data. Lastly you can pass - an existing :class:`telegram.Sticker` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'sticker': parse_file_input(sticker, Sticker)} - - return self._message( # type: ignore[return-value] - 'sendSticker', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_video( - self, - chat_id: Union[int, str], - video: Union[FileInput, 'Video'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - width: int = None, - height: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - supports_streaming: bool = None, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send video files, Telegram clients support mp4 videos - (other formats may be sent as Document). - - Bots can currently send video files of up to 50 MB in size, this limit may be changed in - the future. - - Note: - * The video argument can be either a file_id, an URL or a file from disk - ``open(filename, 'rb')`` - * ``thumb`` will be ignored for small video files, for which Telegram can easily - generate thumb nails. However, this behaviour is undocumented and might be changed - by Telegram. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - video (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Video`): Video file to send. - Pass a file_id as String to send an video file that exists on the Telegram servers - (recommended), pass an HTTP URL as a String for Telegram to get an video file from - the Internet, or upload a new one using multipart/form-data. Lastly you can pass - an existing :class:`telegram.Video` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the video, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - duration (:obj:`int`, optional): Duration of sent video in seconds. - width (:obj:`int`, optional): Video width. - height (:obj:`int`, optional): Video height. - caption (:obj:`str`, optional): Video caption (may also be used when resending videos - by file_id), 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in the media caption. See the - constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - supports_streaming (:obj:`bool`, optional): Pass :obj:`True`, if the uploaded video is - suitable for streaming. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail - of the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'video': parse_file_input(video, Video, filename=filename), - 'parse_mode': parse_mode, - } - - if duration: - data['duration'] = duration - if caption: - data['caption'] = caption - if caption_entities: - data['caption_entities'] = [me.to_dict() for me in caption_entities] - if supports_streaming: - data['supports_streaming'] = supports_streaming - if width: - data['width'] = width - if height: - data['height'] = height - if thumb: - data['thumb'] = parse_file_input(thumb, attach=True) - - return self._message( # type: ignore[return-value] - 'sendVideo', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_video_note( - self, - chat_id: Union[int, str], - video_note: Union[FileInput, 'VideoNote'], - duration: int = None, - length: int = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - filename: str = None, - protect_content: bool = None, - ) -> Message: - """ - As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. - Use this method to send video messages. - - Note: - * The video_note argument can be either a file_id or a file from disk - ``open(filename, 'rb')`` - * ``thumb`` will be ignored for small video files, for which Telegram can easily - generate thumb nails. However, this behaviour is undocumented and might be changed - by Telegram. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - video_note (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.VideoNote`): Video note - to send. Pass a file_id as String to send a video note that exists on the Telegram - servers (recommended) or upload a new video using multipart/form-data. Or you can - pass an existing :class:`telegram.VideoNote` object to send. Sending video notes by - a URL is currently unsupported. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the video note, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - duration (:obj:`int`, optional): Duration of sent video in seconds. - length (:obj:`int`, optional): Video width and height, i.e. diameter of the video - message. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail - of the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'video_note': parse_file_input(video_note, VideoNote, filename=filename), - } - - if duration is not None: - data['duration'] = duration - if length is not None: - data['length'] = length - if thumb: - data['thumb'] = parse_file_input(thumb, attach=True) - - return self._message( # type: ignore[return-value] - 'sendVideoNote', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_animation( - self, - chat_id: Union[int, str], - animation: Union[FileInput, 'Animation'], - duration: int = None, - width: int = None, - height: int = None, - thumb: FileInput = None, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). - Bots can currently send animation files of up to 50 MB in size, this limit may be changed - in the future. - - Note: - ``thumb`` will be ignored for small files, for which Telegram can easily - generate thumb nails. However, this behaviour is undocumented and might be changed - by Telegram. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - animation (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Animation`): Animation to - send. Pass a file_id as String to send an animation that exists on the Telegram - servers (recommended), pass an HTTP URL as a String for Telegram to get an - animation from the Internet, or upload a new animation using multipart/form-data. - Lastly you can pass an existing :class:`telegram.Animation` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the animation, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - duration (:obj:`int`, optional): Duration of sent animation in seconds. - width (:obj:`int`, optional): Animation width. - height (:obj:`int`, optional): Animation height. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail - of the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - caption (:obj:`str`, optional): Animation caption (may also be used when resending - animations by file_id), 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in the media caption. See the - constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'animation': parse_file_input(animation, Animation, filename=filename), - 'parse_mode': parse_mode, - } - - if duration: - data['duration'] = duration - if width: - data['width'] = width - if height: - data['height'] = height - if thumb: - data['thumb'] = parse_file_input(thumb, attach=True) - if caption: - data['caption'] = caption - if caption_entities: - data['caption_entities'] = [me.to_dict() for me in caption_entities] - - return self._message( # type: ignore[return-value] - 'sendAnimation', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_voice( - self, - chat_id: Union[int, str], - voice: Union[FileInput, 'Voice'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send audio files, if you want Telegram clients to display the file - as a playable voice message. For this to work, your audio must be in an .ogg file - encoded with OPUS (other formats may be sent as Audio or Document). Bots can currently - send voice messages of up to 50 MB in size, this limit may be changed in the future. - - Note: - The voice argument can be either a file_id, an URL or a file from disk - ``open(filename, 'rb')`` - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - voice (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Voice`): Voice file to send. - Pass a file_id as String to send an voice file that exists on the Telegram servers - (recommended), pass an HTTP URL as a String for Telegram to get an voice file from - the Internet, or upload a new one using multipart/form-data. Lastly you can pass - an existing :class:`telegram.Voice` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the voice, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - caption (:obj:`str`, optional): Voice message caption, 0-1024 characters after entities - parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in the media caption. See the - constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - duration (:obj:`int`, optional): Duration of the voice message in seconds. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'voice': parse_file_input(voice, Voice, filename=filename), - 'parse_mode': parse_mode, - } - - if duration: - data['duration'] = duration - if caption: - data['caption'] = caption - - if caption_entities: - data['caption_entities'] = [me.to_dict() for me in caption_entities] - - return self._message( # type: ignore[return-value] - 'sendVoice', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_media_group( - self, - chat_id: Union[int, str], - media: List[ - Union['InputMediaAudio', 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo'] - ], - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> List[Message]: - """Use this method to send a group of photos or videos as an album. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - media (List[:class:`telegram.InputMediaAudio`, :class:`telegram.InputMediaDocument`, \ - :class:`telegram.InputMediaPhoto`, :class:`telegram.InputMediaVideo`]): An array - describing messages to be sent, must include 2–10 items. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - List[:class:`telegram.Message`]: An array of the sent Messages. - - Raises: - :class:`telegram.error.TelegramError` - """ - data: JSONDict = { - 'chat_id': chat_id, - 'media': media, - 'disable_notification': disable_notification, - 'allow_sending_without_reply': allow_sending_without_reply, - } - - for med in data['media']: - if med.parse_mode == DEFAULT_NONE: - if self.defaults: - med.parse_mode = DefaultValue.get_value(self.defaults.parse_mode) - else: - med.parse_mode = None - - if reply_to_message_id: - data['reply_to_message_id'] = reply_to_message_id - - if protect_content: - data['protect_content'] = protect_content - - result = self._post('sendMediaGroup', data, timeout=timeout, api_kwargs=api_kwargs) - - return Message.de_list(result, self) # type: ignore - - @log - def send_location( - self, - chat_id: Union[int, str], - latitude: float = None, - longitude: float = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - location: Location = None, - live_period: int = None, - api_kwargs: JSONDict = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> Message: - """Use this method to send point on the map. - - Note: - You can either supply a :obj:`latitude` and :obj:`longitude` or a :obj:`location`. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - latitude (:obj:`float`, optional): Latitude of location. - longitude (:obj:`float`, optional): Longitude of location. - location (:class:`telegram.Location`, optional): The location to send. - horizontal_accuracy (:obj:`int`, optional): The radius of uncertainty for the location, - measured in meters; 0-1500. - live_period (:obj:`int`, optional): Period in seconds for which the location will be - updated, should be between 60 and 86400. - heading (:obj:`int`, optional): For live locations, a direction in which the user is - moving, in degrees. Must be between 1 and 360 if specified. - proximity_alert_radius (:obj:`int`, optional): For live locations, a maximum distance - for proximity alerts about approaching another chat member, in meters. Must be - between 1 and 100000 if specified. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - if not ((latitude is not None and longitude is not None) or location): - raise ValueError( - "Either location or latitude and longitude must be passed as argument." - ) - - if not (latitude is not None or longitude is not None) ^ bool(location): - raise ValueError( - "Either location or latitude and longitude must be passed as argument. Not both." - ) - - if isinstance(location, Location): - latitude = location.latitude - longitude = location.longitude - - data: JSONDict = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude} - - if live_period: - data['live_period'] = live_period - if horizontal_accuracy: - data['horizontal_accuracy'] = horizontal_accuracy - if heading: - data['heading'] = heading - if proximity_alert_radius: - data['proximity_alert_radius'] = proximity_alert_radius - - return self._message( # type: ignore[return-value] - 'sendLocation', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def edit_message_live_location( - self, - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - latitude: float = None, - longitude: float = None, - location: Location = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - ) -> Union[Message, bool]: - """Use this method to edit live location messages sent by the bot or via the bot - (for inline bots). A location can be edited until its :attr:`telegram.Location.live_period` - expires or editing is explicitly disabled by a call to :meth:`stop_message_live_location`. - - Note: - You can either supply a :obj:`latitude` and :obj:`longitude` or a :obj:`location`. - - Args: - chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not - specified. Unique identifier for the target chat or username of the target channel - (in the format ``@channelusername``). - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the message to edit. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - latitude (:obj:`float`, optional): Latitude of location. - longitude (:obj:`float`, optional): Longitude of location. - location (:class:`telegram.Location`, optional): The location to send. - horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the - location, measured in meters; 0-1500. - heading (:obj:`int`, optional): Direction in which the user is moving, in degrees. Must - be between 1 and 360 if specified. - proximity_alert_radius (:obj:`int`, optional): Maximum distance for proximity alerts - about approaching another chat member, in meters. Must be between 1 and 100000 if - specified. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for a new inline keyboard. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, if edited message is not an inline message, the - edited message is returned, otherwise :obj:`True` is returned. - """ - if not (all([latitude, longitude]) or location): - raise ValueError( - "Either location or latitude and longitude must be passed as argument." - ) - if not (latitude is not None or longitude is not None) ^ bool(location): - raise ValueError( - "Either location or latitude and longitude must be passed as argument. Not both." - ) - - if isinstance(location, Location): - latitude = location.latitude - longitude = location.longitude - - data: JSONDict = {'latitude': latitude, 'longitude': longitude} - - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - if horizontal_accuracy: - data['horizontal_accuracy'] = horizontal_accuracy - if heading: - data['heading'] = heading - if proximity_alert_radius: - data['proximity_alert_radius'] = proximity_alert_radius - - return self._message( - 'editMessageLiveLocation', - data, - timeout=timeout, - reply_markup=reply_markup, - api_kwargs=api_kwargs, - ) - - @log - def stop_message_live_location( - self, - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """Use this method to stop updating a live location message sent by the bot or via the bot - (for inline bots) before live_period expires. - - Args: - chat_id (:obj:`int` | :obj:`str`): Required if inline_message_id is not specified. - Unique identifier for the target chat or username of the target channel - (in the format ``@channelusername``). - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the sent message with live location to stop. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for a new inline keyboard. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - sent Message is returned, otherwise :obj:`True` is returned. - """ - data: JSONDict = {} - - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - - return self._message( - 'stopMessageLiveLocation', - data, - timeout=timeout, - reply_markup=reply_markup, - api_kwargs=api_kwargs, - ) - - @log - def send_venue( - self, - chat_id: Union[int, str], - latitude: float = None, - longitude: float = None, - title: str = None, - address: str = None, - foursquare_id: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - venue: Venue = None, - foursquare_type: str = None, - api_kwargs: JSONDict = None, - google_place_id: str = None, - google_place_type: str = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> Message: - """Use this method to send information about a venue. - - Note: - * You can either supply :obj:`venue`, or :obj:`latitude`, :obj:`longitude`, - :obj:`title` and :obj:`address` and optionally :obj:`foursquare_id` and - :obj:`foursquare_type` or optionally :obj:`google_place_id` and - :obj:`google_place_type`. - * Foursquare details and Google Pace details are mutually exclusive. However, this - behaviour is undocumented and might be changed by Telegram. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - latitude (:obj:`float`, optional): Latitude of venue. - longitude (:obj:`float`, optional): Longitude of venue. - title (:obj:`str`, optional): Name of the venue. - address (:obj:`str`, optional): Address of the venue. - foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue. - foursquare_type (:obj:`str`, optional): Foursquare type of the venue, if known. - (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or - "food/icecream".) - google_place_id (:obj:`str`, optional): Google Places identifier of the venue. - google_place_type (:obj:`str`, optional): Google Places type of the venue. (See - `supported types \ - `_.) - venue (:class:`telegram.Venue`, optional): The venue to send. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - if not (venue or all([latitude, longitude, address, title])): - raise ValueError( - "Either venue or latitude, longitude, address and title must be" - "passed as arguments." - ) - - if isinstance(venue, Venue): - latitude = venue.location.latitude - longitude = venue.location.longitude - address = venue.address - title = venue.title - foursquare_id = venue.foursquare_id - foursquare_type = venue.foursquare_type - google_place_id = venue.google_place_id - google_place_type = venue.google_place_type - - data: JSONDict = { - 'chat_id': chat_id, - 'latitude': latitude, - 'longitude': longitude, - 'address': address, - 'title': title, - } - - if foursquare_id: - data['foursquare_id'] = foursquare_id - if foursquare_type: - data['foursquare_type'] = foursquare_type - if google_place_id: - data['google_place_id'] = google_place_id - if google_place_type: - data['google_place_type'] = google_place_type - - return self._message( # type: ignore[return-value] - 'sendVenue', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_contact( - self, - chat_id: Union[int, str], - phone_number: str = None, - first_name: str = None, - last_name: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - contact: Contact = None, - vcard: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> Message: - """Use this method to send phone contacts. - - Note: - You can either supply :obj:`contact` or :obj:`phone_number` and :obj:`first_name` - with optionally :obj:`last_name` and optionally :obj:`vcard`. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - phone_number (:obj:`str`, optional): Contact's phone number. - first_name (:obj:`str`, optional): Contact's first name. - last_name (:obj:`str`, optional): Contact's last name. - vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard, - 0-2048 bytes. - contact (:class:`telegram.Contact`, optional): The contact to send. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - if (not contact) and (not all([phone_number, first_name])): - raise ValueError( - "Either contact or phone_number and first_name must be passed as arguments." - ) - - if isinstance(contact, Contact): - phone_number = contact.phone_number - first_name = contact.first_name - last_name = contact.last_name - vcard = contact.vcard - - data: JSONDict = { - 'chat_id': chat_id, - 'phone_number': phone_number, - 'first_name': first_name, - } - - if last_name: - data['last_name'] = last_name - if vcard: - data['vcard'] = vcard - - return self._message( # type: ignore[return-value] - 'sendContact', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_game( - self, - chat_id: Union[int, str], - game_short_name: str, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> Message: - """Use this method to send a game. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat. - game_short_name (:obj:`str`): Short name of the game, serves as the unique identifier - for the game. Set up your games via `@BotFather `_. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for a new inline keyboard. If empty, one ‘Play game_title’ button will be - shown. If not empty, the first button must launch the game. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'game_short_name': game_short_name} - - return self._message( # type: ignore[return-value] - 'sendGame', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def send_chat_action( - self, - chat_id: Union[str, int], - action: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method when you need to tell the user that something is happening on the bot's - side. The status is set for 5 seconds or less (when a message arrives from your bot, - Telegram clients clear its typing status). Telegram only recommends using this method when - a response from the bot will take a noticeable amount of time to arrive. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - action(:class:`telegram.ChatAction` | :obj:`str`): Type of action to broadcast. Choose - one, depending on what the user is about to receive. For convenience look at the - constants in :class:`telegram.ChatAction` - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'action': action} - - result = self._post('sendChatAction', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - def _effective_inline_results( # pylint: disable=R0201 - self, - results: Union[ - Sequence['InlineQueryResult'], Callable[[int], Optional[Sequence['InlineQueryResult']]] - ], - next_offset: str = None, - current_offset: str = None, - ) -> Tuple[Sequence['InlineQueryResult'], Optional[str]]: - """ - Builds the effective results from the results input. - We make this a stand-alone method so tg.ext.ExtBot can wrap it. - - Returns: - Tuple of 1. the effective results and 2. correct the next_offset - - """ - if current_offset is not None and next_offset is not None: - raise ValueError('`current_offset` and `next_offset` are mutually exclusive!') - - if current_offset is not None: - # Convert the string input to integer - if current_offset == '': - current_offset_int = 0 - else: - current_offset_int = int(current_offset) - - # for now set to empty string, stating that there are no more results - # might change later - next_offset = '' - - if callable(results): - callable_output = results(current_offset_int) - if not callable_output: - effective_results: Sequence['InlineQueryResult'] = [] - else: - effective_results = callable_output - # the callback *might* return more results on the next call, so we increment - # the page count - next_offset = str(current_offset_int + 1) - else: - if len(results) > (current_offset_int + 1) * MAX_INLINE_QUERY_RESULTS: - # we expect more results for the next page - next_offset_int = current_offset_int + 1 - next_offset = str(next_offset_int) - effective_results = results[ - current_offset_int - * MAX_INLINE_QUERY_RESULTS : next_offset_int - * MAX_INLINE_QUERY_RESULTS - ] - else: - effective_results = results[current_offset_int * MAX_INLINE_QUERY_RESULTS :] - else: - effective_results = results # type: ignore[assignment] - - return effective_results, next_offset - - @log - def answer_inline_query( - self, - inline_query_id: str, - results: Union[ - Sequence['InlineQueryResult'], Callable[[int], Optional[Sequence['InlineQueryResult']]] - ], - cache_time: int = 300, - is_personal: bool = None, - next_offset: str = None, - switch_pm_text: str = None, - switch_pm_parameter: str = None, - timeout: ODVInput[float] = DEFAULT_NONE, - current_offset: str = None, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to send answers to an inline query. No more than 50 results per query are - allowed. - - Warning: - In most use cases :attr:`current_offset` should not be passed manually. Instead of - calling this method directly, use the shortcut :meth:`telegram.InlineQuery.answer` with - ``auto_pagination=True``, which will take care of passing the correct value. - - Args: - inline_query_id (:obj:`str`): Unique identifier for the answered query. - results (List[:class:`telegram.InlineQueryResult`] | Callable): A list of results for - the inline query. In case :attr:`current_offset` is passed, ``results`` may also be - a callable that accepts the current page index starting from 0. It must return - either a list of :class:`telegram.InlineQueryResult` instances or :obj:`None` if - there are no more results. - cache_time (:obj:`int`, optional): The maximum amount of time in seconds that the - result of the inline query may be cached on the server. Defaults to ``300``. - is_personal (:obj:`bool`, optional): Pass :obj:`True`, if results may be cached on - the server side only for the user that sent the query. By default, - results may be returned to any user who sends the same query. - next_offset (:obj:`str`, optional): Pass the offset that a client should send in the - next query with the same text to receive more results. Pass an empty string if - there are no more results or if you don't support pagination. Offset length can't - exceed 64 bytes. - switch_pm_text (:obj:`str`, optional): If passed, clients will display a button with - specified text that switches the user to a private chat with the bot and sends the - bot a start message with the parameter ``switch_pm_parameter``. - switch_pm_parameter (:obj:`str`, optional): Deep-linking parameter for the /start - message sent to the bot when user presses the switch button. 1-64 characters, - only A-Z, a-z, 0-9, _ and - are allowed. - current_offset (:obj:`str`, optional): The :attr:`telegram.InlineQuery.offset` of - the inline query to answer. If passed, PTB will automatically take care of - the pagination for you, i.e. pass the correct ``next_offset`` and truncate the - results list/get the results from the callable you passed. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Example: - An inline bot that sends YouTube videos can ask the user to connect the bot to their - YouTube account to adapt search results accordingly. To do this, it displays a - 'Connect your YouTube account' button above the results, or even before showing any. - The user presses the button, switches to a private chat with the bot and, in doing so, - passes a start parameter that instructs the bot to return an oauth link. Once done, the - bot can offer a switch_inline button so that the user can easily return to the chat - where they wanted to use the bot's inline capabilities. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - - @no_type_check - def _set_defaults(res): - # pylint: disable=W0212 - if hasattr(res, 'parse_mode') and res.parse_mode == DEFAULT_NONE: - if self.defaults: - res.parse_mode = self.defaults.parse_mode - else: - res.parse_mode = None - if hasattr(res, 'input_message_content') and res.input_message_content: - if ( - hasattr(res.input_message_content, 'parse_mode') - and res.input_message_content.parse_mode == DEFAULT_NONE - ): - if self.defaults: - res.input_message_content.parse_mode = DefaultValue.get_value( - self.defaults.parse_mode - ) - else: - res.input_message_content.parse_mode = None - if ( - hasattr(res.input_message_content, 'disable_web_page_preview') - and res.input_message_content.disable_web_page_preview == DEFAULT_NONE - ): - if self.defaults: - res.input_message_content.disable_web_page_preview = ( - DefaultValue.get_value(self.defaults.disable_web_page_preview) - ) - else: - res.input_message_content.disable_web_page_preview = None - - effective_results, next_offset = self._effective_inline_results( - results=results, next_offset=next_offset, current_offset=current_offset - ) - - # Apply defaults - for result in effective_results: - _set_defaults(result) - - results_dicts = [res.to_dict() for res in effective_results] - - data: JSONDict = {'inline_query_id': inline_query_id, 'results': results_dicts} - - if cache_time or cache_time == 0: - data['cache_time'] = cache_time - if is_personal: - data['is_personal'] = is_personal - if next_offset is not None: - data['next_offset'] = next_offset - if switch_pm_text: - data['switch_pm_text'] = switch_pm_text - if switch_pm_parameter: - data['switch_pm_parameter'] = switch_pm_parameter - - return self._post( # type: ignore[return-value] - 'answerInlineQuery', - data, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - @log - def get_user_profile_photos( - self, - user_id: Union[str, int], - offset: int = None, - limit: int = 100, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Optional[UserProfilePhotos]: - """Use this method to get a list of profile pictures for a user. - - Args: - user_id (:obj:`int`): Unique identifier of the target user. - offset (:obj:`int`, optional): Sequential number of the first photo to be returned. - By default, all photos are returned. - limit (:obj:`int`, optional): Limits the number of photos to be retrieved. Values - between 1-100 are accepted. Defaults to ``100``. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.UserProfilePhotos` - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'user_id': user_id} - - if offset is not None: - data['offset'] = offset - if limit: - data['limit'] = limit - - result = self._post('getUserProfilePhotos', data, timeout=timeout, api_kwargs=api_kwargs) - - return UserProfilePhotos.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def get_file( - self, - file_id: Union[ - str, Animation, Audio, ChatPhoto, Document, PhotoSize, Sticker, Video, VideoNote, Voice - ], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> File: - """ - Use this method to get basic info about a file and prepare it for downloading. For the - moment, bots can download files of up to 20MB in size. The file can then be downloaded - with :meth:`telegram.File.download`. It is guaranteed that the link will be - valid for at least 1 hour. When the link expires, a new one can be requested by - calling get_file again. - - Note: - This function may not preserve the original file name and MIME type. - You should save the file's MIME type and name (if available) when the File object - is received. - - Args: - file_id (:obj:`str` | :class:`telegram.Animation` | :class:`telegram.Audio` | \ - :class:`telegram.ChatPhoto` | :class:`telegram.Document` | \ - :class:`telegram.PhotoSize` | :class:`telegram.Sticker` | \ - :class:`telegram.Video` | :class:`telegram.VideoNote` | \ - :class:`telegram.Voice`): - Either the file identifier or an object that has a file_id attribute - to get file information about. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - try: - file_id = file_id.file_id # type: ignore[union-attr] - except AttributeError: - pass - - data: JSONDict = {'file_id': file_id} - - result = self._post('getFile', data, timeout=timeout, api_kwargs=api_kwargs) - - if result.get('file_path') and not is_local_file( # type: ignore[union-attr] - result['file_path'] # type: ignore[index] - ): - result['file_path'] = '{}/{}'.format( # type: ignore[index] - self.base_file_url, result['file_path'] # type: ignore[index] - ) - - return File.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def kick_chat_member( - self, - chat_id: Union[str, int], - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - until_date: Union[int, datetime] = None, - api_kwargs: JSONDict = None, - revoke_messages: bool = None, - ) -> bool: - """ - Deprecated, use :func:`~telegram.Bot.ban_chat_member` instead. - - .. deprecated:: 13.7 - - """ - warnings.warn( - '`bot.kick_chat_member` is deprecated. Use `bot.ban_chat_member` instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - return self.ban_chat_member( - chat_id=chat_id, - user_id=user_id, - timeout=timeout, - until_date=until_date, - api_kwargs=api_kwargs, - revoke_messages=revoke_messages, - ) - - @log - def ban_chat_member( - self, - chat_id: Union[str, int], - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - until_date: Union[int, datetime] = None, - api_kwargs: JSONDict = None, - revoke_messages: bool = None, - ) -> bool: - """ - Use this method to ban a user from a group, supergroup or a channel. In the case of - supergroups and channels, the user will not be able to return to the group on their own - using invite links, etc., unless unbanned first. The bot must be an administrator in the - chat for this to work and must have the appropriate admin rights. - - .. versionadded:: 13.7 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target group or username - of the target supergroup or channel (in the format ``@channelusername``). - user_id (:obj:`int`): Unique identifier of the target user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - until_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the user will - be unbanned, unix time. If user is banned for more than 366 days or less than 30 - seconds from the current time they are considered to be banned forever. Applied - for supergroups and channels only. - For timezone naive :obj:`datetime.datetime` objects, the default timezone of the - bot will be used. - revoke_messages (:obj:`bool`, optional): Pass :obj:`True` to delete all messages from - the chat for the user that is being removed. If :obj:`False`, the user will be able - to see messages in the group that were sent before the user was removed. - Always :obj:`True` for supergroups and channels. - - .. versionadded:: 13.4 - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'user_id': user_id} - - if until_date is not None: - if isinstance(until_date, datetime): - until_date = to_timestamp( - until_date, tzinfo=self.defaults.tzinfo if self.defaults else None - ) - data['until_date'] = until_date - - if revoke_messages is not None: - data['revoke_messages'] = revoke_messages - - result = self._post('banChatMember', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def ban_chat_sender_chat( - self, - chat_id: Union[str, int], - sender_chat_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to ban a channel chat in a supergroup or a channel. Until the chat is - unbanned, the owner of the banned chat won't be able to send messages on behalf of **any of - their channels**. The bot must be an administrator in the supergroup or channel for this - to work and must have the appropriate administrator rights. - - .. versionadded:: 13.9 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target group or username - of the target supergroup or channel (in the format ``@channelusername``). - sender_chat_id (:obj:`int`): Unique identifier of the target sender chat. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id} - - result = self._post('banChatSenderChat', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def unban_chat_member( - self, - chat_id: Union[str, int], - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - only_if_banned: bool = None, - ) -> bool: - """Use this method to unban a previously kicked user in a supergroup or channel. - - The user will *not* return to the group or channel automatically, but will be able to join - via link, etc. The bot must be an administrator for this to work. By default, this method - guarantees that after the call the user is not a member of the chat, but will be able to - join it. So if the user is a member of the chat they will also be *removed* from the chat. - If you don't want this, use the parameter :attr:`only_if_banned`. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup or channel (in the format ``@channelusername``). - user_id (:obj:`int`): Unique identifier of the target user. - only_if_banned (:obj:`bool`, optional): Do nothing if the user is not banned. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'user_id': user_id} - - if only_if_banned is not None: - data['only_if_banned'] = only_if_banned - - result = self._post('unbanChatMember', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def unban_chat_sender_chat( - self, - chat_id: Union[str, int], - sender_chat_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to unban a previously banned channel in a supergroup or channel. - The bot must be an administrator for this to work and must have the - appropriate administrator rights. - - .. versionadded:: 13.9 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup or channel (in the format ``@channelusername``). - sender_chat_id (:obj:`int`): Unique identifier of the target sender chat. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id} - - result = self._post('unbanChatSenderChat', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def answer_callback_query( - self, - callback_query_id: str, - text: str = None, - show_alert: bool = False, - url: str = None, - cache_time: int = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to send answers to callback queries sent from inline keyboards. The answer - will be displayed to the user as a notification at the top of the chat screen or as an - alert. - Alternatively, the user can be redirected to the specified Game URL. For this option to - work, you must first create a game for your bot via `@BotFather `_ - and accept the terms. Otherwise, you may use links like t.me/your_bot?start=XXXX that open - your bot with a parameter. - - Args: - callback_query_id (:obj:`str`): Unique identifier for the query to be answered. - text (:obj:`str`, optional): Text of the notification. If not specified, nothing will - be shown to the user, 0-200 characters. - show_alert (:obj:`bool`, optional): If :obj:`True`, an alert will be shown by the - client instead of a notification at the top of the chat screen. Defaults to - :obj:`False`. - url (:obj:`str`, optional): URL that will be opened by the user's client. If you have - created a Game and accepted the conditions via - `@BotFather `_, specify the URL that - opens your game - note that this will only work if the query comes from a callback - game button. Otherwise, you may use links like t.me/your_bot?start=XXXX that open - your bot with a parameter. - cache_time (:obj:`int`, optional): The maximum amount of time in seconds that the - result of the callback query may be cached client-side. Defaults to 0. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool` On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'callback_query_id': callback_query_id} - - if text: - data['text'] = text - if show_alert: - data['show_alert'] = show_alert - if url: - data['url'] = url - if cache_time is not None: - data['cache_time'] = cache_time - - result = self._post('answerCallbackQuery', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def edit_message_text( - self, - text: str, - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - ) -> Union[Message, bool]: - """ - Use this method to edit text and game messages. - - Args: - chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not - specified. Unique identifier for the target chat or username of the target channel - (in the format ``@channelusername``) - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the message to edit. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - text (:obj:`str`): New text of the message, 1-4096 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in your bot's message. See the - constants in :class:`telegram.ParseMode` for the available modes. - entities (List[:class:`telegram.MessageEntity`], optional): List of special entities - that appear in message text, which can be specified instead of :attr:`parse_mode`. - disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in - this message. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for an inline keyboard. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, if edited message is not an inline message, the - edited message is returned, otherwise :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'text': text, - 'parse_mode': parse_mode, - 'disable_web_page_preview': disable_web_page_preview, - } - - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - if entities: - data['entities'] = [me.to_dict() for me in entities] - - return self._message( - 'editMessageText', - data, - timeout=timeout, - reply_markup=reply_markup, - api_kwargs=api_kwargs, - ) - - @log - def edit_message_caption( - self, - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - caption: str = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - ) -> Union[Message, bool]: - """ - Use this method to edit captions of messages. - - Args: - chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not - specified. Unique identifier for the target chat or username of the target channel - (in the format ``@channelusername``) - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the message to edit. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - caption (:obj:`str`, optional): New caption of the message, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to - show bold, italic, fixed-width text or inline URLs in the media caption. See the - constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for an inline keyboard. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, if edited message is not an inline message, the - edited message is returned, otherwise :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - if inline_message_id is None and (chat_id is None or message_id is None): - raise ValueError( - 'edit_message_caption: Both chat_id and message_id are required when ' - 'inline_message_id is not specified' - ) - - data: JSONDict = {'parse_mode': parse_mode} - - if caption: - data['caption'] = caption - if caption_entities: - data['caption_entities'] = [me.to_dict() for me in caption_entities] - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - - return self._message( - 'editMessageCaption', - data, - timeout=timeout, - reply_markup=reply_markup, - api_kwargs=api_kwargs, - ) - - @log - def edit_message_media( - self, - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - media: 'InputMedia' = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """ - Use this method to edit animation, audio, document, photo, or video messages. If a message - is part of a message album, then it can be edited only to an audio for audio albums, only - to a document for document albums and to a photo or a video otherwise. When an inline - message is edited, a new file can't be uploaded. Use a previously uploaded file via its - ``file_id`` or specify a URL. - - Args: - chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not - specified. Unique identifier for the target chat or username of the target channel - (in the format ``@channelusername``). - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the message to edit. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - media (:class:`telegram.InputMedia`): An object for a new media content - of the message. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for an inline keyboard. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ - if inline_message_id is None and (chat_id is None or message_id is None): - raise ValueError( - 'edit_message_media: Both chat_id and message_id are required when ' - 'inline_message_id is not specified' - ) - - data: JSONDict = {'media': media} - - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - - return self._message( - 'editMessageMedia', - data, - timeout=timeout, - reply_markup=reply_markup, - api_kwargs=api_kwargs, - ) - - @log - def edit_message_reply_markup( - self, - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - reply_markup: Optional['InlineKeyboardMarkup'] = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """ - Use this method to edit only the reply markup of messages sent by the bot or via the bot - (for inline bots). - - Args: - chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not - specified. Unique identifier for the target chat or username of the target channel - (in the format ``@channelusername``). - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the message to edit. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for an inline keyboard. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, if edited message is not an inline message, the - edited message is returned, otherwise :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - if inline_message_id is None and (chat_id is None or message_id is None): - raise ValueError( - 'edit_message_reply_markup: Both chat_id and message_id are required when ' - 'inline_message_id is not specified' - ) - - data: JSONDict = {} - - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - - return self._message( - 'editMessageReplyMarkup', - data, - timeout=timeout, - reply_markup=reply_markup, - api_kwargs=api_kwargs, - ) - - @log - def get_updates( - self, - offset: int = None, - limit: int = 100, - timeout: float = 0, - read_latency: float = 2.0, - allowed_updates: List[str] = None, - api_kwargs: JSONDict = None, - ) -> List[Update]: - """Use this method to receive incoming updates using long polling. - - Args: - offset (:obj:`int`, optional): Identifier of the first update to be returned. Must be - greater by one than the highest among the identifiers of previously received - updates. By default, updates starting with the earliest unconfirmed update are - returned. An update is considered confirmed as soon as getUpdates is called with an - offset higher than its :attr:`telegram.Update.update_id`. The negative offset can - be specified to retrieve updates starting from -offset update from the end of the - updates queue. All previous updates will forgotten. - limit (:obj:`int`, optional): Limits the number of updates to be retrieved. Values - between 1-100 are accepted. Defaults to ``100``. - timeout (:obj:`int`, optional): Timeout in seconds for long polling. Defaults to ``0``, - i.e. usual short polling. Should be positive, short polling should be used for - testing purposes only. - read_latency (:obj:`float` | :obj:`int`, optional): Grace time in seconds for receiving - the reply from server. Will be added to the ``timeout`` value and used as the read - timeout from server. Defaults to ``2``. - allowed_updates (List[:obj:`str`]), optional): A JSON-serialized list the types of - updates you want your bot to receive. For example, specify ["message", - "edited_channel_post", "callback_query"] to only receive updates of these types. - See :class:`telegram.Update` for a complete list of available update types. - Specify an empty list to receive all updates except - :attr:`telegram.Update.chat_member` (default). If not specified, the previous - setting will be used. Please note that this parameter doesn't affect updates - created before the call to the get_updates, so unwanted updates may be received for - a short period of time. - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Note: - 1. This method will not work if an outgoing webhook is set up. - 2. In order to avoid getting duplicate updates, recalculate offset after each - server response. - 3. To take full advantage of this library take a look at :class:`telegram.ext.Updater` - - Returns: - List[:class:`telegram.Update`] - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'timeout': timeout} - - if offset: - data['offset'] = offset - if limit: - data['limit'] = limit - if allowed_updates is not None: - data['allowed_updates'] = allowed_updates - - # Ideally we'd use an aggressive read timeout for the polling. However, - # * Short polling should return within 2 seconds. - # * Long polling poses a different problem: the connection might have been dropped while - # waiting for the server to return and there's no way of knowing the connection had been - # dropped in real time. - result = cast( - List[JSONDict], - self._post( - 'getUpdates', - data, - timeout=float(read_latency) + float(timeout), - api_kwargs=api_kwargs, - ), - ) - - if result: - self.logger.debug('Getting updates: %s', [u['update_id'] for u in result]) - else: - self.logger.debug('No new updates found.') - - return Update.de_list(result, self) # type: ignore[return-value] - - @log - def set_webhook( - self, - url: str = None, - certificate: FileInput = None, - timeout: ODVInput[float] = DEFAULT_NONE, - max_connections: int = 40, - allowed_updates: List[str] = None, - api_kwargs: JSONDict = None, - ip_address: str = None, - drop_pending_updates: bool = None, - ) -> bool: - """ - Use this method to specify a url and receive incoming updates via an outgoing webhook. - Whenever there is an update for the bot, Telegram will send an HTTPS POST request to the - specified url, containing a JSON-serialized Update. In case of an unsuccessful request, - Telegram will give up after a reasonable amount of attempts. - - If you'd like to make sure that the Webhook request comes from Telegram, Telegram - recommends using a secret path in the URL, e.g. https://www.example.com/. Since - nobody else knows your bot's token, you can be pretty sure it's us. - - Note: - The certificate argument should be a file from disk ``open(filename, 'rb')``. - - Args: - url (:obj:`str`): HTTPS url to send updates to. Use an empty string to remove webhook - integration. - certificate (:obj:`filelike`): Upload your public key certificate so that the root - certificate in use can be checked. See our self-signed guide for details. - (https://goo.gl/rw7w6Y) - ip_address (:obj:`str`, optional): The fixed IP address which will be used to send - webhook requests instead of the IP address resolved through DNS. - max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS - connections to the webhook for update delivery, 1-100. Defaults to ``40``. Use - lower values to limit the load on your bot's server, and higher values to increase - your bot's throughput. - allowed_updates (List[:obj:`str`], optional): A JSON-serialized list the types of - updates you want your bot to receive. For example, specify ["message", - "edited_channel_post", "callback_query"] to only receive updates of these types. - See :class:`telegram.Update` for a complete list of available update types. - Specify an empty list to receive all updates except - :attr:`telegram.Update.chat_member` (default). If not specified, the previous - setting will be used. Please note that this parameter doesn't affect updates - created before the call to the set_webhook, so unwanted updates may be received for - a short period of time. - drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending - updates. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Note: - 1. You will not be able to receive updates using :meth:`get_updates` for long as an - outgoing webhook is set up. - 2. To use a self-signed certificate, you need to upload your public key certificate - using certificate parameter. Please upload as InputFile, sending a String will not - work. - 3. Ports currently supported for Webhooks: ``443``, ``80``, ``88``, ``8443``. - - If you're having any trouble setting up webhooks, please check out this `guide to - Webhooks`_. - - Returns: - :obj:`bool` On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - .. _`guide to Webhooks`: https://core.telegram.org/bots/webhooks - - """ - data: JSONDict = {} - - if url is not None: - data['url'] = url - if certificate: - data['certificate'] = parse_file_input(certificate) - if max_connections is not None: - data['max_connections'] = max_connections - if allowed_updates is not None: - data['allowed_updates'] = allowed_updates - if ip_address: - data['ip_address'] = ip_address - if drop_pending_updates: - data['drop_pending_updates'] = drop_pending_updates - - result = self._post('setWebhook', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def delete_webhook( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - drop_pending_updates: bool = None, - ) -> bool: - """ - Use this method to remove webhook integration if you decide to switch back to - :meth:`get_updates()`. - - Args: - drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending - updates. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data = {} - - if drop_pending_updates: - data['drop_pending_updates'] = drop_pending_updates - - result = self._post('deleteWebhook', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def leave_chat( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method for your bot to leave a group, supergroup or channel. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup or channel (in the format ``@channelusername``). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - result = self._post('leaveChat', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def get_chat( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Chat: - """ - Use this method to get up to date information about the chat (current name of the user for - one-on-one conversations, current username of a user, group or channel, etc.). - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup or channel (in the format ``@channelusername``). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Chat` - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - result = self._post('getChat', data, timeout=timeout, api_kwargs=api_kwargs) - - return Chat.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def get_chat_administrators( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> List[ChatMember]: - """ - Use this method to get a list of administrators in a chat. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup or channel (in the format ``@channelusername``). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - List[:class:`telegram.ChatMember`]: On success, returns a list of ``ChatMember`` - objects that contains information about all chat administrators except - other bots. If the chat is a group or a supergroup and no administrators were - appointed, only the creator will be returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - result = self._post('getChatAdministrators', data, timeout=timeout, api_kwargs=api_kwargs) - - return ChatMember.de_list(result, self) # type: ignore - - @log - def get_chat_members_count( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> int: - """ - Deprecated, use :func:`~telegram.Bot.get_chat_member_count` instead. - - .. deprecated:: 13.7 - """ - warnings.warn( - '`bot.get_chat_members_count` is deprecated. ' - 'Use `bot.get_chat_member_count` instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - return self.get_chat_member_count(chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs) - - @log - def get_chat_member_count( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> int: - """Use this method to get the number of members in a chat. - - .. versionadded:: 13.7 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup or channel (in the format ``@channelusername``). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`int`: Number of members in the chat. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - result = self._post('getChatMemberCount', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def get_chat_member( - self, - chat_id: Union[str, int], - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> ChatMember: - """Use this method to get information about a member of a chat. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup or channel (in the format ``@channelusername``). - user_id (:obj:`int`): Unique identifier of the target user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.ChatMember` - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'user_id': user_id} - - result = self._post('getChatMember', data, timeout=timeout, api_kwargs=api_kwargs) - - return ChatMember.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def set_chat_sticker_set( - self, - chat_id: Union[str, int], - sticker_set_name: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to set a new group sticker set for a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate - admin rights. Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned - in :meth:`get_chat` requests to check if the bot can use this method. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup (in the format @supergroupusername). - sticker_set_name (:obj:`str`): Name of the sticker set to be set as the group - sticker set. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - data: JSONDict = {'chat_id': chat_id, 'sticker_set_name': sticker_set_name} - - result = self._post('setChatStickerSet', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def delete_chat_sticker_set( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to delete a group sticker set from a supergroup. The bot must be an - administrator in the chat for this to work and must have the appropriate admin rights. - Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned in - :meth:`get_chat` requests to check if the bot can use this method. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup (in the format @supergroupusername). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - data: JSONDict = {'chat_id': chat_id} - - result = self._post('deleteChatStickerSet', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - def get_webhook_info( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> WebhookInfo: - """Use this method to get current webhook status. Requires no parameters. - - If the bot is using :meth:`get_updates`, will return an object with the - :attr:`telegram.WebhookInfo.url` field empty. - - Args: - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.WebhookInfo` - - """ - result = self._post('getWebhookInfo', None, timeout=timeout, api_kwargs=api_kwargs) - - return WebhookInfo.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def set_game_score( - self, - user_id: Union[int, str], - score: int, - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - force: bool = None, - disable_edit_message: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """ - Use this method to set the score of the specified user in a game. - - Args: - user_id (:obj:`int`): User identifier. - score (:obj:`int`): New score, must be non-negative. - force (:obj:`bool`, optional): Pass :obj:`True`, if the high score is allowed to - decrease. This can be useful when fixing mistakes or banning cheaters. - disable_edit_message (:obj:`bool`, optional): Pass :obj:`True`, if the game message - should not be automatically edited to include the current scoreboard. - chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not - specified. Unique identifier for the target chat. - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the sent message. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: The edited message, or if the message wasn't sent by the bot - , :obj:`True`. - - Raises: - :class:`telegram.error.TelegramError`: If the new score is not greater than the user's - current score in the chat and force is :obj:`False`. - - """ - data: JSONDict = {'user_id': user_id, 'score': score} - - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - if force is not None: - data['force'] = force - if disable_edit_message is not None: - data['disable_edit_message'] = disable_edit_message - - return self._message( - 'setGameScore', - data, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - @log - def get_game_high_scores( - self, - user_id: Union[int, str], - chat_id: Union[str, int] = None, - message_id: int = None, - inline_message_id: int = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> List[GameHighScore]: - """ - Use this method to get data for high score tables. Will return the score of the specified - user and several of their neighbors in a game. - - Note: - This method will currently return scores for the target user, plus two of their - closest neighbors on each side. Will also return the top three users if the user and - his neighbors are not among them. Please note that this behavior is subject to change. - - Args: - user_id (:obj:`int`): Target user id. - chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not - specified. Unique identifier for the target chat. - message_id (:obj:`int`, optional): Required if inline_message_id is not specified. - Identifier of the sent message. - inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not - specified. Identifier of the inline message. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - List[:class:`telegram.GameHighScore`] - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'user_id': user_id} - - if chat_id: - data['chat_id'] = chat_id - if message_id: - data['message_id'] = message_id - if inline_message_id: - data['inline_message_id'] = inline_message_id - - result = self._post('getGameHighScores', data, timeout=timeout, api_kwargs=api_kwargs) - - return GameHighScore.de_list(result, self) # type: ignore - - @log - def send_invoice( - self, - chat_id: Union[int, str], - title: str, - description: str, - payload: str, - provider_token: str, - currency: str, - prices: List['LabeledPrice'], - start_parameter: str = None, - photo_url: str = None, - photo_size: int = None, - photo_width: int = None, - photo_height: int = None, - need_name: bool = None, - need_phone_number: bool = None, - need_email: bool = None, - need_shipping_address: bool = None, - is_flexible: bool = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: InlineKeyboardMarkup = None, - provider_data: Union[str, object] = None, - send_phone_number_to_provider: bool = None, - send_email_to_provider: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - max_tip_amount: int = None, - suggested_tip_amounts: List[int] = None, - protect_content: bool = None, - ) -> Message: - """Use this method to send invoices. - - Warning: - As of API 5.2 :attr:`start_parameter` is an optional argument and therefore the order - of the arguments had to be changed. Use keyword arguments to make sure that the - arguments are passed correctly. - - .. versionchanged:: 13.5 - As of Bot API 5.2, the parameter :attr:`start_parameter` is optional. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - title (:obj:`str`): Product name, 1-32 characters. - description (:obj:`str`): Product description, 1-255 characters. - payload (:obj:`str`): Bot-defined invoice payload, 1-128 bytes. This will not be - displayed to the user, use for your internal processes. - provider_token (:obj:`str`): Payments provider token, obtained via - `@BotFather `_. - currency (:obj:`str`): Three-letter ISO 4217 currency code. - prices (List[:class:`telegram.LabeledPrice`)]: Price breakdown, a JSON-serialized list - of components (e.g. product price, tax, discount, delivery cost, delivery tax, - bonus, etc.). - max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the - smallest units of the currency (integer, not float/double). For example, for a - maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in - `currencies.json `_, it - shows the number of digits past the decimal point for each currency (2 for the - majority of currencies). Defaults to ``0``. - - .. versionadded:: 13.5 - suggested_tip_amounts (List[:obj:`int`], optional): A JSON-serialized array of - suggested amounts of tips in the smallest units of the currency (integer, not - float/double). At most 4 suggested tip amounts can be specified. The suggested tip - amounts must be positive, passed in a strictly increased order and must not exceed - ``max_tip_amount``. - - .. versionadded:: 13.5 - start_parameter (:obj:`str`, optional): Unique deep-linking parameter. If left empty, - *forwarded copies* of the sent message will have a *Pay* button, allowing - multiple users to pay directly from the forwarded message, using the same invoice. - If non-empty, forwarded copies of the sent message will have a *URL* button with a - deep link to the bot (instead of a *Pay* button), with the value used as the - start parameter. - - .. versionchanged:: 13.5 - As of Bot API 5.2, this parameter is optional. - provider_data (:obj:`str` | :obj:`object`, optional): JSON-serialized data about the - invoice, which will be shared with the payment provider. A detailed description of - required fields should be provided by the payment provider. When an object is - passed, it will be encoded as JSON. - photo_url (:obj:`str`, optional): URL of the product photo for the invoice. Can be a - photo of the goods or a marketing image for a service. People like it better when - they see what they are paying for. - photo_size (:obj:`str`, optional): Photo size. - photo_width (:obj:`int`, optional): Photo width. - photo_height (:obj:`int`, optional): Photo height. - need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full - name to complete the order. - need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's - phone number to complete the order. - need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email - to complete the order. - need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the - user's shipping address to complete the order. - send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's - phone number should be sent to provider. - send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email - address should be sent to provider. - is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on - the shipping method. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for an inline keyboard. If empty, one 'Pay total price' button will be - shown. If not empty, the first button must be a Pay button. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'title': title, - 'description': description, - 'payload': payload, - 'provider_token': provider_token, - 'currency': currency, - 'prices': [p.to_dict() for p in prices], - } - if max_tip_amount is not None: - data['max_tip_amount'] = max_tip_amount - if suggested_tip_amounts is not None: - data['suggested_tip_amounts'] = suggested_tip_amounts - if start_parameter is not None: - data['start_parameter'] = start_parameter - if provider_data is not None: - if isinstance(provider_data, str): - data['provider_data'] = provider_data - else: - data['provider_data'] = json.dumps(provider_data) - if photo_url is not None: - data['photo_url'] = photo_url - if photo_size is not None: - data['photo_size'] = photo_size - if photo_width is not None: - data['photo_width'] = photo_width - if photo_height is not None: - data['photo_height'] = photo_height - if need_name is not None: - data['need_name'] = need_name - if need_phone_number is not None: - data['need_phone_number'] = need_phone_number - if need_email is not None: - data['need_email'] = need_email - if need_shipping_address is not None: - data['need_shipping_address'] = need_shipping_address - if is_flexible is not None: - data['is_flexible'] = is_flexible - if send_phone_number_to_provider is not None: - data['send_phone_number_to_provider'] = send_phone_number_to_provider - if send_email_to_provider is not None: - data['send_email_to_provider'] = send_email_to_provider - - return self._message( # type: ignore[return-value] - 'sendInvoice', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def answer_shipping_query( # pylint: disable=C0103 - self, - shipping_query_id: str, - ok: bool, - shipping_options: List[ShippingOption] = None, - error_message: str = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - If you sent an invoice requesting a shipping address and the parameter ``is_flexible`` was - specified, the Bot API will send an :class:`telegram.Update` with a - :attr:`Update.shipping_query` field to the bot. Use this method to reply to shipping - queries. - - Args: - shipping_query_id (:obj:`str`): Unique identifier for the query to be answered. - ok (:obj:`bool`): Specify :obj:`True` if delivery to the specified address is possible - and :obj:`False` if there are any problems (for example, if delivery to the - specified address is not possible). - shipping_options (List[:class:`telegram.ShippingOption`]), optional]: Required if ok is - :obj:`True`. A JSON-serialized array of available shipping options. - error_message (:obj:`str`, optional): Required if ok is :obj:`False`. Error message in - human readable form that explains why it is impossible to complete the order (e.g. - "Sorry, delivery to your desired address is unavailable"). Telegram will display - this message to the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - ok = bool(ok) - - if ok and (shipping_options is None or error_message is not None): - raise TelegramError( - 'answerShippingQuery: If ok is True, shipping_options ' - 'should not be empty and there should not be error_message' - ) - - if not ok and (shipping_options is not None or error_message is None): - raise TelegramError( - 'answerShippingQuery: If ok is False, error_message ' - 'should not be empty and there should not be shipping_options' - ) - - data: JSONDict = {'shipping_query_id': shipping_query_id, 'ok': ok} - - if ok: - if not shipping_options: - # not using an assert statement directly here since they are removed in - # the optimized bytecode - raise AssertionError - data['shipping_options'] = [option.to_dict() for option in shipping_options] - if error_message is not None: - data['error_message'] = error_message - - result = self._post('answerShippingQuery', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def answer_pre_checkout_query( # pylint: disable=C0103 - self, - pre_checkout_query_id: str, - ok: bool, - error_message: str = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Once the user has confirmed their payment and shipping details, the Bot API sends the final - confirmation in the form of an :class:`telegram.Update` with the field - :attr:`Update.pre_checkout_query`. Use this method to respond to such pre-checkout queries. - - Note: - The Bot API must receive an answer within 10 seconds after the pre-checkout - query was sent. - - Args: - pre_checkout_query_id (:obj:`str`): Unique identifier for the query to be answered. - ok (:obj:`bool`): Specify :obj:`True` if everything is alright - (goods are available, etc.) and the bot is ready to proceed with the order. Use - :obj:`False` if there are any problems. - error_message (:obj:`str`, optional): Required if ok is :obj:`False`. Error message - in human readable form that explains the reason for failure to proceed with - the checkout (e.g. "Sorry, somebody just bought the last of our amazing black - T-shirts while you were busy filling out your payment details. Please choose a - different color or garment!"). Telegram will display this message to the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - ok = bool(ok) - - if not (ok ^ (error_message is not None)): # pylint: disable=C0325 - raise TelegramError( - 'answerPreCheckoutQuery: If ok is True, there should ' - 'not be error_message; if ok is False, error_message ' - 'should not be empty' - ) - - data: JSONDict = {'pre_checkout_query_id': pre_checkout_query_id, 'ok': ok} - - if error_message is not None: - data['error_message'] = error_message - - result = self._post('answerPreCheckoutQuery', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def restrict_chat_member( - self, - chat_id: Union[str, int], - user_id: Union[str, int], - permissions: ChatPermissions, - until_date: Union[int, datetime] = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to restrict a user in a supergroup. The bot must be an administrator in - the supergroup for this to work and must have the appropriate admin rights. Pass - :obj:`True` for all boolean parameters to lift restrictions from a user. - - Note: - Since Bot API 4.4, :meth:`restrict_chat_member` takes the new user permissions in a - single argument of type :class:`telegram.ChatPermissions`. The old way of passing - parameters will not keep working forever. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target supergroup (in the format @supergroupusername). - user_id (:obj:`int`): Unique identifier of the target user. - until_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when restrictions - will be lifted for the user, unix time. If user is restricted for more than 366 - days or less than 30 seconds from the current time, they are considered to be - restricted forever. - For timezone naive :obj:`datetime.datetime` objects, the default timezone of the - bot will be used. - permissions (:class:`telegram.ChatPermissions`): A JSON-serialized object for new user - permissions. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ - data: JSONDict = { - 'chat_id': chat_id, - 'user_id': user_id, - 'permissions': permissions.to_dict(), - } - - if until_date is not None: - if isinstance(until_date, datetime): - until_date = to_timestamp( - until_date, tzinfo=self.defaults.tzinfo if self.defaults else None - ) - data['until_date'] = until_date - - result = self._post('restrictChatMember', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def promote_chat_member( - self, - chat_id: Union[str, int], - user_id: Union[str, int], - can_change_info: bool = None, - can_post_messages: bool = None, - can_edit_messages: bool = None, - can_delete_messages: bool = None, - can_invite_users: bool = None, - can_restrict_members: bool = None, - can_pin_messages: bool = None, - can_promote_members: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - is_anonymous: bool = None, - can_manage_chat: bool = None, - can_manage_voice_chats: bool = None, - ) -> bool: - """ - Use this method to promote or demote a user in a supergroup or a channel. The bot must be - an administrator in the chat for this to work and must have the appropriate admin rights. - Pass :obj:`False` for all boolean parameters to demote a user. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - user_id (:obj:`int`): Unique identifier of the target user. - is_anonymous (:obj:`bool`, optional): Pass :obj:`True`, if the administrator's presence - in the chat is hidden. - can_manage_chat (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - access the chat event log, chat statistics, message statistics in channels, see - channel members, see anonymous administrators in supergroups and ignore slow mode. - Implied by any other administrator privilege. - - .. versionadded:: 13.4 - - can_manage_voice_chats (:obj:`bool`, optional): Pass :obj:`True`, if the administrator - can manage voice chats. - - .. versionadded:: 13.4 - - can_change_info (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - change chat title, photo and other settings. - can_post_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - create channel posts, channels only. - can_edit_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - edit messages of other users and can pin messages, channels only. - can_delete_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - delete messages of other users. - can_invite_users (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - invite new users to the chat. - can_restrict_members (:obj:`bool`, optional): Pass :obj:`True`, if the administrator - can restrict, ban or unban chat members. - can_pin_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - pin messages, supergroups only. - can_promote_members (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can - add new administrators with a subset of his own privileges or demote administrators - that he has promoted, directly or indirectly (promoted by administrators that were - appointed by him). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'user_id': user_id} - - if is_anonymous is not None: - data['is_anonymous'] = is_anonymous - if can_change_info is not None: - data['can_change_info'] = can_change_info - if can_post_messages is not None: - data['can_post_messages'] = can_post_messages - if can_edit_messages is not None: - data['can_edit_messages'] = can_edit_messages - if can_delete_messages is not None: - data['can_delete_messages'] = can_delete_messages - if can_invite_users is not None: - data['can_invite_users'] = can_invite_users - if can_restrict_members is not None: - data['can_restrict_members'] = can_restrict_members - if can_pin_messages is not None: - data['can_pin_messages'] = can_pin_messages - if can_promote_members is not None: - data['can_promote_members'] = can_promote_members - if can_manage_chat is not None: - data['can_manage_chat'] = can_manage_chat - if can_manage_voice_chats is not None: - data['can_manage_voice_chats'] = can_manage_voice_chats - - result = self._post('promoteChatMember', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_chat_permissions( - self, - chat_id: Union[str, int], - permissions: ChatPermissions, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to set default chat permissions for all members. The bot must be an - administrator in the group or a supergroup for this to work and must have the - :attr:`telegram.ChatMember.can_restrict_members` admin rights. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username of - the target supergroup (in the format `@supergroupusername`). - permissions (:class:`telegram.ChatPermissions`): New default chat permissions. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'permissions': permissions.to_dict()} - - result = self._post('setChatPermissions', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_chat_administrator_custom_title( - self, - chat_id: Union[int, str], - user_id: Union[int, str], - custom_title: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to set a custom title for administrators promoted by the bot in a - supergroup. The bot must be an administrator for this to work. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username of - the target supergroup (in the format `@supergroupusername`). - user_id (:obj:`int`): Unique identifier of the target administrator. - custom_title (:obj:`str`): New custom title for the administrator; 0-16 characters, - emoji are not allowed. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'user_id': user_id, 'custom_title': custom_title} - - result = self._post( - 'setChatAdministratorCustomTitle', data, timeout=timeout, api_kwargs=api_kwargs - ) - - return result # type: ignore[return-value] - - @log - def export_chat_invite_link( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> str: - """ - Use this method to generate a new primary invite link for a chat; any previously generated - link is revoked. The bot must be an administrator in the chat for this to work and must - have the appropriate admin rights. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Note: - Each administrator in a chat generates their own invite links. Bots can't use invite - links generated by other administrators. If you want your bot to work with invite - links, it will need to generate its own link using :meth:`export_chat_invite_link` or - by calling the :meth:`get_chat` method. If your bot needs to generate a new primary - invite link replacing its previous one, use :attr:`export_chat_invite_link` again. - - Returns: - :obj:`str`: New invite link on success. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - result = self._post('exportChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def create_chat_invite_link( - self, - chat_id: Union[str, int], - expire_date: Union[int, datetime] = None, - member_limit: int = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - name: str = None, - creates_join_request: bool = None, - ) -> ChatInviteLink: - """ - Use this method to create an additional invite link for a chat. The bot must be an - administrator in the chat for this to work and must have the appropriate admin rights. - The link can be revoked using the method :meth:`revoke_chat_invite_link`. - - .. versionadded:: 13.4 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - expire_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the link will - expire. Integer input will be interpreted as Unix timestamp. - For timezone naive :obj:`datetime.datetime` objects, the default timezone of the - bot will be used. - member_limit (:obj:`int`, optional): Maximum number of users that can be members of - the chat simultaneously after joining the chat via this invite link; 1-99999. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - name (:obj:`str`, optional): Invite link name; 0-32 characters. - - .. versionadded:: 13.8 - creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat - via the link need to be approved by chat administrators. - If :obj:`True`, ``member_limit`` can't be specified. - - .. versionadded:: 13.8 - - Returns: - :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - - """ - if creates_join_request and member_limit: - raise ValueError( - "If `creates_join_request` is `True`, `member_limit` can't be specified." - ) - - data: JSONDict = { - 'chat_id': chat_id, - } - - if expire_date is not None: - if isinstance(expire_date, datetime): - expire_date = to_timestamp( - expire_date, tzinfo=self.defaults.tzinfo if self.defaults else None - ) - data['expire_date'] = expire_date - - if member_limit is not None: - data['member_limit'] = member_limit - - if name is not None: - data['name'] = name - - if creates_join_request is not None: - data['creates_join_request'] = creates_join_request - - result = self._post('createChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs) - - return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def edit_chat_invite_link( - self, - chat_id: Union[str, int], - invite_link: str, - expire_date: Union[int, datetime] = None, - member_limit: int = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - name: str = None, - creates_join_request: bool = None, - ) -> ChatInviteLink: - """ - Use this method to edit a non-primary invite link created by the bot. The bot must be an - administrator in the chat for this to work and must have the appropriate admin rights. - - Note: - Though not stated explicitly in the official docs, Telegram changes not only the - optional parameters that are explicitly passed, but also replaces all other optional - parameters to the default values. However, since not documented, this behaviour may - change unbeknown to PTB. - - .. versionadded:: 13.4 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - invite_link (:obj:`str`): The invite link to edit. - expire_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the link will - expire. - For timezone naive :obj:`datetime.datetime` objects, the default timezone of the - bot will be used. - member_limit (:obj:`int`, optional): Maximum number of users that can be members of - the chat simultaneously after joining the chat via this invite link; 1-99999. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - name (:obj:`str`, optional): Invite link name; 0-32 characters. - - .. versionadded:: 13.8 - creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat - via the link need to be approved by chat administrators. - If :obj:`True`, ``member_limit`` can't be specified. - - .. versionadded:: 13.8 - - Returns: - :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - - """ - if creates_join_request and member_limit: - raise ValueError( - "If `creates_join_request` is `True`, `member_limit` can't be specified." - ) - - data: JSONDict = {'chat_id': chat_id, 'invite_link': invite_link} - - if expire_date is not None: - if isinstance(expire_date, datetime): - expire_date = to_timestamp( - expire_date, tzinfo=self.defaults.tzinfo if self.defaults else None - ) - data['expire_date'] = expire_date - - if member_limit is not None: - data['member_limit'] = member_limit - - if name is not None: - data['name'] = name - - if creates_join_request is not None: - data['creates_join_request'] = creates_join_request - - result = self._post('editChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs) - - return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def revoke_chat_invite_link( - self, - chat_id: Union[str, int], - invite_link: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> ChatInviteLink: - """ - Use this method to revoke an invite link created by the bot. If the primary link is - revoked, a new link is automatically generated. The bot must be an administrator in the - chat for this to work and must have the appropriate admin rights. - - .. versionadded:: 13.4 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - invite_link (:obj:`str`): The invite link to edit. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'invite_link': invite_link} - - result = self._post('revokeChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs) - - return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def approve_chat_join_request( - self, - chat_id: Union[str, int], - user_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to approve a chat join request. - - The bot must be an administrator in the chat for this to work and must have the - :attr:`telegram.ChatPermissions.can_invite_users` administrator right. - - .. versionadded:: 13.8 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - user_id (:obj:`int`): Unique identifier of the target user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ - data: JSONDict = {'chat_id': chat_id, 'user_id': user_id} - - result = self._post('approveChatJoinRequest', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def decline_chat_join_request( - self, - chat_id: Union[str, int], - user_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to decline a chat join request. - - The bot must be an administrator in the chat for this to work and must have the - :attr:`telegram.ChatPermissions.can_invite_users` administrator right. - - .. versionadded:: 13.8 - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - user_id (:obj:`int`): Unique identifier of the target user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ - data: JSONDict = {'chat_id': chat_id, 'user_id': user_id} - - result = self._post('declineChatJoinRequest', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_chat_photo( - self, - chat_id: Union[str, int], - photo: FileInput, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to set a new profile photo for the chat. - - Photos can't be changed for private chats. The bot must be an administrator in the chat - for this to work and must have the appropriate admin rights. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - photo (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`): New chat photo. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'photo': parse_file_input(photo)} - - result = self._post('setChatPhoto', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def delete_chat_photo( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to delete a chat photo. Photos can't be changed for private chats. The bot - must be an administrator in the chat for this to work and must have the appropriate admin - rights. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - result = self._post('deleteChatPhoto', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_chat_title( - self, - chat_id: Union[str, int], - title: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to change the title of a chat. Titles can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate - admin rights. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - title (:obj:`str`): New chat title, 1-255 characters. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'title': title} - - result = self._post('setChatTitle', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_chat_description( - self, - chat_id: Union[str, int], - description: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to change the description of a group, a supergroup or a channel. The bot - must be an administrator in the chat for this to work and must have the appropriate admin - rights. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - description (:obj:`str`): New chat description, 0-255 characters. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'description': description} - - result = self._post('setChatDescription', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def pin_chat_message( - self, - chat_id: Union[str, int], - message_id: int, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to add a message to the list of pinned messages in a chat. If the - chat is not a private chat, the bot must be an administrator in the chat for this to work - and must have the :attr:`telegram.ChatMember.can_pin_messages` admin right in a supergroup - or :attr:`telegram.ChatMember.can_edit_messages` admin right in a channel. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - message_id (:obj:`int`): Identifier of a message to pin. - disable_notification (:obj:`bool`, optional): Pass :obj:`True`, if it is not necessary - to send a notification to all chat members about the new pinned message. - Notifications are always disabled in channels and private chats. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'message_id': message_id, - 'disable_notification': disable_notification, - } - - return self._post( # type: ignore[return-value] - 'pinChatMessage', data, timeout=timeout, api_kwargs=api_kwargs - ) - - @log - def unpin_chat_message( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - message_id: int = None, - ) -> bool: - """ - Use this method to remove a message from the list of pinned messages in a chat. If the - chat is not a private chat, the bot must be an administrator in the chat for this to work - and must have the :attr:`telegram.ChatMember.can_pin_messages` admin right in a - supergroup or :attr:`telegram.ChatMember.can_edit_messages` admin right in a channel. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - message_id (:obj:`int`, optional): Identifier of a message to unpin. If not specified, - the most recent pinned message (by sending date) will be unpinned. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - if message_id is not None: - data['message_id'] = message_id - - return self._post( # type: ignore[return-value] - 'unpinChatMessage', data, timeout=timeout, api_kwargs=api_kwargs - ) - - @log - def unpin_all_chat_messages( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Use this method to clear the list of pinned messages in a chat. If the - chat is not a private chat, the bot must be an administrator in the chat for this - to work and must have the :attr:`telegram.ChatMember.can_pin_messages` admin right in a - supergroup or :attr:`telegram.ChatMember.can_edit_messages` admin right in a channel. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - return self._post( # type: ignore[return-value] - 'unpinAllChatMessages', data, timeout=timeout, api_kwargs=api_kwargs - ) - - @log - def get_sticker_set( - self, - name: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> StickerSet: - """Use this method to get a sticker set. - - Args: - name (:obj:`str`): Name of the sticker set. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.StickerSet` - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'name': name} - - result = self._post('getStickerSet', data, timeout=timeout, api_kwargs=api_kwargs) - - return StickerSet.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def upload_sticker_file( - self, - user_id: Union[str, int], - png_sticker: FileInput, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - ) -> File: - """ - Use this method to upload a ``.PNG`` file with a sticker for later use in - :meth:`create_new_sticker_set` and :meth:`add_sticker_to_set` methods (can be used multiple - times). - - Note: - The png_sticker argument can be either a file_id, an URL or a file from disk - ``open(filename, 'rb')`` - - Args: - user_id (:obj:`int`): User identifier of sticker file owner. - png_sticker (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path`): - **PNG** image with the sticker, must be up to 512 kilobytes in size, - dimensions must not exceed 512px, and either width or height must be exactly 512px. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.File`: On success, the uploaded File is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'user_id': user_id, 'png_sticker': parse_file_input(png_sticker)} - - result = self._post('uploadStickerFile', data, timeout=timeout, api_kwargs=api_kwargs) - - return File.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def create_new_sticker_set( - self, - user_id: Union[str, int], - name: str, - title: str, - emojis: str, - png_sticker: FileInput = None, - contains_masks: bool = None, - mask_position: MaskPosition = None, - timeout: DVInput[float] = DEFAULT_20, - tgs_sticker: FileInput = None, - api_kwargs: JSONDict = None, - webm_sticker: FileInput = None, - ) -> bool: - """ - Use this method to create new sticker set owned by a user. - The bot will be able to edit the created sticker set. - You must use exactly one of the fields ``png_sticker``, ``tgs_sticker``, or - ``webm_sticker``. - - Warning: - As of API 4.7 ``png_sticker`` is an optional argument and therefore the order of the - arguments had to be changed. Use keyword arguments to make sure that the arguments are - passed correctly. - - Note: - The png_sticker and tgs_sticker argument can be either a file_id, an URL or a file from - disk ``open(filename, 'rb')`` - - Args: - user_id (:obj:`int`): User identifier of created sticker set owner. - name (:obj:`str`): Short name of sticker set, to be used in t.me/addstickers/ URLs - (e.g., animals). Can contain only english letters, digits and underscores. - Must begin with a letter, can't contain consecutive underscores and - must end in "_by_". is case insensitive. - 1-64 characters. - title (:obj:`str`): Sticker set title, 1-64 characters. - png_sticker (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path`, \ - optional): **PNG** image with the sticker, - must be up to 512 kilobytes in size, dimensions must not exceed 512px, - and either width or height must be exactly 512px. Pass a file_id as a String to - send a file that already exists on the Telegram servers, pass an HTTP URL as a - String for Telegram to get a file from the Internet, or upload a new one - using multipart/form-data. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - tgs_sticker (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path`, \ - optional): **TGS** animation with the sticker, uploaded using multipart/form-data. - See https://core.telegram.org/stickers#animated-sticker-requirements for technical - requirements. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - webm_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`,\ - optional): **WEBM** video with the sticker, uploaded using multipart/form-data. - See https://core.telegram.org/stickers#video-sticker-requirements for - technical requirements. - - .. versionadded:: 13.11 - - emojis (:obj:`str`): One or more emoji corresponding to the sticker. - contains_masks (:obj:`bool`, optional): Pass :obj:`True`, if a set of mask stickers - should be created. - mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask - should be placed on faces. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'user_id': user_id, 'name': name, 'title': title, 'emojis': emojis} - - if png_sticker is not None: - data['png_sticker'] = parse_file_input(png_sticker) - if tgs_sticker is not None: - data['tgs_sticker'] = parse_file_input(tgs_sticker) - if webm_sticker is not None: - data['webm_sticker'] = parse_file_input(webm_sticker) - if contains_masks is not None: - data['contains_masks'] = contains_masks - if mask_position is not None: - # We need to_json() instead of to_dict() here, because we're sending a media - # message here, which isn't json dumped by utils.request - data['mask_position'] = mask_position.to_json() - - result = self._post('createNewStickerSet', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def add_sticker_to_set( - self, - user_id: Union[str, int], - name: str, - emojis: str, - png_sticker: FileInput = None, - mask_position: MaskPosition = None, - timeout: DVInput[float] = DEFAULT_20, - tgs_sticker: FileInput = None, - api_kwargs: JSONDict = None, - webm_sticker: FileInput = None, - ) -> bool: - """ - Use this method to add a new sticker to a set created by the bot. - You **must** use exactly one of the fields ``png_sticker``, ``tgs_sticker`` or - ``webm_sticker``. Animated stickers can be added to animated sticker sets and only to them. - Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 - stickers. - - Warning: - As of API 4.7 ``png_sticker`` is an optional argument and therefore the order of the - arguments had to be changed. Use keyword arguments to make sure that the arguments are - passed correctly. - - Note: - The png_sticker and tgs_sticker argument can be either a file_id, an URL or a file from - disk ``open(filename, 'rb')`` - - Args: - user_id (:obj:`int`): User identifier of created sticker set owner. - - name (:obj:`str`): Sticker set name. - png_sticker (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path`, \ - optional): **PNG** image with the sticker, - must be up to 512 kilobytes in size, dimensions must not exceed 512px, - and either width or height must be exactly 512px. Pass a file_id as a String to - send a file that already exists on the Telegram servers, pass an HTTP URL as a - String for Telegram to get a file from the Internet, or upload a new one - using multipart/form-data. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - tgs_sticker (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path`, \ - optional): **TGS** animation with the sticker, uploaded using multipart/form-data. - See https://core.telegram.org/stickers#animated-sticker-requirements for technical - requirements. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - webm_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`,\ - optional): **WEBM** video with the sticker, uploaded using multipart/form-data. - See https://core.telegram.org/stickers#video-sticker-requirements for - technical requirements. - - .. versionadded:: 13.11 - emojis (:obj:`str`): One or more emoji corresponding to the sticker. - mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask - should be placed on faces. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'user_id': user_id, 'name': name, 'emojis': emojis} - - if png_sticker is not None: - data['png_sticker'] = parse_file_input(png_sticker) - if tgs_sticker is not None: - data['tgs_sticker'] = parse_file_input(tgs_sticker) - if webm_sticker is not None: - data['webm_sticker'] = parse_file_input(webm_sticker) - if mask_position is not None: - # We need to_json() instead of to_dict() here, because we're sending a media - # message here, which isn't json dumped by utils.request - data['mask_position'] = mask_position.to_json() - - result = self._post('addStickerToSet', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_sticker_position_in_set( - self, - sticker: str, - position: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to move a sticker in a set created by the bot to a specific position. - - Args: - sticker (:obj:`str`): File identifier of the sticker. - position (:obj:`int`): New sticker position in the set, zero-based. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'sticker': sticker, 'position': position} - - result = self._post( - 'setStickerPositionInSet', data, timeout=timeout, api_kwargs=api_kwargs - ) - - return result # type: ignore[return-value] - - @log - def delete_sticker_from_set( - self, - sticker: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to delete a sticker from a set created by the bot. - - Args: - sticker (:obj:`str`): File identifier of the sticker. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'sticker': sticker} - - result = self._post('deleteStickerFromSet', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_sticker_set_thumb( - self, - name: str, - user_id: Union[str, int], - thumb: FileInput = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set - for animated sticker sets only. Video thumbnails can be set only for video sticker sets - only. - - Note: - The thumb can be either a file_id, an URL or a file from disk ``open(filename, 'rb')`` - - Args: - name (:obj:`str`): Sticker set name - user_id (:obj:`int`): User identifier of created sticker set owner. - thumb (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path`, \ - optional): A **PNG** image with the thumbnail, must - be up to 128 kilobytes in size and have width and height exactly 100px, or a - **TGS** animation with the thumbnail up to 32 kilobytes in size; see - https://core.telegram.org/stickers#animated-sticker-requirements for animated - sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 - kilobytes in size; see - https://core.telegram.org/stickers#video-sticker-requirements for video sticker - technical requirements. Pass a file_id as a String to send a file that - already exists on the Telegram servers, pass an HTTP URL as a String for Telegram - to get a file from the Internet, or upload a new one using multipart/form-data. - Animated sticker set thumbnails can't be uploaded via HTTP URL. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'name': name, 'user_id': user_id} - - if thumb is not None: - data['thumb'] = parse_file_input(thumb) - - result = self._post('setStickerSetThumb', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def set_passport_data_errors( - self, - user_id: Union[str, int], - errors: List[PassportElementError], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """ - Informs a user that some of the Telegram Passport elements they provided contains errors. - The user will not be able to re-submit their Passport to you until the errors are fixed - (the contents of the field for which you returned the error must change). - - Use this if the data submitted by the user doesn't satisfy the standards your service - requires for any reason. For example, if a birthday date seems invalid, a submitted - document is blurry, a scan shows evidence of tampering, etc. Supply some details in the - error message to make sure the user knows how to correct the issues. - - Args: - user_id (:obj:`int`): User identifier - errors (List[:class:`PassportElementError`]): A JSON-serialized array describing the - errors. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during - creation of the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'user_id': user_id, 'errors': [error.to_dict() for error in errors]} - - result = self._post('setPassportDataErrors', data, timeout=timeout, api_kwargs=api_kwargs) - - return result # type: ignore[return-value] - - @log - def send_poll( - self, - chat_id: Union[int, str], - question: str, - options: List[str], - is_anonymous: bool = True, - type: str = Poll.REGULAR, # pylint: disable=W0622 - allows_multiple_answers: bool = False, - correct_option_id: int = None, - is_closed: bool = None, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - explanation: str = None, - explanation_parse_mode: ODVInput[str] = DEFAULT_NONE, - open_period: int = None, - close_date: Union[int, datetime] = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send a native poll. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - question (:obj:`str`): Poll question, 1-300 characters. - options (List[:obj:`str`]): List of answer options, 2-10 strings 1-100 characters each. - is_anonymous (:obj:`bool`, optional): :obj:`True`, if the poll needs to be anonymous, - defaults to :obj:`True`. - type (:obj:`str`, optional): Poll type, :attr:`telegram.Poll.QUIZ` or - :attr:`telegram.Poll.REGULAR`, defaults to :attr:`telegram.Poll.REGULAR`. - allows_multiple_answers (:obj:`bool`, optional): :obj:`True`, if the poll allows - multiple answers, ignored for polls in quiz mode, defaults to :obj:`False`. - correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer - option, required for polls in quiz mode. - explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect - answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most - 2 line feeds after entities parsing. - explanation_parse_mode (:obj:`str`, optional): Mode for parsing entities in the - explanation. See the constants in :class:`telegram.ParseMode` for the available - modes. - explanation_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in message text, which can be specified instead of - :attr:`parse_mode`. - open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active - after creation, 5-600. Can't be used together with :attr:`close_date`. - close_date (:obj:`int` | :obj:`datetime.datetime`, optional): Point in time (Unix - timestamp) when the poll will be automatically closed. Must be at least 5 and no - more than 600 seconds in the future. Can't be used together with - :attr:`open_period`. - For timezone naive :obj:`datetime.datetime` objects, the default timezone of the - bot will be used. - is_closed (:obj:`bool`, optional): Pass :obj:`True`, if the poll needs to be - immediately closed. This can be useful for poll preview. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = { - 'chat_id': chat_id, - 'question': question, - 'options': options, - 'explanation_parse_mode': explanation_parse_mode, - } - - if not is_anonymous: - data['is_anonymous'] = is_anonymous - if type: - data['type'] = type - if allows_multiple_answers: - data['allows_multiple_answers'] = allows_multiple_answers - if correct_option_id is not None: - data['correct_option_id'] = correct_option_id - if is_closed: - data['is_closed'] = is_closed - if explanation: - data['explanation'] = explanation - if explanation_entities: - data['explanation_entities'] = [me.to_dict() for me in explanation_entities] - if open_period: - data['open_period'] = open_period - if close_date: - if isinstance(close_date, datetime): - close_date = to_timestamp( - close_date, tzinfo=self.defaults.tzinfo if self.defaults else None - ) - data['close_date'] = close_date - - return self._message( # type: ignore[return-value] - 'sendPoll', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def stop_poll( - self, - chat_id: Union[int, str], - message_id: int, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Poll: - """ - Use this method to stop a poll which was sent by the bot. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - message_id (:obj:`int`): Identifier of the original message with the poll. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): A JSON-serialized - object for a new message inline keyboard. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Poll`: On success, the stopped Poll with the final results is - returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id, 'message_id': message_id} - - if reply_markup: - if isinstance(reply_markup, ReplyMarkup): - # We need to_json() instead of to_dict() here, because reply_markups may be - # attached to media messages, which aren't json dumped by utils.request - data['reply_markup'] = reply_markup.to_json() - else: - data['reply_markup'] = reply_markup - - result = self._post('stopPoll', data, timeout=timeout, api_kwargs=api_kwargs) - - return Poll.de_json(result, self) # type: ignore[return-value, arg-type] - - @log - def send_dice( - self, - chat_id: Union[int, str], - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - emoji: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> Message: - """ - Use this method to send an animated emoji that will display a random value. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - emoji (:obj:`str`, optional): Emoji on which the dice throw animation is based. - Currently, must be one of “🎲”, “🎯”, “🏀”, “⚽”, "🎳", or “🎰”. Dice can have - values 1-6 for “🎲”, “🎯” and "🎳", values 1-5 for “🏀” and “⚽”, and values 1-64 - for “🎰”. Defaults to “🎲”. - - .. versionchanged:: 13.4 - Added the "🎳" emoji. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A - JSON-serialized object for an inline keyboard, custom reply keyboard, instructions - to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {'chat_id': chat_id} - - if emoji: - data['emoji'] = emoji - - return self._message( # type: ignore[return-value] - 'sendDice', - data, - timeout=timeout, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - allow_sending_without_reply=allow_sending_without_reply, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - @log - def get_my_commands( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - scope: BotCommandScope = None, - language_code: str = None, - ) -> List[BotCommand]: - """ - Use this method to get the current list of the bot's commands for the given scope and user - language. - - Args: - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - scope (:class:`telegram.BotCommandScope`, optional): A JSON-serialized object, - describing scope of users. Defaults to :class:`telegram.BotCommandScopeDefault`. - - .. versionadded:: 13.7 - - language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code or an empty - string. - - .. versionadded:: 13.7 - - Returns: - List[:class:`telegram.BotCommand`]: On success, the commands set for the bot. An empty - list is returned if commands are not set. - - Raises: - :class:`telegram.error.TelegramError` - - """ - data: JSONDict = {} - - if scope: - data['scope'] = scope.to_dict() - - if language_code: - data['language_code'] = language_code - - result = self._post('getMyCommands', data, timeout=timeout, api_kwargs=api_kwargs) - - if (scope is None or scope.type == scope.DEFAULT) and language_code is None: - self._commands = BotCommand.de_list(result, self) # type: ignore[assignment,arg-type] - return self._commands # type: ignore[return-value] - - return BotCommand.de_list(result, self) # type: ignore[return-value,arg-type] - - @log - def set_my_commands( - self, - commands: List[Union[BotCommand, Tuple[str, str]]], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - scope: BotCommandScope = None, - language_code: str = None, - ) -> bool: - """ - Use this method to change the list of the bot's commands. See the - `Telegram docs `_ for more details about bot - commands. - - Args: - commands (List[:class:`BotCommand` | (:obj:`str`, :obj:`str`)]): A JSON-serialized list - of bot commands to be set as the list of the bot's commands. At most 100 commands - can be specified. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - scope (:class:`telegram.BotCommandScope`, optional): A JSON-serialized object, - describing scope of users for which the commands are relevant. Defaults to - :class:`telegram.BotCommandScopeDefault`. - - .. versionadded:: 13.7 - - language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, for whose language - there are no dedicated commands. - - .. versionadded:: 13.7 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - - """ - cmds = [c if isinstance(c, BotCommand) else BotCommand(c[0], c[1]) for c in commands] - - data: JSONDict = {'commands': [c.to_dict() for c in cmds]} - - if scope: - data['scope'] = scope.to_dict() - - if language_code: - data['language_code'] = language_code - - result = self._post('setMyCommands', data, timeout=timeout, api_kwargs=api_kwargs) - - # Set commands only for default scope. No need to check for outcome. - # If request failed, we won't come this far - if (scope is None or scope.type == scope.DEFAULT) and language_code is None: - self._commands = cmds - - return result # type: ignore[return-value] - - @log - def delete_my_commands( - self, - scope: BotCommandScope = None, - language_code: str = None, - api_kwargs: JSONDict = None, - timeout: ODVInput[float] = DEFAULT_NONE, - ) -> bool: - """ - Use this method to delete the list of the bot's commands for the given scope and user - language. After deletion, - `higher level commands `_ - will be shown to affected users. - - .. versionadded:: 13.7 - - Args: - scope (:class:`telegram.BotCommandScope`, optional): A JSON-serialized object, - describing scope of users for which the commands are relevant. Defaults to - :class:`telegram.BotCommandScopeDefault`. - language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, for whose language - there are no dedicated commands. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ - data: JSONDict = {} - - if scope: - data['scope'] = scope.to_dict() - - if language_code: - data['language_code'] = language_code - - result = self._post('deleteMyCommands', data, timeout=timeout, api_kwargs=api_kwargs) - - if (scope is None or scope.type == scope.DEFAULT) and language_code is None: - self._commands = [] - - return result # type: ignore[return-value] - - @log - def log_out(self, timeout: ODVInput[float] = DEFAULT_NONE) -> bool: - """ - Use this method to log out from the cloud Bot API server before launching the bot locally. - You *must* log out the bot before running it locally, otherwise there is no guarantee that - the bot will receive updates. After a successful call, you can immediately log in on a - local server, but will not be able to log in back to the cloud Bot API server for 10 - minutes. - - Args: - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - - Returns: - :obj:`True`: On success - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self._post('logOut', timeout=timeout) # type: ignore[return-value] - - @log - def close(self, timeout: ODVInput[float] = DEFAULT_NONE) -> bool: - """ - Use this method to close the bot instance before moving it from one local server to - another. You need to delete the webhook before calling this method to ensure that the bot - isn't launched again after server restart. The method will return error 429 in the first - 10 minutes after the bot is launched. - - Args: - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - - Returns: - :obj:`True`: On success - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self._post('close', timeout=timeout) # type: ignore[return-value] - - @log - def copy_message( - self, - chat_id: Union[int, str], - from_chat_id: Union[str, int], - message_id: int, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> MessageId: - """ - Use this method to copy messages of any kind. Service messages and invoice messages can't - be copied. The method is analogous to the method :meth:`forward_message`, but the copied - message doesn't have a link to the original message. - - Args: - chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username - of the target channel (in the format ``@channelusername``). - from_chat_id (:obj:`int` | :obj:`str`): Unique identifier for the chat where the - original message was sent (or channel username in the format ``@channelusername``). - message_id (:obj:`int`): Message identifier in the chat specified in from_chat_id. - caption (:obj:`str`, optional): New caption for media, 0-1024 characters after - entities parsing. If not specified, the original caption is kept. - parse_mode (:obj:`str`, optional): Mode for parsing entities in the new caption. See - the constants in :class:`telegram.ParseMode` for the available modes. - caption_entities (:class:`telegram.utils.types.SLT[MessageEntity]`): List of special - entities that appear in the new caption, which can be specified instead of - parse_mode - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - protect_content (:obj:`bool`, optional): Protects the contents of the sent message from - forwarding and saving. - - .. versionadded:: 13.10 - - reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the - original message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. - A JSON-serialized object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the - Telegram API. - - Returns: - :class:`telegram.MessageId`: On success - - Raises: - :class:`telegram.error.TelegramError` - """ - data: JSONDict = { - 'chat_id': chat_id, - 'from_chat_id': from_chat_id, - 'message_id': message_id, - 'parse_mode': parse_mode, - 'disable_notification': disable_notification, - 'allow_sending_without_reply': allow_sending_without_reply, - } - if caption is not None: - data['caption'] = caption - if caption_entities: - data['caption_entities'] = caption_entities - if reply_to_message_id: - data['reply_to_message_id'] = reply_to_message_id - if protect_content: - data['protect_content'] = protect_content - if reply_markup: - if isinstance(reply_markup, ReplyMarkup): - # We need to_json() instead of to_dict() here, because reply_markups may be - # attached to media messages, which aren't json dumped by utils.request - data['reply_markup'] = reply_markup.to_json() - else: - data['reply_markup'] = reply_markup - - result = self._post('copyMessage', data, timeout=timeout, api_kwargs=api_kwargs) - return MessageId.de_json(result, self) # type: ignore[return-value, arg-type] - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name} - - if self.last_name: - data['last_name'] = self.last_name - - return data - - def __eq__(self, other: object) -> bool: - return self.bot == other - - def __hash__(self) -> int: - return hash(self.bot) - - # camelCase aliases - getMe = get_me - """Alias for :meth:`get_me`""" - sendMessage = send_message - """Alias for :meth:`send_message`""" - deleteMessage = delete_message - """Alias for :meth:`delete_message`""" - forwardMessage = forward_message - """Alias for :meth:`forward_message`""" - sendPhoto = send_photo - """Alias for :meth:`send_photo`""" - sendAudio = send_audio - """Alias for :meth:`send_audio`""" - sendDocument = send_document - """Alias for :meth:`send_document`""" - sendSticker = send_sticker - """Alias for :meth:`send_sticker`""" - sendVideo = send_video - """Alias for :meth:`send_video`""" - sendAnimation = send_animation - """Alias for :meth:`send_animation`""" - sendVoice = send_voice - """Alias for :meth:`send_voice`""" - sendVideoNote = send_video_note - """Alias for :meth:`send_video_note`""" - sendMediaGroup = send_media_group - """Alias for :meth:`send_media_group`""" - sendLocation = send_location - """Alias for :meth:`send_location`""" - editMessageLiveLocation = edit_message_live_location - """Alias for :meth:`edit_message_live_location`""" - stopMessageLiveLocation = stop_message_live_location - """Alias for :meth:`stop_message_live_location`""" - sendVenue = send_venue - """Alias for :meth:`send_venue`""" - sendContact = send_contact - """Alias for :meth:`send_contact`""" - sendGame = send_game - """Alias for :meth:`send_game`""" - sendChatAction = send_chat_action - """Alias for :meth:`send_chat_action`""" - answerInlineQuery = answer_inline_query - """Alias for :meth:`answer_inline_query`""" - getUserProfilePhotos = get_user_profile_photos - """Alias for :meth:`get_user_profile_photos`""" - getFile = get_file - """Alias for :meth:`get_file`""" - banChatMember = ban_chat_member - """Alias for :meth:`ban_chat_member`""" - banChatSenderChat = ban_chat_sender_chat - """Alias for :meth:`ban_chat_sender_chat`""" - kickChatMember = kick_chat_member - """Alias for :meth:`kick_chat_member`""" - unbanChatMember = unban_chat_member - """Alias for :meth:`unban_chat_member`""" - unbanChatSenderChat = unban_chat_sender_chat - """Alias for :meth:`unban_chat_sender_chat`""" - answerCallbackQuery = answer_callback_query - """Alias for :meth:`answer_callback_query`""" - editMessageText = edit_message_text - """Alias for :meth:`edit_message_text`""" - editMessageCaption = edit_message_caption - """Alias for :meth:`edit_message_caption`""" - editMessageMedia = edit_message_media - """Alias for :meth:`edit_message_media`""" - editMessageReplyMarkup = edit_message_reply_markup - """Alias for :meth:`edit_message_reply_markup`""" - getUpdates = get_updates - """Alias for :meth:`get_updates`""" - setWebhook = set_webhook - """Alias for :meth:`set_webhook`""" - deleteWebhook = delete_webhook - """Alias for :meth:`delete_webhook`""" - leaveChat = leave_chat - """Alias for :meth:`leave_chat`""" - getChat = get_chat - """Alias for :meth:`get_chat`""" - getChatAdministrators = get_chat_administrators - """Alias for :meth:`get_chat_administrators`""" - getChatMember = get_chat_member - """Alias for :meth:`get_chat_member`""" - setChatStickerSet = set_chat_sticker_set - """Alias for :meth:`set_chat_sticker_set`""" - deleteChatStickerSet = delete_chat_sticker_set - """Alias for :meth:`delete_chat_sticker_set`""" - getChatMemberCount = get_chat_member_count - """Alias for :meth:`get_chat_member_count`""" - getChatMembersCount = get_chat_members_count - """Alias for :meth:`get_chat_members_count`""" - getWebhookInfo = get_webhook_info - """Alias for :meth:`get_webhook_info`""" - setGameScore = set_game_score - """Alias for :meth:`set_game_score`""" - getGameHighScores = get_game_high_scores - """Alias for :meth:`get_game_high_scores`""" - sendInvoice = send_invoice - """Alias for :meth:`send_invoice`""" - answerShippingQuery = answer_shipping_query - """Alias for :meth:`answer_shipping_query`""" - answerPreCheckoutQuery = answer_pre_checkout_query - """Alias for :meth:`answer_pre_checkout_query`""" - restrictChatMember = restrict_chat_member - """Alias for :meth:`restrict_chat_member`""" - promoteChatMember = promote_chat_member - """Alias for :meth:`promote_chat_member`""" - setChatPermissions = set_chat_permissions - """Alias for :meth:`set_chat_permissions`""" - setChatAdministratorCustomTitle = set_chat_administrator_custom_title - """Alias for :meth:`set_chat_administrator_custom_title`""" - exportChatInviteLink = export_chat_invite_link - """Alias for :meth:`export_chat_invite_link`""" - createChatInviteLink = create_chat_invite_link - """Alias for :meth:`create_chat_invite_link`""" - editChatInviteLink = edit_chat_invite_link - """Alias for :meth:`edit_chat_invite_link`""" - revokeChatInviteLink = revoke_chat_invite_link - """Alias for :meth:`revoke_chat_invite_link`""" - approveChatJoinRequest = approve_chat_join_request - """Alias for :meth:`approve_chat_join_request`""" - declineChatJoinRequest = decline_chat_join_request - """Alias for :meth:`decline_chat_join_request`""" - setChatPhoto = set_chat_photo - """Alias for :meth:`set_chat_photo`""" - deleteChatPhoto = delete_chat_photo - """Alias for :meth:`delete_chat_photo`""" - setChatTitle = set_chat_title - """Alias for :meth:`set_chat_title`""" - setChatDescription = set_chat_description - """Alias for :meth:`set_chat_description`""" - pinChatMessage = pin_chat_message - """Alias for :meth:`pin_chat_message`""" - unpinChatMessage = unpin_chat_message - """Alias for :meth:`unpin_chat_message`""" - unpinAllChatMessages = unpin_all_chat_messages - """Alias for :meth:`unpin_all_chat_messages`""" - getStickerSet = get_sticker_set - """Alias for :meth:`get_sticker_set`""" - uploadStickerFile = upload_sticker_file - """Alias for :meth:`upload_sticker_file`""" - createNewStickerSet = create_new_sticker_set - """Alias for :meth:`create_new_sticker_set`""" - addStickerToSet = add_sticker_to_set - """Alias for :meth:`add_sticker_to_set`""" - setStickerPositionInSet = set_sticker_position_in_set - """Alias for :meth:`set_sticker_position_in_set`""" - deleteStickerFromSet = delete_sticker_from_set - """Alias for :meth:`delete_sticker_from_set`""" - setStickerSetThumb = set_sticker_set_thumb - """Alias for :meth:`set_sticker_set_thumb`""" - setPassportDataErrors = set_passport_data_errors - """Alias for :meth:`set_passport_data_errors`""" - sendPoll = send_poll - """Alias for :meth:`send_poll`""" - stopPoll = stop_poll - """Alias for :meth:`stop_poll`""" - sendDice = send_dice - """Alias for :meth:`send_dice`""" - getMyCommands = get_my_commands - """Alias for :meth:`get_my_commands`""" - setMyCommands = set_my_commands - """Alias for :meth:`set_my_commands`""" - deleteMyCommands = delete_my_commands - """Alias for :meth:`delete_my_commands`""" - logOut = log_out - """Alias for :meth:`log_out`""" - copyMessage = copy_message - """Alias for :meth:`copy_message`""" diff --git a/telegramer/include/telegram/botcommand.py b/telegramer/include/telegram/botcommand.py deleted file mode 100644 index 7d0f2da..0000000 --- a/telegramer/include/telegram/botcommand.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Bot Command.""" -from typing import Any - -from telegram import TelegramObject - - -class BotCommand(TelegramObject): - """ - This object represents a bot command. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`command` and :attr:`description` are equal. - - Args: - command (:obj:`str`): Text of the command, 1-32 characters. Can contain only lowercase - English letters, digits and underscores. - description (:obj:`str`): Description of the command, 1-256 characters. - - Attributes: - command (:obj:`str`): Text of the command. - description (:obj:`str`): Description of the command. - - """ - - __slots__ = ('description', '_id_attrs', 'command') - - def __init__(self, command: str, description: str, **_kwargs: Any): - self.command = command - self.description = description - - self._id_attrs = (self.command, self.description) diff --git a/telegramer/include/telegram/botcommandscope.py b/telegramer/include/telegram/botcommandscope.py deleted file mode 100644 index 283f768..0000000 --- a/telegramer/include/telegram/botcommandscope.py +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=W0622 -"""This module contains objects representing Telegram bot command scopes.""" -from typing import Any, Union, Optional, TYPE_CHECKING, Dict, Type - -from telegram import TelegramObject, constants -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class BotCommandScope(TelegramObject): - """Base class for objects that represent the scope to which bot commands are applied. - Currently, the following 7 scopes are supported: - - * :class:`telegram.BotCommandScopeDefault` - * :class:`telegram.BotCommandScopeAllPrivateChats` - * :class:`telegram.BotCommandScopeAllGroupChats` - * :class:`telegram.BotCommandScopeAllChatAdministrators` - * :class:`telegram.BotCommandScopeChat` - * :class:`telegram.BotCommandScopeChatAdministrators` - * :class:`telegram.BotCommandScopeChatMember` - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`type` is equal. For subclasses with additional attributes, - the notion of equality is overridden. - - Note: - Please see the `official docs`_ on how Telegram determines which commands to display. - - .. _`official docs`: https://core.telegram.org/bots/api#determining-list-of-commands - - .. versionadded:: 13.7 - - Args: - type (:obj:`str`): Scope type. - - Attributes: - type (:obj:`str`): Scope type. - """ - - __slots__ = ('type', '_id_attrs') - - DEFAULT = constants.BOT_COMMAND_SCOPE_DEFAULT - """:const:`telegram.constants.BOT_COMMAND_SCOPE_DEFAULT`""" - ALL_PRIVATE_CHATS = constants.BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS - """:const:`telegram.constants.BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS`""" - ALL_GROUP_CHATS = constants.BOT_COMMAND_SCOPE_ALL_GROUP_CHATS - """:const:`telegram.constants.BOT_COMMAND_SCOPE_ALL_GROUP_CHATS`""" - ALL_CHAT_ADMINISTRATORS = constants.BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS - """:const:`telegram.constants.BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS`""" - CHAT = constants.BOT_COMMAND_SCOPE_CHAT - """:const:`telegram.constants.BOT_COMMAND_SCOPE_CHAT`""" - CHAT_ADMINISTRATORS = constants.BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS - """:const:`telegram.constants.BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS`""" - CHAT_MEMBER = constants.BOT_COMMAND_SCOPE_CHAT_MEMBER - """:const:`telegram.constants.BOT_COMMAND_SCOPE_CHAT_MEMBER`""" - - def __init__(self, type: str, **_kwargs: Any): - self.type = type - self._id_attrs = (self.type,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['BotCommandScope']: - """Converts JSON data to the appropriate :class:`BotCommandScope` object, i.e. takes - care of selecting the correct subclass. - - Args: - data (Dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with this object. - - Returns: - The Telegram object. - - """ - data = cls._parse_data(data) - - if not data: - return None - - _class_mapping: Dict[str, Type['BotCommandScope']] = { - cls.DEFAULT: BotCommandScopeDefault, - cls.ALL_PRIVATE_CHATS: BotCommandScopeAllPrivateChats, - cls.ALL_GROUP_CHATS: BotCommandScopeAllGroupChats, - cls.ALL_CHAT_ADMINISTRATORS: BotCommandScopeAllChatAdministrators, - cls.CHAT: BotCommandScopeChat, - cls.CHAT_ADMINISTRATORS: BotCommandScopeChatAdministrators, - cls.CHAT_MEMBER: BotCommandScopeChatMember, - } - - if cls is BotCommandScope: - return _class_mapping.get(data['type'], cls)(**data, bot=bot) - return cls(**data) - - -class BotCommandScopeDefault(BotCommandScope): - """Represents the default scope of bot commands. Default commands are used if no commands with - a `narrower scope`_ are specified for the user. - - .. _`narrower scope`: https://core.telegram.org/bots/api#determining-list-of-commands - - .. versionadded:: 13.7 - - Attributes: - type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.DEFAULT`. - """ - - __slots__ = () - - def __init__(self, **_kwargs: Any): - super().__init__(type=BotCommandScope.DEFAULT) - - -class BotCommandScopeAllPrivateChats(BotCommandScope): - """Represents the scope of bot commands, covering all private chats. - - .. versionadded:: 13.7 - - Attributes: - type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.ALL_PRIVATE_CHATS`. - """ - - __slots__ = () - - def __init__(self, **_kwargs: Any): - super().__init__(type=BotCommandScope.ALL_PRIVATE_CHATS) - - -class BotCommandScopeAllGroupChats(BotCommandScope): - """Represents the scope of bot commands, covering all group and supergroup chats. - - .. versionadded:: 13.7 - - Attributes: - type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.ALL_GROUP_CHATS`. - """ - - __slots__ = () - - def __init__(self, **_kwargs: Any): - super().__init__(type=BotCommandScope.ALL_GROUP_CHATS) - - -class BotCommandScopeAllChatAdministrators(BotCommandScope): - """Represents the scope of bot commands, covering all group and supergroup chat administrators. - - .. versionadded:: 13.7 - - Attributes: - type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.ALL_CHAT_ADMINISTRATORS`. - """ - - __slots__ = () - - def __init__(self, **_kwargs: Any): - super().__init__(type=BotCommandScope.ALL_CHAT_ADMINISTRATORS) - - -class BotCommandScopeChat(BotCommandScope): - """Represents the scope of bot commands, covering a specific chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`type` and :attr:`chat_id` are equal. - - .. versionadded:: 13.7 - - Args: - chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the - target supergroup (in the format ``@supergroupusername``) - - Attributes: - type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.CHAT`. - chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the - target supergroup (in the format ``@supergroupusername``) - """ - - __slots__ = ('chat_id',) - - def __init__(self, chat_id: Union[str, int], **_kwargs: Any): - super().__init__(type=BotCommandScope.CHAT) - self.chat_id = ( - chat_id if isinstance(chat_id, str) and chat_id.startswith('@') else int(chat_id) - ) - self._id_attrs = (self.type, self.chat_id) - - -class BotCommandScopeChatAdministrators(BotCommandScope): - """Represents the scope of bot commands, covering all administrators of a specific group or - supergroup chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`type` and :attr:`chat_id` are equal. - - .. versionadded:: 13.7 - - Args: - chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the - target supergroup (in the format ``@supergroupusername``) - - Attributes: - type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.CHAT_ADMINISTRATORS`. - chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the - target supergroup (in the format ``@supergroupusername``) - """ - - __slots__ = ('chat_id',) - - def __init__(self, chat_id: Union[str, int], **_kwargs: Any): - super().__init__(type=BotCommandScope.CHAT_ADMINISTRATORS) - self.chat_id = ( - chat_id if isinstance(chat_id, str) and chat_id.startswith('@') else int(chat_id) - ) - self._id_attrs = (self.type, self.chat_id) - - -class BotCommandScopeChatMember(BotCommandScope): - """Represents the scope of bot commands, covering a specific member of a group or supergroup - chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`type`, :attr:`chat_id` and :attr:`user_id` are equal. - - .. versionadded:: 13.7 - - Args: - chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the - target supergroup (in the format ``@supergroupusername``) - user_id (:obj:`int`): Unique identifier of the target user. - - Attributes: - type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.CHAT_MEMBER`. - chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the - target supergroup (in the format ``@supergroupusername``) - user_id (:obj:`int`): Unique identifier of the target user. - """ - - __slots__ = ('chat_id', 'user_id') - - def __init__(self, chat_id: Union[str, int], user_id: int, **_kwargs: Any): - super().__init__(type=BotCommandScope.CHAT_MEMBER) - self.chat_id = ( - chat_id if isinstance(chat_id, str) and chat_id.startswith('@') else int(chat_id) - ) - self.user_id = int(user_id) - self._id_attrs = (self.type, self.chat_id, self.user_id) diff --git a/telegramer/include/telegram/callbackquery.py b/telegramer/include/telegram/callbackquery.py deleted file mode 100644 index b783636..0000000 --- a/telegramer/include/telegram/callbackquery.py +++ /dev/null @@ -1,660 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=W0622 -"""This module contains an object that represents a Telegram CallbackQuery""" -from typing import TYPE_CHECKING, Any, List, Optional, Union, Tuple, ClassVar - -from telegram import Message, TelegramObject, User, Location, ReplyMarkup, constants -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput, DVInput - -if TYPE_CHECKING: - from telegram import ( - Bot, - GameHighScore, - InlineKeyboardMarkup, - MessageId, - InputMedia, - MessageEntity, - ) - - -class CallbackQuery(TelegramObject): - """ - This object represents an incoming callback query from a callback button in an inline keyboard. - - If the button that originated the query was attached to a message sent by the bot, the field - :attr:`message` will be present. If the button was attached to a message sent via the bot (in - inline mode), the field :attr:`inline_message_id` will be present. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Note: - * In Python ``from`` is a reserved word, use ``from_user`` instead. - * Exactly one of the fields :attr:`data` or :attr:`game_short_name` will be present. - * After the user presses an inline button, Telegram clients will display a progress bar - until you call :attr:`answer`. It is, therefore, necessary to react - by calling :attr:`telegram.Bot.answer_callback_query` even if no notification to the user - is needed (e.g., without specifying any of the optional parameters). - * If you're using :attr:`Bot.arbitrary_callback_data`, :attr:`data` may be an instance - of :class:`telegram.ext.InvalidCallbackData`. This will be the case, if the data - associated with the button triggering the :class:`telegram.CallbackQuery` was already - deleted or if :attr:`data` was manipulated by a malicious client. - - .. versionadded:: 13.6 - - - Args: - id (:obj:`str`): Unique identifier for this query. - from_user (:class:`telegram.User`): Sender. - chat_instance (:obj:`str`): Global identifier, uniquely corresponding to the chat to which - the message with the callback button was sent. Useful for high scores in games. - message (:class:`telegram.Message`, optional): Message with the callback button that - originated the query. Note that message content and message date will not be available - if the message is too old. - data (:obj:`str`, optional): Data associated with the callback button. Be aware that a bad - client can send arbitrary data in this field. - inline_message_id (:obj:`str`, optional): Identifier of the message sent via the bot in - inline mode, that originated the query. - game_short_name (:obj:`str`, optional): Short name of a Game to be returned, serves as - the unique identifier for the game - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - - Attributes: - id (:obj:`str`): Unique identifier for this query. - from_user (:class:`telegram.User`): Sender. - chat_instance (:obj:`str`): Global identifier, uniquely corresponding to the chat to which - the message with the callback button was sent. - message (:class:`telegram.Message`): Optional. Message with the callback button that - originated the query. - data (:obj:`str` | :obj:`object`): Optional. Data associated with the callback button. - inline_message_id (:obj:`str`): Optional. Identifier of the message sent via the bot in - inline mode, that originated the query. - game_short_name (:obj:`str`): Optional. Short name of a Game to be returned. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'game_short_name', - 'message', - 'chat_instance', - 'id', - 'from_user', - 'inline_message_id', - 'data', - '_id_attrs', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - from_user: User, - chat_instance: str, - message: Message = None, - data: str = None, - inline_message_id: str = None, - game_short_name: str = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.id = id # pylint: disable=C0103 - self.from_user = from_user - self.chat_instance = chat_instance - # Optionals - self.message = message - self.data = data - self.inline_message_id = inline_message_id - self.game_short_name = game_short_name - - self.bot = bot - - self._id_attrs = (self.id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['CallbackQuery']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['from_user'] = User.de_json(data.get('from'), bot) - data['message'] = Message.de_json(data.get('message'), bot) - - return cls(bot=bot, **data) - - def answer( - self, - text: str = None, - show_alert: bool = False, - url: str = None, - cache_time: int = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.answer_callback_query(update.callback_query.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.answer_callback_query`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.answer_callback_query( - callback_query_id=self.id, - text=text, - show_alert=show_alert, - url=url, - cache_time=cache_time, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def edit_message_text( - self, - text: str, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - ) -> Union[Message, bool]: - """Shortcut for either:: - - update.callback_query.message.edit_text(text, *args, **kwargs) - - or:: - - bot.edit_message_text(text, inline_message_id=update.callback_query.inline_message_id, - *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_text` and :meth:`telegram.Message.edit_text`. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - """ - if self.inline_message_id: - return self.bot.edit_message_text( - inline_message_id=self.inline_message_id, - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - entities=entities, - chat_id=None, - message_id=None, - ) - return self.message.edit_text( - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - entities=entities, - ) - - def edit_message_caption( - self, - caption: str = None, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - ) -> Union[Message, bool]: - """Shortcut for either:: - - update.callback_query.message.edit_caption(caption, *args, **kwargs) - - or:: - - bot.edit_message_caption(caption=caption - inline_message_id=update.callback_query.inline_message_id, - *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_caption` and :meth:`telegram.Message.edit_caption`. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - """ - if self.inline_message_id: - return self.bot.edit_message_caption( - caption=caption, - inline_message_id=self.inline_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - caption_entities=caption_entities, - chat_id=None, - message_id=None, - ) - return self.message.edit_caption( - caption=caption, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - caption_entities=caption_entities, - ) - - def edit_message_reply_markup( - self, - reply_markup: Optional['InlineKeyboardMarkup'] = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """Shortcut for either:: - - update.callback_query.message.edit_reply_markup( - reply_markup=reply_markup, - *args, - **kwargs - ) - - or:: - - bot.edit_message_reply_markup - inline_message_id=update.callback_query.inline_message_id, - reply_markup=reply_markup, - *args, - **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_reply_markup` and - :meth:`telegram.Message.edit_reply_markup`. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - """ - if self.inline_message_id: - return self.bot.edit_message_reply_markup( - reply_markup=reply_markup, - inline_message_id=self.inline_message_id, - timeout=timeout, - api_kwargs=api_kwargs, - chat_id=None, - message_id=None, - ) - return self.message.edit_reply_markup( - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def edit_message_media( - self, - media: 'InputMedia' = None, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """Shortcut for either:: - - update.callback_query.message.edit_media(*args, **kwargs) - - or:: - - bot.edit_message_media(inline_message_id=update.callback_query.inline_message_id, - *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_media` and :meth:`telegram.Message.edit_media`. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - """ - if self.inline_message_id: - return self.bot.edit_message_media( - inline_message_id=self.inline_message_id, - media=media, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - chat_id=None, - message_id=None, - ) - return self.message.edit_media( - media=media, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def edit_message_live_location( - self, - latitude: float = None, - longitude: float = None, - location: Location = None, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - ) -> Union[Message, bool]: - """Shortcut for either:: - - update.callback_query.message.edit_live_location(*args, **kwargs) - - or:: - - bot.edit_message_live_location( - inline_message_id=update.callback_query.inline_message_id, - *args, **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_live_location` and - :meth:`telegram.Message.edit_live_location`. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - """ - if self.inline_message_id: - return self.bot.edit_message_live_location( - inline_message_id=self.inline_message_id, - latitude=latitude, - longitude=longitude, - location=location, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - horizontal_accuracy=horizontal_accuracy, - heading=heading, - proximity_alert_radius=proximity_alert_radius, - chat_id=None, - message_id=None, - ) - return self.message.edit_live_location( - latitude=latitude, - longitude=longitude, - location=location, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - horizontal_accuracy=horizontal_accuracy, - heading=heading, - proximity_alert_radius=proximity_alert_radius, - ) - - def stop_message_live_location( - self, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """Shortcut for either:: - - update.callback_query.message.stop_live_location(*args, **kwargs) - - or:: - - bot.stop_message_live_location( - inline_message_id=update.callback_query.inline_message_id, - *args, **kwargs - ) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.stop_message_live_location` and - :meth:`telegram.Message.stop_live_location`. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - """ - if self.inline_message_id: - return self.bot.stop_message_live_location( - inline_message_id=self.inline_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - chat_id=None, - message_id=None, - ) - return self.message.stop_live_location( - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def set_game_score( - self, - user_id: Union[int, str], - score: int, - force: bool = None, - disable_edit_message: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union[Message, bool]: - """Shortcut for either:: - - update.callback_query.message.set_game_score(*args, **kwargs) - - or:: - - bot.set_game_score(inline_message_id=update.callback_query.inline_message_id, - *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.set_game_score` and :meth:`telegram.Message.set_game_score`. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - - """ - if self.inline_message_id: - return self.bot.set_game_score( - inline_message_id=self.inline_message_id, - user_id=user_id, - score=score, - force=force, - disable_edit_message=disable_edit_message, - timeout=timeout, - api_kwargs=api_kwargs, - chat_id=None, - message_id=None, - ) - return self.message.set_game_score( - user_id=user_id, - score=score, - force=force, - disable_edit_message=disable_edit_message, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def get_game_high_scores( - self, - user_id: Union[int, str], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> List['GameHighScore']: - """Shortcut for either:: - - update.callback_query.message.get_game_high_score(*args, **kwargs) - - or:: - - bot.get_game_high_scores(inline_message_id=update.callback_query.inline_message_id, - *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.get_game_high_scores` and :meth:`telegram.Message.get_game_high_score`. - - Returns: - List[:class:`telegram.GameHighScore`] - - """ - if self.inline_message_id: - return self.bot.get_game_high_scores( - inline_message_id=self.inline_message_id, - user_id=user_id, - timeout=timeout, - api_kwargs=api_kwargs, - chat_id=None, - message_id=None, - ) - return self.message.get_game_high_scores( - user_id=user_id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def delete_message( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - update.callback_query.message.delete(*args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Message.delete`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.message.delete( - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def pin_message( - self, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - update.callback_query.message.pin(*args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Message.pin`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.message.pin( - disable_notification=disable_notification, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def unpin_message( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - update.callback_query.message.unpin(*args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Message.unpin`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.message.unpin( - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def copy_message( - self, - chat_id: Union[int, str], - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> 'MessageId': - """Shortcut for:: - - update.callback_query.message.copy( - chat_id, - from_chat_id=update.message.chat_id, - message_id=update.message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Message.copy`. - - Returns: - :class:`telegram.MessageId`: On success, returns the MessageId of the sent message. - - """ - return self.message.copy( - chat_id=chat_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - MAX_ANSWER_TEXT_LENGTH: ClassVar[int] = constants.MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH - """ - :const:`telegram.constants.MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH` - - .. versionadded:: 13.2 - """ diff --git a/telegramer/include/telegram/chat.py b/telegramer/include/telegram/chat.py deleted file mode 100644 index b5dbfe4..0000000 --- a/telegramer/include/telegram/chat.py +++ /dev/null @@ -1,1815 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=W0622 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Chat.""" -import warnings -from datetime import datetime -from typing import TYPE_CHECKING, List, Optional, ClassVar, Union, Tuple, Any - -from telegram import ChatPhoto, TelegramObject, constants -from telegram.utils.types import JSONDict, FileInput, ODVInput, DVInput -from telegram.utils.deprecate import TelegramDeprecationWarning - -from .chatpermissions import ChatPermissions -from .chatlocation import ChatLocation -from .utils.helpers import DEFAULT_NONE, DEFAULT_20 - -if TYPE_CHECKING: - from telegram import ( - Bot, - ChatMember, - ChatInviteLink, - Message, - MessageId, - ReplyMarkup, - Contact, - InlineKeyboardMarkup, - Location, - Venue, - MessageEntity, - InputMediaAudio, - InputMediaDocument, - InputMediaPhoto, - InputMediaVideo, - PhotoSize, - Audio, - Document, - Animation, - LabeledPrice, - Sticker, - Video, - VideoNote, - Voice, - ) - - -class Chat(TelegramObject): - """This object represents a chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Args: - id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits - and some programming languages may have difficulty/silent defects in interpreting it. - But it is smaller than 52 bits, so a signed 64 bit integer or double-precision float - type are safe for storing this identifier. - type (:obj:`str`): Type of chat, can be either 'private', 'group', 'supergroup' or - 'channel'. - title (:obj:`str`, optional): Title, for supergroups, channels and group chats. - username(:obj:`str`, optional): Username, for private chats, supergroups and channels if - available. - first_name(:obj:`str`, optional): First name of the other party in a private chat. - last_name(:obj:`str`, optional): Last name of the other party in a private chat. - photo (:class:`telegram.ChatPhoto`, optional): Chat photo. - Returned only in :meth:`telegram.Bot.get_chat`. - bio (:obj:`str`, optional): Bio of the other party in a private chat. Returned only in - :meth:`telegram.Bot.get_chat`. - has_private_forwards (:obj:`bool`, optional): :obj:`True`, if privacy settings of the other - party in the private chat allows to use ``tg://user?id=`` links only in chats - with the user. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.9 - description (:obj:`str`, optional): Description, for groups, supergroups and channel chats. - Returned only in :meth:`telegram.Bot.get_chat`. - invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and - channel. Returned only in :meth:`telegram.Bot.get_chat`. - pinned_message (:class:`telegram.Message`, optional): The most recent pinned message - (by sending date). Returned only in :meth:`telegram.Bot.get_chat`. - permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, - for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`. - slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between - consecutive messages sent by each unprivileged user. - Returned only in :meth:`telegram.Bot.get_chat`. - message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to - the chat will be automatically deleted; in seconds. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.4 - has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't - be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.9 - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set. - Returned only in :meth:`telegram.Bot.get_chat`. - can_set_sticker_set (:obj:`bool`, optional): :obj:`True`, if the bot can change group the - sticker set. Returned only in :meth:`telegram.Bot.get_chat`. - linked_chat_id (:obj:`int`, optional): Unique identifier for the linked chat, i.e. the - discussion group identifier for a channel and vice versa; for supergroups and channel - chats. Returned only in :meth:`telegram.Bot.get_chat`. - location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which - the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - id (:obj:`int`): Unique identifier for this chat. - type (:obj:`str`): Type of chat. - title (:obj:`str`): Optional. Title, for supergroups, channels and group chats. - username (:obj:`str`): Optional. Username. - first_name (:obj:`str`): Optional. First name of the other party in a private chat. - last_name (:obj:`str`): Optional. Last name of the other party in a private chat. - photo (:class:`telegram.ChatPhoto`): Optional. Chat photo. - bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in - :meth:`telegram.Bot.get_chat`. - has_private_forwards (:obj:`bool`): Optional. :obj:`True`, if privacy settings of the other - party in the private chat allows to use ``tg://user?id=`` links only in chats - with the user. - - .. versionadded:: 13.9 - description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats. - invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and - channel. Returned only in :meth:`telegram.Bot.get_chat`. - pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message - (by sending date). Returned only in :meth:`telegram.Bot.get_chat`. - permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, - for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`. - slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between - consecutive messages sent by each unprivileged user. Returned only in - :meth:`telegram.Bot.get_chat`. - message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to - the chat will be automatically deleted; in seconds. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.4 - has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't - be forwarded to other chats. - - .. versionadded:: 13.9 - sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set. - can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the - sticker set. - linked_chat_id (:obj:`int`): Optional. Unique identifier for the linked chat, i.e. the - discussion group identifier for a channel and vice versa; for supergroups and channel - chats. Returned only in :meth:`telegram.Bot.get_chat`. - location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which - the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`. - - """ - - __slots__ = ( - 'bio', - 'id', - 'type', - 'last_name', - 'bot', - 'sticker_set_name', - 'slow_mode_delay', - 'location', - 'first_name', - 'permissions', - 'invite_link', - 'pinned_message', - 'description', - 'can_set_sticker_set', - 'username', - 'title', - 'photo', - 'linked_chat_id', - 'all_members_are_administrators', - 'message_auto_delete_time', - 'has_protected_content', - 'has_private_forwards', - '_id_attrs', - ) - - SENDER: ClassVar[str] = constants.CHAT_SENDER - """:const:`telegram.constants.CHAT_SENDER` - - .. versionadded:: 13.5 - """ - PRIVATE: ClassVar[str] = constants.CHAT_PRIVATE - """:const:`telegram.constants.CHAT_PRIVATE`""" - GROUP: ClassVar[str] = constants.CHAT_GROUP - """:const:`telegram.constants.CHAT_GROUP`""" - SUPERGROUP: ClassVar[str] = constants.CHAT_SUPERGROUP - """:const:`telegram.constants.CHAT_SUPERGROUP`""" - CHANNEL: ClassVar[str] = constants.CHAT_CHANNEL - """:const:`telegram.constants.CHAT_CHANNEL`""" - - def __init__( - self, - id: int, - type: str, - title: str = None, - username: str = None, - first_name: str = None, - last_name: str = None, - bot: 'Bot' = None, - photo: ChatPhoto = None, - description: str = None, - invite_link: str = None, - pinned_message: 'Message' = None, - permissions: ChatPermissions = None, - sticker_set_name: str = None, - can_set_sticker_set: bool = None, - slow_mode_delay: int = None, - bio: str = None, - linked_chat_id: int = None, - location: ChatLocation = None, - message_auto_delete_time: int = None, - has_private_forwards: bool = None, - has_protected_content: bool = None, - **_kwargs: Any, - ): - # Required - self.id = int(id) # pylint: disable=C0103 - self.type = type - # Optionals - self.title = title - self.username = username - self.first_name = first_name - self.last_name = last_name - # TODO: Remove (also from tests), when Telegram drops this completely - self.all_members_are_administrators = _kwargs.get('all_members_are_administrators') - self.photo = photo - self.bio = bio - self.has_private_forwards = has_private_forwards - self.description = description - self.invite_link = invite_link - self.pinned_message = pinned_message - self.permissions = permissions - self.slow_mode_delay = slow_mode_delay - self.message_auto_delete_time = ( - int(message_auto_delete_time) if message_auto_delete_time is not None else None - ) - self.has_protected_content = has_protected_content - self.sticker_set_name = sticker_set_name - self.can_set_sticker_set = can_set_sticker_set - self.linked_chat_id = linked_chat_id - self.location = location - - self.bot = bot - self._id_attrs = (self.id,) - - @property - def full_name(self) -> Optional[str]: - """ - :obj:`str`: Convenience property. If :attr:`first_name` is not :obj:`None` gives, - :attr:`first_name` followed by (if available) :attr:`last_name`. - - Note: - :attr:`full_name` will always be :obj:`None`, if the chat is a (super)group or - channel. - - .. versionadded:: 13.2 - """ - if not self.first_name: - return None - if self.last_name: - return f'{self.first_name} {self.last_name}' - return self.first_name - - @property - def link(self) -> Optional[str]: - """:obj:`str`: Convenience property. If the chat has a :attr:`username`, returns a t.me - link of the chat. - """ - if self.username: - return f"https://t.me/{self.username}" - return None - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Chat']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['photo'] = ChatPhoto.de_json(data.get('photo'), bot) - from telegram import Message # pylint: disable=C0415 - - data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot) - data['permissions'] = ChatPermissions.de_json(data.get('permissions'), bot) - data['location'] = ChatLocation.de_json(data.get('location'), bot) - - return cls(bot=bot, **data) - - def leave(self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None) -> bool: - """Shortcut for:: - - bot.leave_chat(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.leave_chat`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.leave_chat( - chat_id=self.id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def get_administrators( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> List['ChatMember']: - """Shortcut for:: - - bot.get_chat_administrators(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.get_chat_administrators`. - - Returns: - List[:class:`telegram.ChatMember`]: A list of administrators in a chat. An Array of - :class:`telegram.ChatMember` objects that contains information about all - chat administrators except other bots. If the chat is a group or a supergroup - and no administrators were appointed, only the creator will be returned. - - """ - return self.bot.get_chat_administrators( - chat_id=self.id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def get_members_count( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> int: - """ - Deprecated, use :func:`~telegram.Chat.get_member_count` instead. - - .. deprecated:: 13.7 - """ - warnings.warn( - '`Chat.get_members_count` is deprecated. Use `Chat.get_member_count` instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - - return self.get_member_count( - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def get_member_count( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> int: - """Shortcut for:: - - bot.get_chat_member_count(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.get_chat_member_count`. - - Returns: - :obj:`int` - """ - return self.bot.get_chat_member_count( - chat_id=self.id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def get_member( - self, - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> 'ChatMember': - """Shortcut for:: - - bot.get_chat_member(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_chat_member`. - - Returns: - :class:`telegram.ChatMember` - - """ - return self.bot.get_chat_member( - chat_id=self.id, - user_id=user_id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def kick_member( - self, - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - until_date: Union[int, datetime] = None, - api_kwargs: JSONDict = None, - revoke_messages: bool = None, - ) -> bool: - """ - Deprecated, use :func:`~telegram.Chat.ban_member` instead. - - .. deprecated:: 13.7 - """ - warnings.warn( - '`Chat.kick_member` is deprecated. Use `Chat.ban_member` instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - - return self.ban_member( - user_id=user_id, - timeout=timeout, - until_date=until_date, - api_kwargs=api_kwargs, - revoke_messages=revoke_messages, - ) - - def ban_member( - self, - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - until_date: Union[int, datetime] = None, - api_kwargs: JSONDict = None, - revoke_messages: bool = None, - ) -> bool: - """Shortcut for:: - - bot.ban_chat_member(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.ban_chat_member`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - """ - return self.bot.ban_chat_member( - chat_id=self.id, - user_id=user_id, - timeout=timeout, - until_date=until_date, - api_kwargs=api_kwargs, - revoke_messages=revoke_messages, - ) - - def ban_sender_chat( - self, - sender_chat_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.ban_chat_sender_chat(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.ban_chat_sender_chat`. - - .. versionadded:: 13.9 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.ban_chat_sender_chat( - chat_id=self.id, sender_chat_id=sender_chat_id, timeout=timeout, api_kwargs=api_kwargs - ) - - def ban_chat( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.ban_chat_sender_chat(sender_chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.ban_chat_sender_chat`. - - .. versionadded:: 13.9 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.ban_chat_sender_chat( - chat_id=chat_id, sender_chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs - ) - - def unban_sender_chat( - self, - sender_chat_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.unban_chat_sender_chat(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.unban_chat_sender_chat`. - - .. versionadded:: 13.9 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unban_chat_sender_chat( - chat_id=self.id, sender_chat_id=sender_chat_id, timeout=timeout, api_kwargs=api_kwargs - ) - - def unban_chat( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.unban_chat_sender_chat(sender_chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.unban_chat_sender_chat`. - - .. versionadded:: 13.9 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unban_chat_sender_chat( - chat_id=chat_id, sender_chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs - ) - - def unban_member( - self, - user_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - only_if_banned: bool = None, - ) -> bool: - """Shortcut for:: - - bot.unban_chat_member(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.unban_chat_member`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unban_chat_member( - chat_id=self.id, - user_id=user_id, - timeout=timeout, - api_kwargs=api_kwargs, - only_if_banned=only_if_banned, - ) - - def promote_member( - self, - user_id: Union[str, int], - can_change_info: bool = None, - can_post_messages: bool = None, - can_edit_messages: bool = None, - can_delete_messages: bool = None, - can_invite_users: bool = None, - can_restrict_members: bool = None, - can_pin_messages: bool = None, - can_promote_members: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - is_anonymous: bool = None, - can_manage_chat: bool = None, - can_manage_voice_chats: bool = None, - ) -> bool: - """Shortcut for:: - - bot.promote_chat_member(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.promote_chat_member`. - - .. versionadded:: 13.2 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.promote_chat_member( - chat_id=self.id, - user_id=user_id, - can_change_info=can_change_info, - can_post_messages=can_post_messages, - can_edit_messages=can_edit_messages, - can_delete_messages=can_delete_messages, - can_invite_users=can_invite_users, - can_restrict_members=can_restrict_members, - can_pin_messages=can_pin_messages, - can_promote_members=can_promote_members, - timeout=timeout, - api_kwargs=api_kwargs, - is_anonymous=is_anonymous, - can_manage_chat=can_manage_chat, - can_manage_voice_chats=can_manage_voice_chats, - ) - - def restrict_member( - self, - user_id: Union[str, int], - permissions: ChatPermissions, - until_date: Union[int, datetime] = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.restrict_chat_member(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.restrict_chat_member`. - - .. versionadded:: 13.2 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.restrict_chat_member( - chat_id=self.id, - user_id=user_id, - permissions=permissions, - until_date=until_date, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def set_permissions( - self, - permissions: ChatPermissions, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.set_chat_permissions(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.set_chat_permissions`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.set_chat_permissions( - chat_id=self.id, - permissions=permissions, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def set_administrator_custom_title( - self, - user_id: Union[int, str], - custom_title: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.set_chat_administrator_custom_title(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.set_chat_administrator_custom_title`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.set_chat_administrator_custom_title( - chat_id=self.id, - user_id=user_id, - custom_title=custom_title, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def pin_message( - self, - message_id: int, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.pin_chat_message(chat_id=update.effective_chat.id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.pin_chat_message`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.pin_chat_message( - chat_id=self.id, - message_id=message_id, - disable_notification=disable_notification, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def unpin_message( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - message_id: int = None, - ) -> bool: - """Shortcut for:: - - bot.unpin_chat_message(chat_id=update.effective_chat.id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.unpin_chat_message`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unpin_chat_message( - chat_id=self.id, - timeout=timeout, - api_kwargs=api_kwargs, - message_id=message_id, - ) - - def unpin_all_messages( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.unpin_all_chat_messages(chat_id=update.effective_chat.id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.unpin_all_chat_messages`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unpin_all_chat_messages( - chat_id=self.id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def send_message( - self, - text: str, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_message(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_message( - chat_id=self.id, - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - entities=entities, - protect_content=protect_content, - ) - - def send_media_group( - self, - media: List[ - Union['InputMediaAudio', 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo'] - ], - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> List['Message']: - """Shortcut for:: - - bot.send_media_group(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_media_group`. - - Returns: - List[:class:`telegram.Message`]: On success, instance representing the message posted. - - """ - return self.bot.send_media_group( - chat_id=self.id, - media=media, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_chat_action( - self, - action: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.send_chat_action(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_chat_action`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.send_chat_action( - chat_id=self.id, - action=action, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - send_action = send_chat_action - """Alias for :attr:`send_chat_action`""" - - def send_photo( - self, - photo: Union[FileInput, 'PhotoSize'], - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_photo(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_photo`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_photo( - chat_id=self.id, - photo=photo, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_contact( - self, - phone_number: str = None, - first_name: str = None, - last_name: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - contact: 'Contact' = None, - vcard: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_contact(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_contact`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_contact( - chat_id=self.id, - phone_number=phone_number, - first_name=first_name, - last_name=last_name, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - contact=contact, - vcard=vcard, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_audio( - self, - audio: Union[FileInput, 'Audio'], - duration: int = None, - performer: str = None, - title: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_audio(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_audio`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_audio( - chat_id=self.id, - audio=audio, - duration=duration, - performer=performer, - title=title, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_document( - self, - document: Union[FileInput, 'Document'], - filename: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - disable_content_type_detection: bool = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_document(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_document`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_document( - chat_id=self.id, - document=document, - filename=filename, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - thumb=thumb, - api_kwargs=api_kwargs, - disable_content_type_detection=disable_content_type_detection, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - protect_content=protect_content, - ) - - def send_dice( - self, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - emoji: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_dice(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_dice`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_dice( - chat_id=self.id, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - emoji=emoji, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_game( - self, - game_short_name: str, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_game(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_game`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_game( - chat_id=self.id, - game_short_name=game_short_name, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_invoice( - self, - title: str, - description: str, - payload: str, - provider_token: str, - currency: str, - prices: List['LabeledPrice'], - start_parameter: str = None, - photo_url: str = None, - photo_size: int = None, - photo_width: int = None, - photo_height: int = None, - need_name: bool = None, - need_phone_number: bool = None, - need_email: bool = None, - need_shipping_address: bool = None, - is_flexible: bool = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'InlineKeyboardMarkup' = None, - provider_data: Union[str, object] = None, - send_phone_number_to_provider: bool = None, - send_email_to_provider: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - max_tip_amount: int = None, - suggested_tip_amounts: List[int] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_invoice(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_invoice`. - - Warning: - As of API 5.2 :attr:`start_parameter` is an optional argument and therefore the order - of the arguments had to be changed. Use keyword arguments to make sure that the - arguments are passed correctly. - - .. versionchanged:: 13.5 - As of Bot API 5.2, the parameter :attr:`start_parameter` is optional. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_invoice( - chat_id=self.id, - title=title, - description=description, - payload=payload, - provider_token=provider_token, - currency=currency, - prices=prices, - start_parameter=start_parameter, - photo_url=photo_url, - photo_size=photo_size, - photo_width=photo_width, - photo_height=photo_height, - need_name=need_name, - need_phone_number=need_phone_number, - need_email=need_email, - need_shipping_address=need_shipping_address, - is_flexible=is_flexible, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - provider_data=provider_data, - send_phone_number_to_provider=send_phone_number_to_provider, - send_email_to_provider=send_email_to_provider, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - max_tip_amount=max_tip_amount, - suggested_tip_amounts=suggested_tip_amounts, - protect_content=protect_content, - ) - - def send_location( - self, - latitude: float = None, - longitude: float = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - location: 'Location' = None, - live_period: int = None, - api_kwargs: JSONDict = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_location(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_location`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_location( - chat_id=self.id, - latitude=latitude, - longitude=longitude, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - location=location, - live_period=live_period, - api_kwargs=api_kwargs, - horizontal_accuracy=horizontal_accuracy, - heading=heading, - proximity_alert_radius=proximity_alert_radius, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_animation( - self, - animation: Union[FileInput, 'Animation'], - duration: int = None, - width: int = None, - height: int = None, - thumb: FileInput = None, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_animation(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_animation`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_animation( - chat_id=self.id, - animation=animation, - duration=duration, - width=width, - height=height, - thumb=thumb, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_sticker( - self, - sticker: Union[FileInput, 'Sticker'], - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_sticker(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_sticker`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_sticker( - chat_id=self.id, - sticker=sticker, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_venue( - self, - latitude: float = None, - longitude: float = None, - title: str = None, - address: str = None, - foursquare_id: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - venue: 'Venue' = None, - foursquare_type: str = None, - api_kwargs: JSONDict = None, - google_place_id: str = None, - google_place_type: str = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_venue(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_venue`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_venue( - chat_id=self.id, - latitude=latitude, - longitude=longitude, - title=title, - address=address, - foursquare_id=foursquare_id, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - venue=venue, - foursquare_type=foursquare_type, - api_kwargs=api_kwargs, - google_place_id=google_place_id, - google_place_type=google_place_type, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_video( - self, - video: Union[FileInput, 'Video'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - width: int = None, - height: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - supports_streaming: bool = None, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_video(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_video`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_video( - chat_id=self.id, - video=video, - duration=duration, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - width=width, - height=height, - parse_mode=parse_mode, - supports_streaming=supports_streaming, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_video_note( - self, - video_note: Union[FileInput, 'VideoNote'], - duration: int = None, - length: int = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_video_note(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_video_note`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_video_note( - chat_id=self.id, - video_note=video_note, - duration=duration, - length=length, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - filename=filename, - protect_content=protect_content, - ) - - def send_voice( - self, - voice: Union[FileInput, 'Voice'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_voice(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_voice`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_voice( - chat_id=self.id, - voice=voice, - duration=duration, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_poll( - self, - question: str, - options: List[str], - is_anonymous: bool = True, - # We use constant.POLL_REGULAR instead of Poll.REGULAR here to avoid circular imports - type: str = constants.POLL_REGULAR, # pylint: disable=W0622 - allows_multiple_answers: bool = False, - correct_option_id: int = None, - is_closed: bool = None, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - explanation: str = None, - explanation_parse_mode: ODVInput[str] = DEFAULT_NONE, - open_period: int = None, - close_date: Union[int, datetime] = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_poll(update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_poll`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_poll( - chat_id=self.id, - question=question, - options=options, - is_anonymous=is_anonymous, - type=type, # pylint=pylint, - allows_multiple_answers=allows_multiple_answers, - correct_option_id=correct_option_id, - is_closed=is_closed, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - explanation=explanation, - explanation_parse_mode=explanation_parse_mode, - open_period=open_period, - close_date=close_date, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - explanation_entities=explanation_entities, - protect_content=protect_content, - ) - - def send_copy( - self, - from_chat_id: Union[str, int], - message_id: int, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> 'MessageId': - """Shortcut for:: - - bot.copy_message(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.copy_message`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.copy_message( - chat_id=self.id, - from_chat_id=from_chat_id, - message_id=message_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def copy_message( - self, - chat_id: Union[int, str], - message_id: int, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> 'MessageId': - """Shortcut for:: - - bot.copy_message(from_chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.copy_message`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.copy_message( - from_chat_id=self.id, - chat_id=chat_id, - message_id=message_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def export_invite_link( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> str: - """Shortcut for:: - - bot.export_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.export_chat_invite_link`. - - .. versionadded:: 13.4 - - Returns: - :obj:`str`: New invite link on success. - - """ - return self.bot.export_chat_invite_link( - chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs - ) - - def create_invite_link( - self, - expire_date: Union[int, datetime] = None, - member_limit: int = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - name: str = None, - creates_join_request: bool = None, - ) -> 'ChatInviteLink': - """Shortcut for:: - - bot.create_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.create_chat_invite_link`. - - .. versionadded:: 13.4 - - .. versionchanged:: 13.8 - Edited signature according to the changes of - :meth:`telegram.Bot.create_chat_invite_link`. - - Returns: - :class:`telegram.ChatInviteLink` - - """ - return self.bot.create_chat_invite_link( - chat_id=self.id, - expire_date=expire_date, - member_limit=member_limit, - timeout=timeout, - api_kwargs=api_kwargs, - name=name, - creates_join_request=creates_join_request, - ) - - def edit_invite_link( - self, - invite_link: str, - expire_date: Union[int, datetime] = None, - member_limit: int = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - name: str = None, - creates_join_request: bool = None, - ) -> 'ChatInviteLink': - """Shortcut for:: - - bot.edit_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_chat_invite_link`. - - .. versionadded:: 13.4 - - .. versionchanged:: 13.8 - Edited signature according to the changes of :meth:`telegram.Bot.edit_chat_invite_link`. - - Returns: - :class:`telegram.ChatInviteLink` - - """ - return self.bot.edit_chat_invite_link( - chat_id=self.id, - invite_link=invite_link, - expire_date=expire_date, - member_limit=member_limit, - timeout=timeout, - api_kwargs=api_kwargs, - name=name, - creates_join_request=creates_join_request, - ) - - def revoke_invite_link( - self, - invite_link: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> 'ChatInviteLink': - """Shortcut for:: - - bot.revoke_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.revoke_chat_invite_link`. - - .. versionadded:: 13.4 - - Returns: - :class:`telegram.ChatInviteLink` - - """ - return self.bot.revoke_chat_invite_link( - chat_id=self.id, invite_link=invite_link, timeout=timeout, api_kwargs=api_kwargs - ) - - def approve_join_request( - self, - user_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.approve_chat_join_request(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.approve_chat_join_request`. - - .. versionadded:: 13.8 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.approve_chat_join_request( - chat_id=self.id, user_id=user_id, timeout=timeout, api_kwargs=api_kwargs - ) - - def decline_join_request( - self, - user_id: int, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.decline_chat_join_request(chat_id=update.effective_chat.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.decline_chat_join_request`. - - .. versionadded:: 13.8 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.decline_chat_join_request( - chat_id=self.id, user_id=user_id, timeout=timeout, api_kwargs=api_kwargs - ) diff --git a/telegramer/include/telegram/chataction.py b/telegramer/include/telegram/chataction.py deleted file mode 100644 index 335ab39..0000000 --- a/telegramer/include/telegram/chataction.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ChatAction.""" -from typing import ClassVar -from telegram import constants -from telegram.utils.deprecate import set_new_attribute_deprecated - - -class ChatAction: - """Helper class to provide constants for different chat actions.""" - - __slots__ = ('__dict__',) # Adding __dict__ here since it doesn't subclass TGObject - FIND_LOCATION: ClassVar[str] = constants.CHATACTION_FIND_LOCATION - """:const:`telegram.constants.CHATACTION_FIND_LOCATION`""" - RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO - """:const:`telegram.constants.CHATACTION_RECORD_AUDIO` - - .. deprecated:: 13.5 - Deprecated by Telegram. Use :attr:`RECORD_VOICE` instead. - """ - RECORD_VOICE: ClassVar[str] = constants.CHATACTION_RECORD_VOICE - """:const:`telegram.constants.CHATACTION_RECORD_VOICE` - - .. versionadded:: 13.5 - """ - RECORD_VIDEO: ClassVar[str] = constants.CHATACTION_RECORD_VIDEO - """:const:`telegram.constants.CHATACTION_RECORD_VIDEO`""" - RECORD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_RECORD_VIDEO_NOTE - """:const:`telegram.constants.CHATACTION_RECORD_VIDEO_NOTE`""" - TYPING: ClassVar[str] = constants.CHATACTION_TYPING - """:const:`telegram.constants.CHATACTION_TYPING`""" - UPLOAD_AUDIO: ClassVar[str] = constants.CHATACTION_UPLOAD_AUDIO - """:const:`telegram.constants.CHATACTION_UPLOAD_AUDIO` - - .. deprecated:: 13.5 - Deprecated by Telegram. Use :attr:`UPLOAD_VOICE` instead. - """ - UPLOAD_VOICE: ClassVar[str] = constants.CHATACTION_UPLOAD_VOICE - """:const:`telegram.constants.CHATACTION_UPLOAD_VOICE` - - .. versionadded:: 13.5 - """ - UPLOAD_DOCUMENT: ClassVar[str] = constants.CHATACTION_UPLOAD_DOCUMENT - """:const:`telegram.constants.CHATACTION_UPLOAD_DOCUMENT`""" - CHOOSE_STICKER: ClassVar[str] = constants.CHATACTION_CHOOSE_STICKER - """:const:`telegram.constants.CHOOSE_STICKER` - - .. versionadded:: 13.8""" - UPLOAD_PHOTO: ClassVar[str] = constants.CHATACTION_UPLOAD_PHOTO - """:const:`telegram.constants.CHATACTION_UPLOAD_PHOTO`""" - UPLOAD_VIDEO: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO - """:const:`telegram.constants.CHATACTION_UPLOAD_VIDEO`""" - UPLOAD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO_NOTE - """:const:`telegram.constants.CHATACTION_UPLOAD_VIDEO_NOTE`""" - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) diff --git a/telegramer/include/telegram/chatinvitelink.py b/telegramer/include/telegram/chatinvitelink.py deleted file mode 100644 index 6852b55..0000000 --- a/telegramer/include/telegram/chatinvitelink.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents an invite link for a chat.""" -import datetime -from typing import TYPE_CHECKING, Any, Optional - -from telegram import TelegramObject, User -from telegram.utils.helpers import from_timestamp, to_timestamp -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class ChatInviteLink(TelegramObject): - """This object represents an invite link for a chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`invite_link`, :attr:`creator`, :attr:`is_primary` and - :attr:`is_revoked` are equal. - - .. versionadded:: 13.4 - - Args: - invite_link (:obj:`str`): The invite link. - creator (:class:`telegram.User`): Creator of the link. - is_primary (:obj:`bool`): :obj:`True`, if the link is primary. - is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked. - expire_date (:class:`datetime.datetime`, optional): Date when the link will expire or - has been expired. - member_limit (:obj:`int`, optional): Maximum number of users that can be members of the - chat simultaneously after joining the chat via this invite link; 1-99999. - name (:obj:`str`, optional): Invite link name. - - .. versionadded:: 13.8 - creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat via - the link need to be approved by chat administrators. - - .. versionadded:: 13.8 - pending_join_request_count (:obj:`int`, optional): Number of pending join requests - created using this link. - - .. versionadded:: 13.8 - - Attributes: - invite_link (:obj:`str`): The invite link. If the link was created by another chat - administrator, then the second part of the link will be replaced with ``'…'``. - creator (:class:`telegram.User`): Creator of the link. - is_primary (:obj:`bool`): :obj:`True`, if the link is primary. - is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked. - expire_date (:class:`datetime.datetime`): Optional. Date when the link will expire or - has been expired. - member_limit (:obj:`int`): Optional. Maximum number of users that can be members - of the chat simultaneously after joining the chat via this invite link; 1-99999. - name (:obj:`str`): Optional. Invite link name. - - .. versionadded:: 13.8 - creates_join_request (:obj:`bool`): Optional. :obj:`True`, if users joining the chat via - the link need to be approved by chat administrators. - - .. versionadded:: 13.8 - pending_join_request_count (:obj:`int`): Optional. Number of pending join requests - created using this link. - - .. versionadded:: 13.8 - - """ - - __slots__ = ( - 'invite_link', - 'creator', - 'is_primary', - 'is_revoked', - 'expire_date', - 'member_limit', - 'name', - 'creates_join_request', - 'pending_join_request_count', - '_id_attrs', - ) - - def __init__( - self, - invite_link: str, - creator: User, - is_primary: bool, - is_revoked: bool, - expire_date: datetime.datetime = None, - member_limit: int = None, - name: str = None, - creates_join_request: bool = None, - pending_join_request_count: int = None, - **_kwargs: Any, - ): - # Required - self.invite_link = invite_link - self.creator = creator - self.is_primary = is_primary - self.is_revoked = is_revoked - - # Optionals - self.expire_date = expire_date - self.member_limit = int(member_limit) if member_limit is not None else None - self.name = name - self.creates_join_request = creates_join_request - self.pending_join_request_count = ( - int(pending_join_request_count) if pending_join_request_count is not None else None - ) - self._id_attrs = (self.invite_link, self.creator, self.is_primary, self.is_revoked) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatInviteLink']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['creator'] = User.de_json(data.get('creator'), bot) - data['expire_date'] = from_timestamp(data.get('expire_date', None)) - - return cls(**data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['expire_date'] = to_timestamp(self.expire_date) - - return data diff --git a/telegramer/include/telegram/chatjoinrequest.py b/telegramer/include/telegram/chatjoinrequest.py deleted file mode 100644 index ec89c66..0000000 --- a/telegramer/include/telegram/chatjoinrequest.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ChatJoinRequest.""" -import datetime -from typing import TYPE_CHECKING, Any, Optional - -from telegram import TelegramObject, User, Chat, ChatInviteLink -from telegram.utils.helpers import from_timestamp, to_timestamp, DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot - - -class ChatJoinRequest(TelegramObject): - """This object represents a join request sent to a chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`chat`, :attr:`from_user` and :attr:`date` are equal. - - Note: - Since Bot API 5.5, bots are allowed to contact users who sent a join request to a chat - where the bot is an administrator with the - :attr:`~telegram.ChatMemberAdministrator.can_invite_users` administrator right – even if - the user never interacted with the bot before. - - .. versionadded:: 13.8 - - Args: - chat (:class:`telegram.Chat`): Chat to which the request was sent. - from_user (:class:`telegram.User`): User that sent the join request. - date (:class:`datetime.datetime`): Date the request was sent. - bio (:obj:`str`, optional): Bio of the user. - invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link that was used - by the user to send the join request. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - - Attributes: - chat (:class:`telegram.Chat`): Chat to which the request was sent. - from_user (:class:`telegram.User`): User that sent the join request. - date (:class:`datetime.datetime`): Date the request was sent. - bio (:obj:`str`): Optional. Bio of the user. - invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link that was used - by the user to send the join request. - - """ - - __slots__ = ( - 'chat', - 'from_user', - 'date', - 'bio', - 'invite_link', - 'bot', - '_id_attrs', - ) - - def __init__( - self, - chat: Chat, - from_user: User, - date: datetime.datetime, - bio: str = None, - invite_link: ChatInviteLink = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.chat = chat - self.from_user = from_user - self.date = date - - # Optionals - self.bio = bio - self.invite_link = invite_link - - self.bot = bot - self._id_attrs = (self.chat, self.from_user, self.date) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatJoinRequest']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['chat'] = Chat.de_json(data.get('chat'), bot) - data['from_user'] = User.de_json(data.get('from'), bot) - data['date'] = from_timestamp(data.get('date', None)) - data['invite_link'] = ChatInviteLink.de_json(data.get('invite_link'), bot) - - return cls(bot=bot, **data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['date'] = to_timestamp(self.date) - - return data - - def approve( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.approve_chat_join_request(chat_id=update.effective_chat.id, - user_id=update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.approve_chat_join_request`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.approve_chat_join_request( - chat_id=self.chat.id, user_id=self.from_user.id, timeout=timeout, api_kwargs=api_kwargs - ) - - def decline( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.decline_chat_join_request(chat_id=update.effective_chat.id, - user_id=update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.decline_chat_join_request`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.decline_chat_join_request( - chat_id=self.chat.id, user_id=self.from_user.id, timeout=timeout, api_kwargs=api_kwargs - ) diff --git a/telegramer/include/telegram/chatlocation.py b/telegramer/include/telegram/chatlocation.py deleted file mode 100644 index f88c708..0000000 --- a/telegramer/include/telegram/chatlocation.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a location to which a chat is connected.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import TelegramObject -from telegram.utils.types import JSONDict - -from .files.location import Location - -if TYPE_CHECKING: - from telegram import Bot - - -class ChatLocation(TelegramObject): - """This object represents a location to which a chat is connected. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`location` is equal. - - Args: - location (:class:`telegram.Location`): The location to which the supergroup is connected. - Can't be a live location. - address (:obj:`str`): Location address; 1-64 characters, as defined by the chat owner - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - location (:class:`telegram.Location`): The location to which the supergroup is connected. - address (:obj:`str`): Location address, as defined by the chat owner - - """ - - __slots__ = ('location', '_id_attrs', 'address') - - def __init__( - self, - location: Location, - address: str, - **_kwargs: Any, - ): - self.location = location - self.address = address - - self._id_attrs = (self.location,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatLocation']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['location'] = Location.de_json(data.get('location'), bot) - - return cls(bot=bot, **data) diff --git a/telegramer/include/telegram/chatmember.py b/telegramer/include/telegram/chatmember.py deleted file mode 100644 index 06968d4..0000000 --- a/telegramer/include/telegram/chatmember.py +++ /dev/null @@ -1,715 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ChatMember.""" -import datetime -from typing import TYPE_CHECKING, Any, Optional, ClassVar, Dict, Type - -from telegram import TelegramObject, User, constants -from telegram.utils.helpers import from_timestamp, to_timestamp -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class ChatMember(TelegramObject): - """Base class for Telegram ChatMember Objects. - Currently, the following 6 types of chat members are supported: - - * :class:`telegram.ChatMemberOwner` - * :class:`telegram.ChatMemberAdministrator` - * :class:`telegram.ChatMemberMember` - * :class:`telegram.ChatMemberRestricted` - * :class:`telegram.ChatMemberLeft` - * :class:`telegram.ChatMemberBanned` - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`user` and :attr:`status` are equal. - - Note: - As of Bot API 5.3, :class:`ChatMember` is nothing but the base class for the subclasses - listed above and is no longer returned directly by :meth:`~telegram.Bot.get_chat`. - Therefore, most of the arguments and attributes were deprecated and you should no longer - use :class:`ChatMember` directly. - - Args: - user (:class:`telegram.User`): Information about the user. - status (:obj:`str`): The member's status in the chat. Can be - :attr:`~telegram.ChatMember.ADMINISTRATOR`, :attr:`~telegram.ChatMember.CREATOR`, - :attr:`~telegram.ChatMember.KICKED`, :attr:`~telegram.ChatMember.LEFT`, - :attr:`~telegram.ChatMember.MEMBER` or :attr:`~telegram.ChatMember.RESTRICTED`. - custom_title (:obj:`str`, optional): Owner and administrators only. - Custom title for this user. - - .. deprecated:: 13.7 - - is_anonymous (:obj:`bool`, optional): Owner and administrators only. :obj:`True`, if the - user's presence in the chat is hidden. - - .. deprecated:: 13.7 - - until_date (:class:`datetime.datetime`, optional): Restricted and kicked only. Date when - restrictions will be lifted for this user. - - .. deprecated:: 13.7 - - can_be_edited (:obj:`bool`, optional): Administrators only. :obj:`True`, if the bot is - allowed to edit administrator privileges of that user. - - .. deprecated:: 13.7 - - can_manage_chat (:obj:`bool`, optional): Administrators only. :obj:`True`, if the - administrator can access the chat event log, chat statistics, message statistics in - channels, see channel members, see anonymous administrators in supergroups and ignore - slow mode. Implied by any other administrator privilege. - - .. versionadded:: 13.4 - .. deprecated:: 13.7 - - can_manage_voice_chats (:obj:`bool`, optional): Administrators only. :obj:`True`, if the - administrator can manage voice chats. - - .. versionadded:: 13.4 - .. deprecated:: 13.7 - - can_change_info (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`, - if the user can change the chat title, photo and other settings. - - .. deprecated:: 13.7 - - can_post_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the - administrator can post in the channel, channels only. - - .. deprecated:: 13.7 - - can_edit_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the - administrator can edit messages of other users and can pin messages; channels only. - - .. deprecated:: 13.7 - - can_delete_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the - administrator can delete messages of other users. - - .. deprecated:: 13.7 - - can_invite_users (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`, - if the user can invite new users to the chat. - - .. deprecated:: 13.7 - - can_restrict_members (:obj:`bool`, optional): Administrators only. :obj:`True`, if the - administrator can restrict, ban or unban chat members. - - .. deprecated:: 13.7 - - can_pin_messages (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`, - if the user can pin messages, groups and supergroups only. - - .. deprecated:: 13.7 - - can_promote_members (:obj:`bool`, optional): Administrators only. :obj:`True`, if the - administrator can add new administrators with a subset of his own privileges or demote - administrators that he has promoted, directly or indirectly (promoted by administrators - that were appointed by the user). - - .. deprecated:: 13.7 - - is_member (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user is a member of - the chat at the moment of the request. - - .. deprecated:: 13.7 - - can_send_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user can - send text messages, contacts, locations and venues. - - .. deprecated:: 13.7 - - can_send_media_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user - can send audios, documents, photos, videos, video notes and voice notes. - - .. deprecated:: 13.7 - - can_send_polls (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user is - allowed to send polls. - - .. deprecated:: 13.7 - - can_send_other_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user - can send animations, games, stickers and use inline bots. - - .. deprecated:: 13.7 - - can_add_web_page_previews (:obj:`bool`, optional): Restricted only. :obj:`True`, if user - may add web page previews to his messages. - - .. deprecated:: 13.7 - - Attributes: - user (:class:`telegram.User`): Information about the user. - status (:obj:`str`): The member's status in the chat. - custom_title (:obj:`str`): Optional. Custom title for owner and administrators. - - .. deprecated:: 13.7 - - is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's presence in the chat is - hidden. - - .. deprecated:: 13.7 - - until_date (:class:`datetime.datetime`): Optional. Date when restrictions will be lifted - for this user. - - .. deprecated:: 13.7 - - can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator - privileges of that user. - - .. deprecated:: 13.7 - - can_manage_chat (:obj:`bool`): Optional. If the administrator can access the chat event - log, chat statistics, message statistics in channels, see channel members, see - anonymous administrators in supergroups and ignore slow mode. - - .. versionadded:: 13.4 - .. deprecated:: 13.7 - - can_manage_voice_chats (:obj:`bool`): Optional. if the administrator can manage - voice chats. - - .. versionadded:: 13.4 - .. deprecated:: 13.7 - - can_change_info (:obj:`bool`): Optional. If the user can change the chat title, photo and - other settings. - - .. deprecated:: 13.7 - - can_post_messages (:obj:`bool`): Optional. If the administrator can post in the channel. - - .. deprecated:: 13.7 - - can_edit_messages (:obj:`bool`): Optional. If the administrator can edit messages of other - users. - - .. deprecated:: 13.7 - - can_delete_messages (:obj:`bool`): Optional. If the administrator can delete messages of - other users. - - .. deprecated:: 13.7 - - can_invite_users (:obj:`bool`): Optional. If the user can invite new users to the chat. - - .. deprecated:: 13.7 - - can_restrict_members (:obj:`bool`): Optional. If the administrator can restrict, ban or - unban chat members. - - .. deprecated:: 13.7 - - can_pin_messages (:obj:`bool`): Optional. If the user can pin messages. - - .. deprecated:: 13.7 - - can_promote_members (:obj:`bool`): Optional. If the administrator can add new - administrators. - - .. deprecated:: 13.7 - - is_member (:obj:`bool`): Optional. Restricted only. :obj:`True`, if the user is a member of - the chat at the moment of the request. - - .. deprecated:: 13.7 - - can_send_messages (:obj:`bool`): Optional. If the user can send text messages, contacts, - locations and venues. - - .. deprecated:: 13.7 - - can_send_media_messages (:obj:`bool`): Optional. If the user can send media messages, - implies can_send_messages. - - .. deprecated:: 13.7 - - can_send_polls (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to - send polls. - - .. deprecated:: 13.7 - - can_send_other_messages (:obj:`bool`): Optional. If the user can send animations, games, - stickers and use inline bots, implies can_send_media_messages. - - .. deprecated:: 13.7 - - can_add_web_page_previews (:obj:`bool`): Optional. If user may add web page previews to his - messages, implies can_send_media_messages - - .. deprecated:: 13.7 - - """ - - __slots__ = ( - 'is_member', - 'can_restrict_members', - 'can_delete_messages', - 'custom_title', - 'can_be_edited', - 'can_post_messages', - 'can_send_messages', - 'can_edit_messages', - 'can_send_media_messages', - 'is_anonymous', - 'can_add_web_page_previews', - 'can_send_other_messages', - 'can_invite_users', - 'can_send_polls', - 'user', - 'can_promote_members', - 'status', - 'can_change_info', - 'can_pin_messages', - 'can_manage_chat', - 'can_manage_voice_chats', - 'until_date', - '_id_attrs', - ) - - ADMINISTRATOR: ClassVar[str] = constants.CHATMEMBER_ADMINISTRATOR - """:const:`telegram.constants.CHATMEMBER_ADMINISTRATOR`""" - CREATOR: ClassVar[str] = constants.CHATMEMBER_CREATOR - """:const:`telegram.constants.CHATMEMBER_CREATOR`""" - KICKED: ClassVar[str] = constants.CHATMEMBER_KICKED - """:const:`telegram.constants.CHATMEMBER_KICKED`""" - LEFT: ClassVar[str] = constants.CHATMEMBER_LEFT - """:const:`telegram.constants.CHATMEMBER_LEFT`""" - MEMBER: ClassVar[str] = constants.CHATMEMBER_MEMBER - """:const:`telegram.constants.CHATMEMBER_MEMBER`""" - RESTRICTED: ClassVar[str] = constants.CHATMEMBER_RESTRICTED - """:const:`telegram.constants.CHATMEMBER_RESTRICTED`""" - - def __init__( - self, - user: User, - status: str, - until_date: datetime.datetime = None, - can_be_edited: bool = None, - can_change_info: bool = None, - can_post_messages: bool = None, - can_edit_messages: bool = None, - can_delete_messages: bool = None, - can_invite_users: bool = None, - can_restrict_members: bool = None, - can_pin_messages: bool = None, - can_promote_members: bool = None, - can_send_messages: bool = None, - can_send_media_messages: bool = None, - can_send_polls: bool = None, - can_send_other_messages: bool = None, - can_add_web_page_previews: bool = None, - is_member: bool = None, - custom_title: str = None, - is_anonymous: bool = None, - can_manage_chat: bool = None, - can_manage_voice_chats: bool = None, - **_kwargs: Any, - ): - # Required - self.user = user - self.status = status - - # Optionals - self.custom_title = custom_title - self.is_anonymous = is_anonymous - self.until_date = until_date - self.can_be_edited = can_be_edited - self.can_change_info = can_change_info - self.can_post_messages = can_post_messages - self.can_edit_messages = can_edit_messages - self.can_delete_messages = can_delete_messages - self.can_invite_users = can_invite_users - self.can_restrict_members = can_restrict_members - self.can_pin_messages = can_pin_messages - self.can_promote_members = can_promote_members - self.can_send_messages = can_send_messages - self.can_send_media_messages = can_send_media_messages - self.can_send_polls = can_send_polls - self.can_send_other_messages = can_send_other_messages - self.can_add_web_page_previews = can_add_web_page_previews - self.is_member = is_member - self.can_manage_chat = can_manage_chat - self.can_manage_voice_chats = can_manage_voice_chats - - self._id_attrs = (self.user, self.status) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMember']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['user'] = User.de_json(data.get('user'), bot) - data['until_date'] = from_timestamp(data.get('until_date', None)) - - _class_mapping: Dict[str, Type['ChatMember']] = { - cls.CREATOR: ChatMemberOwner, - cls.ADMINISTRATOR: ChatMemberAdministrator, - cls.MEMBER: ChatMemberMember, - cls.RESTRICTED: ChatMemberRestricted, - cls.LEFT: ChatMemberLeft, - cls.KICKED: ChatMemberBanned, - } - - if cls is ChatMember: - return _class_mapping.get(data['status'], cls)(**data, bot=bot) - return cls(**data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['until_date'] = to_timestamp(self.until_date) - - return data - - -class ChatMemberOwner(ChatMember): - """ - Represents a chat member that owns the chat - and has all administrator privileges. - - .. versionadded:: 13.7 - - Args: - user (:class:`telegram.User`): Information about the user. - custom_title (:obj:`str`, optional): Custom title for this user. - is_anonymous (:obj:`bool`, optional): :obj:`True`, if the - user's presence in the chat is hidden. - - Attributes: - status (:obj:`str`): The member's status in the chat, - always :attr:`telegram.ChatMember.CREATOR`. - user (:class:`telegram.User`): Information about the user. - custom_title (:obj:`str`): Optional. Custom title for - this user. - is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's - presence in the chat is hidden. - """ - - __slots__ = () - - def __init__( - self, - user: User, - custom_title: str = None, - is_anonymous: bool = None, - **_kwargs: Any, - ): - super().__init__( - status=ChatMember.CREATOR, - user=user, - custom_title=custom_title, - is_anonymous=is_anonymous, - ) - - -class ChatMemberAdministrator(ChatMember): - """ - Represents a chat member that has some additional privileges. - - .. versionadded:: 13.7 - - Args: - user (:class:`telegram.User`): Information about the user. - can_be_edited (:obj:`bool`, optional): :obj:`True`, if the bot - is allowed to edit administrator privileges of that user. - custom_title (:obj:`str`, optional): Custom title for this user. - is_anonymous (:obj:`bool`, optional): :obj:`True`, if the user's - presence in the chat is hidden. - can_manage_chat (:obj:`bool`, optional): :obj:`True`, if the administrator - can access the chat event log, chat statistics, message statistics in - channels, see channel members, see anonymous administrators in supergroups - and ignore slow mode. Implied by any other administrator privilege. - can_post_messages (:obj:`bool`, optional): :obj:`True`, if the - administrator can post in the channel, channels only. - can_edit_messages (:obj:`bool`, optional): :obj:`True`, if the - administrator can edit messages of other users and can pin - messages; channels only. - can_delete_messages (:obj:`bool`, optional): :obj:`True`, if the - administrator can delete messages of other users. - can_manage_voice_chats (:obj:`bool`, optional): :obj:`True`, if the - administrator can manage voice chats. - can_restrict_members (:obj:`bool`, optional): :obj:`True`, if the - administrator can restrict, ban or unban chat members. - can_promote_members (:obj:`bool`, optional): :obj:`True`, if the administrator - can add new administrators with a subset of his own privileges or demote - administrators that he has promoted, directly or indirectly (promoted by - administrators that were appointed by the user). - can_change_info (:obj:`bool`, optional): :obj:`True`, if the user can change - the chat title, photo and other settings. - can_invite_users (:obj:`bool`, optional): :obj:`True`, if the user can invite - new users to the chat. - can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed - to pin messages; groups and supergroups only. - - Attributes: - status (:obj:`str`): The member's status in the chat, - always :attr:`telegram.ChatMember.ADMINISTRATOR`. - user (:class:`telegram.User`): Information about the user. - can_be_edited (:obj:`bool`): Optional. :obj:`True`, if the bot - is allowed to edit administrator privileges of that user. - custom_title (:obj:`str`): Optional. Custom title for this user. - is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's - presence in the chat is hidden. - can_manage_chat (:obj:`bool`): Optional. :obj:`True`, if the administrator - can access the chat event log, chat statistics, message statistics in - channels, see channel members, see anonymous administrators in supergroups - and ignore slow mode. Implied by any other administrator privilege. - can_post_messages (:obj:`bool`): Optional. :obj:`True`, if the - administrator can post in the channel, channels only. - can_edit_messages (:obj:`bool`): Optional. :obj:`True`, if the - administrator can edit messages of other users and can pin - messages; channels only. - can_delete_messages (:obj:`bool`): Optional. :obj:`True`, if the - administrator can delete messages of other users. - can_manage_voice_chats (:obj:`bool`): Optional. :obj:`True`, if the - administrator can manage voice chats. - can_restrict_members (:obj:`bool`): Optional. :obj:`True`, if the - administrator can restrict, ban or unban chat members. - can_promote_members (:obj:`bool`): Optional. :obj:`True`, if the administrator - can add new administrators with a subset of his own privileges or demote - administrators that he has promoted, directly or indirectly (promoted by - administrators that were appointed by the user). - can_change_info (:obj:`bool`): Optional. :obj:`True`, if the user can change - the chat title, photo and other settings. - can_invite_users (:obj:`bool`): Optional. :obj:`True`, if the user can invite - new users to the chat. - can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed - to pin messages; groups and supergroups only. - """ - - __slots__ = () - - def __init__( - self, - user: User, - can_be_edited: bool = None, - custom_title: str = None, - is_anonymous: bool = None, - can_manage_chat: bool = None, - can_post_messages: bool = None, - can_edit_messages: bool = None, - can_delete_messages: bool = None, - can_manage_voice_chats: bool = None, - can_restrict_members: bool = None, - can_promote_members: bool = None, - can_change_info: bool = None, - can_invite_users: bool = None, - can_pin_messages: bool = None, - **_kwargs: Any, - ): - super().__init__( - status=ChatMember.ADMINISTRATOR, - user=user, - can_be_edited=can_be_edited, - custom_title=custom_title, - is_anonymous=is_anonymous, - can_manage_chat=can_manage_chat, - can_post_messages=can_post_messages, - can_edit_messages=can_edit_messages, - can_delete_messages=can_delete_messages, - can_manage_voice_chats=can_manage_voice_chats, - can_restrict_members=can_restrict_members, - can_promote_members=can_promote_members, - can_change_info=can_change_info, - can_invite_users=can_invite_users, - can_pin_messages=can_pin_messages, - ) - - -class ChatMemberMember(ChatMember): - """ - Represents a chat member that has no additional - privileges or restrictions. - - .. versionadded:: 13.7 - - Args: - user (:class:`telegram.User`): Information about the user. - - Attributes: - status (:obj:`str`): The member's status in the chat, - always :attr:`telegram.ChatMember.MEMBER`. - user (:class:`telegram.User`): Information about the user. - - """ - - __slots__ = () - - def __init__(self, user: User, **_kwargs: Any): - super().__init__(status=ChatMember.MEMBER, user=user) - - -class ChatMemberRestricted(ChatMember): - """ - Represents a chat member that is under certain restrictions - in the chat. Supergroups only. - - .. versionadded:: 13.7 - - Args: - user (:class:`telegram.User`): Information about the user. - is_member (:obj:`bool`, optional): :obj:`True`, if the user is a - member of the chat at the moment of the request. - can_change_info (:obj:`bool`, optional): :obj:`True`, if the user can change - the chat title, photo and other settings. - can_invite_users (:obj:`bool`, optional): :obj:`True`, if the user can invite - new users to the chat. - can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed - to pin messages; groups and supergroups only. - can_send_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed - to send text messages, contacts, locations and venues. - can_send_media_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed - to send audios, documents, photos, videos, video notes and voice notes. - can_send_polls (:obj:`bool`, optional): :obj:`True`, if the user is allowed - to send polls. - can_send_other_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed - to send animations, games, stickers and use inline bots. - can_add_web_page_previews (:obj:`bool`, optional): :obj:`True`, if the user is - allowed to add web page previews to their messages. - until_date (:class:`datetime.datetime`, optional): Date when restrictions - will be lifted for this user. - - Attributes: - status (:obj:`str`): The member's status in the chat, - always :attr:`telegram.ChatMember.RESTRICTED`. - user (:class:`telegram.User`): Information about the user. - is_member (:obj:`bool`): Optional. :obj:`True`, if the user is a - member of the chat at the moment of the request. - can_change_info (:obj:`bool`): Optional. :obj:`True`, if the user can change - the chat title, photo and other settings. - can_invite_users (:obj:`bool`): Optional. :obj:`True`, if the user can invite - new users to the chat. - can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed - to pin messages; groups and supergroups only. - can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed - to send text messages, contacts, locations and venues. - can_send_media_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed - to send audios, documents, photos, videos, video notes and voice notes. - can_send_polls (:obj:`bool`): Optional. :obj:`True`, if the user is allowed - to send polls. - can_send_other_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed - to send animations, games, stickers and use inline bots. - can_add_web_page_previews (:obj:`bool`): Optional. :obj:`True`, if the user is - allowed to add web page previews to their messages. - until_date (:class:`datetime.datetime`): Optional. Date when restrictions - will be lifted for this user. - - """ - - __slots__ = () - - def __init__( - self, - user: User, - is_member: bool = None, - can_change_info: bool = None, - can_invite_users: bool = None, - can_pin_messages: bool = None, - can_send_messages: bool = None, - can_send_media_messages: bool = None, - can_send_polls: bool = None, - can_send_other_messages: bool = None, - can_add_web_page_previews: bool = None, - until_date: datetime.datetime = None, - **_kwargs: Any, - ): - super().__init__( - status=ChatMember.RESTRICTED, - user=user, - is_member=is_member, - can_change_info=can_change_info, - can_invite_users=can_invite_users, - can_pin_messages=can_pin_messages, - can_send_messages=can_send_messages, - can_send_media_messages=can_send_media_messages, - can_send_polls=can_send_polls, - can_send_other_messages=can_send_other_messages, - can_add_web_page_previews=can_add_web_page_previews, - until_date=until_date, - ) - - -class ChatMemberLeft(ChatMember): - """ - Represents a chat member that isn't currently a member of the chat, - but may join it themselves. - - .. versionadded:: 13.7 - - Args: - user (:class:`telegram.User`): Information about the user. - - Attributes: - status (:obj:`str`): The member's status in the chat, - always :attr:`telegram.ChatMember.LEFT`. - user (:class:`telegram.User`): Information about the user. - """ - - __slots__ = () - - def __init__(self, user: User, **_kwargs: Any): - super().__init__(status=ChatMember.LEFT, user=user) - - -class ChatMemberBanned(ChatMember): - """ - Represents a chat member that was banned in the chat and - can't return to the chat or view chat messages. - - .. versionadded:: 13.7 - - Args: - user (:class:`telegram.User`): Information about the user. - until_date (:class:`datetime.datetime`, optional): Date when restrictions - will be lifted for this user. - - Attributes: - status (:obj:`str`): The member's status in the chat, - always :attr:`telegram.ChatMember.KICKED`. - user (:class:`telegram.User`): Information about the user. - until_date (:class:`datetime.datetime`): Optional. Date when restrictions - will be lifted for this user. - - """ - - __slots__ = () - - def __init__( - self, - user: User, - until_date: datetime.datetime = None, - **_kwargs: Any, - ): - super().__init__( - status=ChatMember.KICKED, - user=user, - until_date=until_date, - ) diff --git a/telegramer/include/telegram/chatmemberupdated.py b/telegramer/include/telegram/chatmemberupdated.py deleted file mode 100644 index cd6c76e..0000000 --- a/telegramer/include/telegram/chatmemberupdated.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ChatMemberUpdated.""" -import datetime -from typing import TYPE_CHECKING, Any, Optional, Dict, Tuple, Union - -from telegram import TelegramObject, User, Chat, ChatMember, ChatInviteLink -from telegram.utils.helpers import from_timestamp, to_timestamp -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class ChatMemberUpdated(TelegramObject): - """This object represents changes in the status of a chat member. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`chat`, :attr:`from_user`, :attr:`date`, - :attr:`old_chat_member` and :attr:`new_chat_member` are equal. - - .. versionadded:: 13.4 - - Note: - In Python ``from`` is a reserved word, use ``from_user`` instead. - - Args: - chat (:class:`telegram.Chat`): Chat the user belongs to. - from_user (:class:`telegram.User`): Performer of the action, which resulted in the change. - date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to - :class:`datetime.datetime`. - old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member. - new_chat_member (:class:`telegram.ChatMember`): New information about the chat member. - invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used - by the user to join the chat. For joining by invite link events only. - - Attributes: - chat (:class:`telegram.Chat`): Chat the user belongs to. - from_user (:class:`telegram.User`): Performer of the action, which resulted in the change. - date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to - :class:`datetime.datetime`. - old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member. - new_chat_member (:class:`telegram.ChatMember`): New information about the chat member. - invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used - by the user to join the chat. - - """ - - __slots__ = ( - 'chat', - 'from_user', - 'date', - 'old_chat_member', - 'new_chat_member', - 'invite_link', - '_id_attrs', - ) - - def __init__( - self, - chat: Chat, - from_user: User, - date: datetime.datetime, - old_chat_member: ChatMember, - new_chat_member: ChatMember, - invite_link: ChatInviteLink = None, - **_kwargs: Any, - ): - # Required - self.chat = chat - self.from_user = from_user - self.date = date - self.old_chat_member = old_chat_member - self.new_chat_member = new_chat_member - - # Optionals - self.invite_link = invite_link - - self._id_attrs = ( - self.chat, - self.from_user, - self.date, - self.old_chat_member, - self.new_chat_member, - ) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMemberUpdated']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['chat'] = Chat.de_json(data.get('chat'), bot) - data['from_user'] = User.de_json(data.get('from'), bot) - data['date'] = from_timestamp(data.get('date')) - data['old_chat_member'] = ChatMember.de_json(data.get('old_chat_member'), bot) - data['new_chat_member'] = ChatMember.de_json(data.get('new_chat_member'), bot) - data['invite_link'] = ChatInviteLink.de_json(data.get('invite_link'), bot) - - return cls(**data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - # Required - data['date'] = to_timestamp(self.date) - - return data - - def difference( - self, - ) -> Dict[ - str, - Tuple[ - Union[str, bool, datetime.datetime, User], Union[str, bool, datetime.datetime, User] - ], - ]: - """Computes the difference between :attr:`old_chat_member` and :attr:`new_chat_member`. - - Example: - .. code:: python - - >>> chat_member_updated.difference() - {'custom_title': ('old title', 'new title')} - - Note: - To determine, if the :attr:`telegram.ChatMember.user` attribute has changed, *every* - attribute of the user will be checked. - - .. versionadded:: 13.5 - - Returns: - Dict[:obj:`str`, Tuple[:obj:`obj`, :obj:`obj`]]: A dictionary mapping attribute names - to tuples of the form ``(old_value, new_value)`` - """ - # we first get the names of the attributes that have changed - # user.to_dict() is unhashable, so that needs some special casing further down - old_dict = self.old_chat_member.to_dict() - old_user_dict = old_dict.pop('user') - new_dict = self.new_chat_member.to_dict() - new_user_dict = new_dict.pop('user') - - # Generator for speed: we only need to iterate over it once - # we can't directly use the values from old_dict ^ new_dict b/c that set is unordered - attributes = (entry[0] for entry in set(old_dict.items()) ^ set(new_dict.items())) - - result = { - attribute: (self.old_chat_member[attribute], self.new_chat_member[attribute]) - for attribute in attributes - } - if old_user_dict != new_user_dict: - result['user'] = (self.old_chat_member.user, self.new_chat_member.user) - - return result # type: ignore[return-value] diff --git a/telegramer/include/telegram/chatpermissions.py b/telegramer/include/telegram/chatpermissions.py deleted file mode 100644 index 44b989a..0000000 --- a/telegramer/include/telegram/chatpermissions.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ChatPermission.""" - -from typing import Any - -from telegram import TelegramObject - - -class ChatPermissions(TelegramObject): - """Describes actions that a non-administrator user is allowed to take in a chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`can_send_messages`, :attr:`can_send_media_messages`, - :attr:`can_send_polls`, :attr:`can_send_other_messages`, :attr:`can_add_web_page_previews`, - :attr:`can_change_info`, :attr:`can_invite_users` and :attr:`can_pin_messages` are equal. - - Note: - Though not stated explicitly in the official docs, Telegram changes not only the - permissions that are set, but also sets all the others to :obj:`False`. However, since not - documented, this behaviour may change unbeknown to PTB. - - Args: - can_send_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to send text - messages, contacts, locations and venues. - can_send_media_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to - send audios, documents, photos, videos, video notes and voice notes, implies - :attr:`can_send_messages`. - can_send_polls (:obj:`bool`, optional): :obj:`True`, if the user is allowed to send polls, - implies :attr:`can_send_messages`. - can_send_other_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to - send animations, games, stickers and use inline bots, implies - :attr:`can_send_media_messages`. - can_add_web_page_previews (:obj:`bool`, optional): :obj:`True`, if the user is allowed to - add web page previews to their messages, implies :attr:`can_send_media_messages`. - can_change_info (:obj:`bool`, optional): :obj:`True`, if the user is allowed to change the - chat title, photo and other settings. Ignored in public supergroups. - can_invite_users (:obj:`bool`, optional): :obj:`True`, if the user is allowed to invite new - users to the chat. - can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to pin - messages. Ignored in public supergroups. - - Attributes: - can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text - messages, contacts, locations and venues. - can_send_media_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to - send audios, documents, photos, videos, video notes and voice notes, implies - :attr:`can_send_messages`. - can_send_polls (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send polls, - implies :attr:`can_send_messages`. - can_send_other_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to - send animations, games, stickers and use inline bots, implies - :attr:`can_send_media_messages`. - can_add_web_page_previews (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to - add web page previews to their messages, implies :attr:`can_send_media_messages`. - can_change_info (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to change the - chat title, photo and other settings. Ignored in public supergroups. - can_invite_users (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to invite - new users to the chat. - can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to pin - messages. Ignored in public supergroups. - - """ - - __slots__ = ( - 'can_send_other_messages', - 'can_invite_users', - 'can_send_polls', - '_id_attrs', - 'can_send_messages', - 'can_send_media_messages', - 'can_change_info', - 'can_pin_messages', - 'can_add_web_page_previews', - ) - - def __init__( - self, - can_send_messages: bool = None, - can_send_media_messages: bool = None, - can_send_polls: bool = None, - can_send_other_messages: bool = None, - can_add_web_page_previews: bool = None, - can_change_info: bool = None, - can_invite_users: bool = None, - can_pin_messages: bool = None, - **_kwargs: Any, - ): - # Required - self.can_send_messages = can_send_messages - self.can_send_media_messages = can_send_media_messages - self.can_send_polls = can_send_polls - self.can_send_other_messages = can_send_other_messages - self.can_add_web_page_previews = can_add_web_page_previews - self.can_change_info = can_change_info - self.can_invite_users = can_invite_users - self.can_pin_messages = can_pin_messages - - self._id_attrs = ( - self.can_send_messages, - self.can_send_media_messages, - self.can_send_polls, - self.can_send_other_messages, - self.can_add_web_page_previews, - self.can_change_info, - self.can_invite_users, - self.can_pin_messages, - ) diff --git a/telegramer/include/telegram/choseninlineresult.py b/telegramer/include/telegram/choseninlineresult.py deleted file mode 100644 index b931553..0000000 --- a/telegramer/include/telegram/choseninlineresult.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0902,R0913 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ChosenInlineResult.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import Location, TelegramObject, User -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class ChosenInlineResult(TelegramObject): - """ - Represents a result of an inline query that was chosen by the user and sent to their chat - partner. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`result_id` is equal. - - Note: - * In Python ``from`` is a reserved word, use ``from_user`` instead. - * It is necessary to enable inline feedback via `@Botfather `_ in - order to receive these objects in updates. - - Args: - result_id (:obj:`str`): The unique identifier for the result that was chosen. - from_user (:class:`telegram.User`): The user that chose the result. - location (:class:`telegram.Location`, optional): Sender location, only for bots that - require user location. - inline_message_id (:obj:`str`, optional): Identifier of the sent inline message. Available - only if there is an inline keyboard attached to the message. Will be also received in - callback queries and can be used to edit the message. - query (:obj:`str`): The query that was used to obtain the result. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - result_id (:obj:`str`): The unique identifier for the result that was chosen. - from_user (:class:`telegram.User`): The user that chose the result. - location (:class:`telegram.Location`): Optional. Sender location. - inline_message_id (:obj:`str`): Optional. Identifier of the sent inline message. - query (:obj:`str`): The query that was used to obtain the result. - - """ - - __slots__ = ('location', 'result_id', 'from_user', 'inline_message_id', '_id_attrs', 'query') - - def __init__( - self, - result_id: str, - from_user: User, - query: str, - location: Location = None, - inline_message_id: str = None, - **_kwargs: Any, - ): - # Required - self.result_id = result_id - self.from_user = from_user - self.query = query - # Optionals - self.location = location - self.inline_message_id = inline_message_id - - self._id_attrs = (self.result_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChosenInlineResult']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - # Required - data['from_user'] = User.de_json(data.pop('from'), bot) - # Optionals - data['location'] = Location.de_json(data.get('location'), bot) - - return cls(**data) diff --git a/telegramer/include/telegram/constants.py b/telegramer/include/telegram/constants.py deleted file mode 100644 index b7600a7..0000000 --- a/telegramer/include/telegram/constants.py +++ /dev/null @@ -1,398 +0,0 @@ -# python-telegram-bot - a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# by the python-telegram-bot contributors -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""Constants in the Telegram network. - -The following constants were extracted from the -`Telegram Bots FAQ `_ and -`Telegram Bots API `_. - -Attributes: - BOT_API_VERSION (:obj:`str`): `5.7`. Telegram Bot API version supported by this - version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``. - - .. versionadded:: 13.4 - MAX_MESSAGE_LENGTH (:obj:`int`): 4096 - MAX_CAPTION_LENGTH (:obj:`int`): 1024 - SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443] - MAX_FILESIZE_DOWNLOAD (:obj:`int`): In bytes (20MB) - MAX_FILESIZE_UPLOAD (:obj:`int`): In bytes (50MB) - MAX_PHOTOSIZE_UPLOAD (:obj:`int`): In bytes (10MB) - MAX_MESSAGES_PER_SECOND_PER_CHAT (:obj:`int`): `1`. Telegram may allow short bursts that go - over this limit, but eventually you'll begin receiving 429 errors. - MAX_MESSAGES_PER_SECOND (:obj:`int`): 30 - MAX_MESSAGES_PER_MINUTE_PER_GROUP (:obj:`int`): 20 - MAX_INLINE_QUERY_RESULTS (:obj:`int`): 50 - MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH (:obj:`int`): 200 - - .. versionadded:: 13.2 - -The following constant have been found by experimentation: - -Attributes: - MAX_MESSAGE_ENTITIES (:obj:`int`): 100 (Beyond this cap telegram will simply ignore further - formatting styles) - ANONYMOUS_ADMIN_ID (:obj:`int`): ``1087968824`` (User id in groups for anonymous admin) - SERVICE_CHAT_ID (:obj:`int`): ``777000`` (Telegram service chat, that also acts as sender of - channel posts forwarded to discussion groups) - FAKE_CHANNEL_ID (:obj:`int`): ``136817688`` (User id in groups when message is sent on behalf - of a channel). - - .. versionadded:: 13.9 - -The following constants are related to specific classes and are also available -as attributes of those classes: - -:class:`telegram.Chat`: - -Attributes: - CHAT_PRIVATE (:obj:`str`): ``'private'`` - CHAT_GROUP (:obj:`str`): ``'group'`` - CHAT_SUPERGROUP (:obj:`str`): ``'supergroup'`` - CHAT_CHANNEL (:obj:`str`): ``'channel'`` - CHAT_SENDER (:obj:`str`): ``'sender'``. Only relevant for - :attr:`telegram.InlineQuery.chat_type`. - - .. versionadded:: 13.5 - -:class:`telegram.ChatAction`: - -Attributes: - CHATACTION_FIND_LOCATION (:obj:`str`): ``'find_location'`` - CHATACTION_RECORD_AUDIO (:obj:`str`): ``'record_audio'`` - - .. deprecated:: 13.5 - Deprecated by Telegram. Use :const:`CHATACTION_RECORD_VOICE` instead. - CHATACTION_RECORD_VOICE (:obj:`str`): ``'record_voice'`` - - .. versionadded:: 13.5 - CHATACTION_RECORD_VIDEO (:obj:`str`): ``'record_video'`` - CHATACTION_RECORD_VIDEO_NOTE (:obj:`str`): ``'record_video_note'`` - CHATACTION_TYPING (:obj:`str`): ``'typing'`` - CHATACTION_UPLOAD_AUDIO (:obj:`str`): ``'upload_audio'`` - - .. deprecated:: 13.5 - Deprecated by Telegram. Use :const:`CHATACTION_UPLOAD_VOICE` instead. - CHATACTION_UPLOAD_VOICE (:obj:`str`): ``'upload_voice'`` - - .. versionadded:: 13.5 - CHATACTION_UPLOAD_DOCUMENT (:obj:`str`): ``'upload_document'`` - CHATACTION_CHOOSE_STICKER (:obj:`str`): ``'choose_sticker'`` - - .. versionadded:: 13.8 - CHATACTION_UPLOAD_PHOTO (:obj:`str`): ``'upload_photo'`` - CHATACTION_UPLOAD_VIDEO (:obj:`str`): ``'upload_video'`` - CHATACTION_UPLOAD_VIDEO_NOTE (:obj:`str`): ``'upload_video_note'`` - -:class:`telegram.ChatMember`: - -Attributes: - CHATMEMBER_ADMINISTRATOR (:obj:`str`): ``'administrator'`` - CHATMEMBER_CREATOR (:obj:`str`): ``'creator'`` - CHATMEMBER_KICKED (:obj:`str`): ``'kicked'`` - CHATMEMBER_LEFT (:obj:`str`): ``'left'`` - CHATMEMBER_MEMBER (:obj:`str`): ``'member'`` - CHATMEMBER_RESTRICTED (:obj:`str`): ``'restricted'`` - -:class:`telegram.Dice`: - -Attributes: - DICE_DICE (:obj:`str`): ``'🎲'`` - DICE_DARTS (:obj:`str`): ``'🎯'`` - DICE_BASKETBALL (:obj:`str`): ``'🏀'`` - DICE_FOOTBALL (:obj:`str`): ``'⚽'`` - DICE_SLOT_MACHINE (:obj:`str`): ``'🎰'`` - DICE_BOWLING (:obj:`str`): ``'🎳'`` - - .. versionadded:: 13.4 - DICE_ALL_EMOJI (List[:obj:`str`]): List of all supported base emoji. - - .. versionchanged:: 13.4 - Added :attr:`DICE_BOWLING` - -:class:`telegram.MessageEntity`: - -Attributes: - MESSAGEENTITY_MENTION (:obj:`str`): ``'mention'`` - MESSAGEENTITY_HASHTAG (:obj:`str`): ``'hashtag'`` - MESSAGEENTITY_CASHTAG (:obj:`str`): ``'cashtag'`` - MESSAGEENTITY_PHONE_NUMBER (:obj:`str`): ``'phone_number'`` - MESSAGEENTITY_BOT_COMMAND (:obj:`str`): ``'bot_command'`` - MESSAGEENTITY_URL (:obj:`str`): ``'url'`` - MESSAGEENTITY_EMAIL (:obj:`str`): ``'email'`` - MESSAGEENTITY_BOLD (:obj:`str`): ``'bold'`` - MESSAGEENTITY_ITALIC (:obj:`str`): ``'italic'`` - MESSAGEENTITY_CODE (:obj:`str`): ``'code'`` - MESSAGEENTITY_PRE (:obj:`str`): ``'pre'`` - MESSAGEENTITY_TEXT_LINK (:obj:`str`): ``'text_link'`` - MESSAGEENTITY_TEXT_MENTION (:obj:`str`): ``'text_mention'`` - MESSAGEENTITY_UNDERLINE (:obj:`str`): ``'underline'`` - MESSAGEENTITY_STRIKETHROUGH (:obj:`str`): ``'strikethrough'`` - MESSAGEENTITY_SPOILER (:obj:`str`): ``'spoiler'`` - - .. versionadded:: 13.10 - MESSAGEENTITY_ALL_TYPES (List[:obj:`str`]): List of all the types of message entity. - -:class:`telegram.ParseMode`: - -Attributes: - PARSEMODE_MARKDOWN (:obj:`str`): ``'Markdown'`` - PARSEMODE_MARKDOWN_V2 (:obj:`str`): ``'MarkdownV2'`` - PARSEMODE_HTML (:obj:`str`): ``'HTML'`` - -:class:`telegram.Poll`: - -Attributes: - POLL_REGULAR (:obj:`str`): ``'regular'`` - POLL_QUIZ (:obj:`str`): ``'quiz'`` - MAX_POLL_QUESTION_LENGTH (:obj:`int`): 300 - MAX_POLL_OPTION_LENGTH (:obj:`int`): 100 - -:class:`telegram.MaskPosition`: - -Attributes: - STICKER_FOREHEAD (:obj:`str`): ``'forehead'`` - STICKER_EYES (:obj:`str`): ``'eyes'`` - STICKER_MOUTH (:obj:`str`): ``'mouth'`` - STICKER_CHIN (:obj:`str`): ``'chin'`` - -:class:`telegram.Update`: - -Attributes: - UPDATE_MESSAGE (:obj:`str`): ``'message'`` - - .. versionadded:: 13.5 - UPDATE_EDITED_MESSAGE (:obj:`str`): ``'edited_message'`` - - .. versionadded:: 13.5 - UPDATE_CHANNEL_POST (:obj:`str`): ``'channel_post'`` - - .. versionadded:: 13.5 - UPDATE_EDITED_CHANNEL_POST (:obj:`str`): ``'edited_channel_post'`` - - .. versionadded:: 13.5 - UPDATE_INLINE_QUERY (:obj:`str`): ``'inline_query'`` - - .. versionadded:: 13.5 - UPDATE_CHOSEN_INLINE_RESULT (:obj:`str`): ``'chosen_inline_result'`` - - .. versionadded:: 13.5 - UPDATE_CALLBACK_QUERY (:obj:`str`): ``'callback_query'`` - - .. versionadded:: 13.5 - UPDATE_SHIPPING_QUERY (:obj:`str`): ``'shipping_query'`` - - .. versionadded:: 13.5 - UPDATE_PRE_CHECKOUT_QUERY (:obj:`str`): ``'pre_checkout_query'`` - - .. versionadded:: 13.5 - UPDATE_POLL (:obj:`str`): ``'poll'`` - - .. versionadded:: 13.5 - UPDATE_POLL_ANSWER (:obj:`str`): ``'poll_answer'`` - - .. versionadded:: 13.5 - UPDATE_MY_CHAT_MEMBER (:obj:`str`): ``'my_chat_member'`` - - .. versionadded:: 13.5 - UPDATE_CHAT_MEMBER (:obj:`str`): ``'chat_member'`` - - .. versionadded:: 13.5 - UPDATE_CHAT_JOIN_REQUEST (:obj:`str`): ``'chat_join_request'`` - - .. versionadded:: 13.8 - UPDATE_ALL_TYPES (List[:obj:`str`]): List of all update types. - - .. versionadded:: 13.5 - .. versionchanged:: 13.8 - -:class:`telegram.BotCommandScope`: - -Attributes: - BOT_COMMAND_SCOPE_DEFAULT (:obj:`str`): ``'default'`` - - ..versionadded:: 13.7 - BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS (:obj:`str`): ``'all_private_chats'`` - - ..versionadded:: 13.7 - BOT_COMMAND_SCOPE_ALL_GROUP_CHATS (:obj:`str`): ``'all_group_chats'`` - - ..versionadded:: 13.7 - BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS (:obj:`str`): ``'all_chat_administrators'`` - - ..versionadded:: 13.7 - BOT_COMMAND_SCOPE_CHAT (:obj:`str`): ``'chat'`` - - ..versionadded:: 13.7 - BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS (:obj:`str`): ``'chat_administrators'`` - - ..versionadded:: 13.7 - BOT_COMMAND_SCOPE_CHAT_MEMBER (:obj:`str`): ``'chat_member'`` - - ..versionadded:: 13.7 - -""" -from typing import List - -BOT_API_VERSION: str = '5.7' -MAX_MESSAGE_LENGTH: int = 4096 -MAX_CAPTION_LENGTH: int = 1024 -ANONYMOUS_ADMIN_ID: int = 1087968824 -SERVICE_CHAT_ID: int = 777000 -FAKE_CHANNEL_ID: int = 136817688 - -# constants above this line are tested - -SUPPORTED_WEBHOOK_PORTS: List[int] = [443, 80, 88, 8443] -MAX_FILESIZE_DOWNLOAD: int = int(20e6) # (20MB) -MAX_FILESIZE_UPLOAD: int = int(50e6) # (50MB) -MAX_PHOTOSIZE_UPLOAD: int = int(10e6) # (10MB) -MAX_MESSAGES_PER_SECOND_PER_CHAT: int = 1 -MAX_MESSAGES_PER_SECOND: int = 30 -MAX_MESSAGES_PER_MINUTE_PER_GROUP: int = 20 -MAX_MESSAGE_ENTITIES: int = 100 -MAX_INLINE_QUERY_RESULTS: int = 50 -MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH: int = 200 - -CHAT_SENDER: str = 'sender' -CHAT_PRIVATE: str = 'private' -CHAT_GROUP: str = 'group' -CHAT_SUPERGROUP: str = 'supergroup' -CHAT_CHANNEL: str = 'channel' - -CHATACTION_FIND_LOCATION: str = 'find_location' -CHATACTION_RECORD_AUDIO: str = 'record_audio' -CHATACTION_RECORD_VOICE: str = 'record_voice' -CHATACTION_RECORD_VIDEO: str = 'record_video' -CHATACTION_RECORD_VIDEO_NOTE: str = 'record_video_note' -CHATACTION_TYPING: str = 'typing' -CHATACTION_UPLOAD_AUDIO: str = 'upload_audio' -CHATACTION_UPLOAD_VOICE: str = 'upload_voice' -CHATACTION_UPLOAD_DOCUMENT: str = 'upload_document' -CHATACTION_CHOOSE_STICKER: str = 'choose_sticker' -CHATACTION_UPLOAD_PHOTO: str = 'upload_photo' -CHATACTION_UPLOAD_VIDEO: str = 'upload_video' -CHATACTION_UPLOAD_VIDEO_NOTE: str = 'upload_video_note' - -CHATMEMBER_ADMINISTRATOR: str = 'administrator' -CHATMEMBER_CREATOR: str = 'creator' -CHATMEMBER_KICKED: str = 'kicked' -CHATMEMBER_LEFT: str = 'left' -CHATMEMBER_MEMBER: str = 'member' -CHATMEMBER_RESTRICTED: str = 'restricted' - -DICE_DICE: str = '🎲' -DICE_DARTS: str = '🎯' -DICE_BASKETBALL: str = '🏀' -DICE_FOOTBALL: str = '⚽' -DICE_SLOT_MACHINE: str = '🎰' -DICE_BOWLING: str = '🎳' -DICE_ALL_EMOJI: List[str] = [ - DICE_DICE, - DICE_DARTS, - DICE_BASKETBALL, - DICE_FOOTBALL, - DICE_SLOT_MACHINE, - DICE_BOWLING, -] - -MESSAGEENTITY_MENTION: str = 'mention' -MESSAGEENTITY_HASHTAG: str = 'hashtag' -MESSAGEENTITY_CASHTAG: str = 'cashtag' -MESSAGEENTITY_PHONE_NUMBER: str = 'phone_number' -MESSAGEENTITY_BOT_COMMAND: str = 'bot_command' -MESSAGEENTITY_URL: str = 'url' -MESSAGEENTITY_EMAIL: str = 'email' -MESSAGEENTITY_BOLD: str = 'bold' -MESSAGEENTITY_ITALIC: str = 'italic' -MESSAGEENTITY_CODE: str = 'code' -MESSAGEENTITY_PRE: str = 'pre' -MESSAGEENTITY_TEXT_LINK: str = 'text_link' -MESSAGEENTITY_TEXT_MENTION: str = 'text_mention' -MESSAGEENTITY_UNDERLINE: str = 'underline' -MESSAGEENTITY_STRIKETHROUGH: str = 'strikethrough' -MESSAGEENTITY_SPOILER: str = 'spoiler' -MESSAGEENTITY_ALL_TYPES: List[str] = [ - MESSAGEENTITY_MENTION, - MESSAGEENTITY_HASHTAG, - MESSAGEENTITY_CASHTAG, - MESSAGEENTITY_PHONE_NUMBER, - MESSAGEENTITY_BOT_COMMAND, - MESSAGEENTITY_URL, - MESSAGEENTITY_EMAIL, - MESSAGEENTITY_BOLD, - MESSAGEENTITY_ITALIC, - MESSAGEENTITY_CODE, - MESSAGEENTITY_PRE, - MESSAGEENTITY_TEXT_LINK, - MESSAGEENTITY_TEXT_MENTION, - MESSAGEENTITY_UNDERLINE, - MESSAGEENTITY_STRIKETHROUGH, - MESSAGEENTITY_SPOILER, -] - -PARSEMODE_MARKDOWN: str = 'Markdown' -PARSEMODE_MARKDOWN_V2: str = 'MarkdownV2' -PARSEMODE_HTML: str = 'HTML' - -POLL_REGULAR: str = 'regular' -POLL_QUIZ: str = 'quiz' -MAX_POLL_QUESTION_LENGTH: int = 300 -MAX_POLL_OPTION_LENGTH: int = 100 - -STICKER_FOREHEAD: str = 'forehead' -STICKER_EYES: str = 'eyes' -STICKER_MOUTH: str = 'mouth' -STICKER_CHIN: str = 'chin' - -UPDATE_MESSAGE = 'message' -UPDATE_EDITED_MESSAGE = 'edited_message' -UPDATE_CHANNEL_POST = 'channel_post' -UPDATE_EDITED_CHANNEL_POST = 'edited_channel_post' -UPDATE_INLINE_QUERY = 'inline_query' -UPDATE_CHOSEN_INLINE_RESULT = 'chosen_inline_result' -UPDATE_CALLBACK_QUERY = 'callback_query' -UPDATE_SHIPPING_QUERY = 'shipping_query' -UPDATE_PRE_CHECKOUT_QUERY = 'pre_checkout_query' -UPDATE_POLL = 'poll' -UPDATE_POLL_ANSWER = 'poll_answer' -UPDATE_MY_CHAT_MEMBER = 'my_chat_member' -UPDATE_CHAT_MEMBER = 'chat_member' -UPDATE_CHAT_JOIN_REQUEST = 'chat_join_request' -UPDATE_ALL_TYPES = [ - UPDATE_MESSAGE, - UPDATE_EDITED_MESSAGE, - UPDATE_CHANNEL_POST, - UPDATE_EDITED_CHANNEL_POST, - UPDATE_INLINE_QUERY, - UPDATE_CHOSEN_INLINE_RESULT, - UPDATE_CALLBACK_QUERY, - UPDATE_SHIPPING_QUERY, - UPDATE_PRE_CHECKOUT_QUERY, - UPDATE_POLL, - UPDATE_POLL_ANSWER, - UPDATE_MY_CHAT_MEMBER, - UPDATE_CHAT_MEMBER, - UPDATE_CHAT_JOIN_REQUEST, -] - -BOT_COMMAND_SCOPE_DEFAULT = 'default' -BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS = 'all_private_chats' -BOT_COMMAND_SCOPE_ALL_GROUP_CHATS = 'all_group_chats' -BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS = 'all_chat_administrators' -BOT_COMMAND_SCOPE_CHAT = 'chat' -BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS = 'chat_administrators' -BOT_COMMAND_SCOPE_CHAT_MEMBER = 'chat_member' diff --git a/telegramer/include/telegram/dice.py b/telegramer/include/telegram/dice.py deleted file mode 100644 index 8836529..0000000 --- a/telegramer/include/telegram/dice.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Dice.""" -from typing import Any, List, ClassVar - -from telegram import TelegramObject, constants - - -class Dice(TelegramObject): - """ - This object represents an animated emoji with a random value for currently supported base - emoji. (The singular form of "dice" is "die". However, PTB mimics the Telegram API, which uses - the term "dice".) - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`value` and :attr:`emoji` are equal. - - Note: - If :attr:`emoji` is "🎯", a value of 6 currently represents a bullseye, while a value of 1 - indicates that the dartboard was missed. However, this behaviour is undocumented and might - be changed by Telegram. - - If :attr:`emoji` is "🏀", a value of 4 or 5 currently score a basket, while a value of 1 to - 3 indicates that the basket was missed. However, this behaviour is undocumented and might - be changed by Telegram. - - If :attr:`emoji` is "⚽", a value of 4 to 5 currently scores a goal, while a value of 1 to - 3 indicates that the goal was missed. However, this behaviour is undocumented and might - be changed by Telegram. - - If :attr:`emoji` is "🎳", a value of 6 knocks all the pins, while a value of 1 means all - the pins were missed. However, this behaviour is undocumented and might be changed by - Telegram. - - If :attr:`emoji` is "🎰", each value corresponds to a unique combination of symbols, which - can be found at our `wiki `_. However, this behaviour is undocumented - and might be changed by Telegram. - - Args: - value (:obj:`int`): Value of the dice. 1-6 for dice, darts and bowling balls, 1-5 for - basketball and football/soccer ball, 1-64 for slot machine. - emoji (:obj:`str`): Emoji on which the dice throw animation is based. - - Attributes: - value (:obj:`int`): Value of the dice. - emoji (:obj:`str`): Emoji on which the dice throw animation is based. - - """ - - __slots__ = ('emoji', 'value', '_id_attrs') - - def __init__(self, value: int, emoji: str, **_kwargs: Any): - self.value = value - self.emoji = emoji - - self._id_attrs = (self.value, self.emoji) - - DICE: ClassVar[str] = constants.DICE_DICE # skipcq: PTC-W0052 - """:const:`telegram.constants.DICE_DICE`""" - DARTS: ClassVar[str] = constants.DICE_DARTS - """:const:`telegram.constants.DICE_DARTS`""" - BASKETBALL: ClassVar[str] = constants.DICE_BASKETBALL - """:const:`telegram.constants.DICE_BASKETBALL`""" - FOOTBALL: ClassVar[str] = constants.DICE_FOOTBALL - """:const:`telegram.constants.DICE_FOOTBALL`""" - SLOT_MACHINE: ClassVar[str] = constants.DICE_SLOT_MACHINE - """:const:`telegram.constants.DICE_SLOT_MACHINE`""" - BOWLING: ClassVar[str] = constants.DICE_BOWLING - """ - :const:`telegram.constants.DICE_BOWLING` - - .. versionadded:: 13.4 - """ - ALL_EMOJI: ClassVar[List[str]] = constants.DICE_ALL_EMOJI - """:const:`telegram.constants.DICE_ALL_EMOJI`""" diff --git a/telegramer/include/telegram/error.py b/telegramer/include/telegram/error.py deleted file mode 100644 index 3cf41d5..0000000 --- a/telegramer/include/telegram/error.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0115 -"""This module contains an object that represents Telegram errors.""" -from typing import Tuple - - -def _lstrip_str(in_s: str, lstr: str) -> str: - """ - Args: - in_s (:obj:`str`): in string - lstr (:obj:`str`): substr to strip from left side - - Returns: - :obj:`str`: The stripped string. - - """ - if in_s.startswith(lstr): - res = in_s[len(lstr) :] - else: - res = in_s - return res - - -class TelegramError(Exception): - """Base class for Telegram errors.""" - - # Apparently the base class Exception already has __dict__ in it, so its not included here - __slots__ = ('message',) - - def __init__(self, message: str): - super().__init__() - - msg = _lstrip_str(message, 'Error: ') - msg = _lstrip_str(msg, '[Error]: ') - msg = _lstrip_str(msg, 'Bad Request: ') - if msg != message: - # api_error - capitalize the msg... - msg = msg.capitalize() - self.message = msg - - def __str__(self) -> str: - return '%s' % self.message - - def __reduce__(self) -> Tuple[type, Tuple[str]]: - return self.__class__, (self.message,) - - -class Unauthorized(TelegramError): - """Raised when the bot has not enough rights to perform the requested action.""" - - __slots__ = () - - -class InvalidToken(TelegramError): - """Raised when the token is invalid.""" - - __slots__ = () - - def __init__(self) -> None: - super().__init__('Invalid token') - - def __reduce__(self) -> Tuple[type, Tuple]: # type: ignore[override] - return self.__class__, () - - -class NetworkError(TelegramError): - """Base class for exceptions due to networking errors.""" - - __slots__ = () - - -class BadRequest(NetworkError): - """Raised when Telegram could not process the request correctly.""" - - __slots__ = () - - -class TimedOut(NetworkError): - """Raised when a request took too long to finish.""" - - __slots__ = () - - def __init__(self) -> None: - super().__init__('Timed out') - - def __reduce__(self) -> Tuple[type, Tuple]: # type: ignore[override] - return self.__class__, () - - -class ChatMigrated(TelegramError): - """ - Raised when the requested group chat migrated to supergroup and has a new chat id. - - Args: - new_chat_id (:obj:`int`): The new chat id of the group. - - """ - - __slots__ = ('new_chat_id',) - - def __init__(self, new_chat_id: int): - super().__init__(f'Group migrated to supergroup. New chat id: {new_chat_id}') - self.new_chat_id = new_chat_id - - def __reduce__(self) -> Tuple[type, Tuple[int]]: # type: ignore[override] - return self.__class__, (self.new_chat_id,) - - -class RetryAfter(TelegramError): - """ - Raised when flood limits where exceeded. - - Args: - retry_after (:obj:`int`): Time in seconds, after which the bot can retry the request. - - """ - - __slots__ = ('retry_after',) - - def __init__(self, retry_after: int): - super().__init__(f'Flood control exceeded. Retry in {float(retry_after)} seconds') - self.retry_after = float(retry_after) - - def __reduce__(self) -> Tuple[type, Tuple[float]]: # type: ignore[override] - return self.__class__, (self.retry_after,) - - -class Conflict(TelegramError): - """Raised when a long poll or webhook conflicts with another one.""" - - __slots__ = () - - def __reduce__(self) -> Tuple[type, Tuple[str]]: - return self.__class__, (self.message,) diff --git a/telegramer/include/telegram/ext/__init__.py b/telegramer/include/telegram/ext/__init__.py deleted file mode 100644 index 1d3a8fe..0000000 --- a/telegramer/include/telegram/ext/__init__.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0413 -"""Extensions over the Telegram Bot API to facilitate bot making""" - -from .extbot import ExtBot -from .basepersistence import BasePersistence -from .picklepersistence import PicklePersistence -from .dictpersistence import DictPersistence -from .handler import Handler -from .callbackcontext import CallbackContext -from .contexttypes import ContextTypes -from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async - -# https://bugs.python.org/issue41451, fixed on 3.7+, doesn't actually remove slots -# try-except is just here in case the __init__ is called twice (like in the tests) -# this block is also the reason for the pylint-ignore at the top of the file -try: - del Dispatcher.__slots__ -except AttributeError as exc: - if str(exc) == '__slots__': - pass - else: - raise exc - -from .jobqueue import JobQueue, Job -from .updater import Updater -from .callbackqueryhandler import CallbackQueryHandler -from .choseninlineresulthandler import ChosenInlineResultHandler -from .inlinequeryhandler import InlineQueryHandler -from .filters import BaseFilter, MessageFilter, UpdateFilter, Filters -from .messagehandler import MessageHandler -from .commandhandler import CommandHandler, PrefixHandler -from .regexhandler import RegexHandler -from .stringcommandhandler import StringCommandHandler -from .stringregexhandler import StringRegexHandler -from .typehandler import TypeHandler -from .conversationhandler import ConversationHandler -from .precheckoutqueryhandler import PreCheckoutQueryHandler -from .shippingqueryhandler import ShippingQueryHandler -from .messagequeue import MessageQueue -from .messagequeue import DelayQueue -from .pollanswerhandler import PollAnswerHandler -from .pollhandler import PollHandler -from .chatmemberhandler import ChatMemberHandler -from .chatjoinrequesthandler import ChatJoinRequestHandler -from .defaults import Defaults -from .callbackdatacache import CallbackDataCache, InvalidCallbackData - -__all__ = ( - 'BaseFilter', - 'BasePersistence', - 'CallbackContext', - 'CallbackDataCache', - 'CallbackQueryHandler', - 'ChatJoinRequestHandler', - 'ChatMemberHandler', - 'ChosenInlineResultHandler', - 'CommandHandler', - 'ContextTypes', - 'ConversationHandler', - 'Defaults', - 'DelayQueue', - 'DictPersistence', - 'Dispatcher', - 'DispatcherHandlerStop', - 'ExtBot', - 'Filters', - 'Handler', - 'InlineQueryHandler', - 'InvalidCallbackData', - 'Job', - 'JobQueue', - 'MessageFilter', - 'MessageHandler', - 'MessageQueue', - 'PicklePersistence', - 'PollAnswerHandler', - 'PollHandler', - 'PreCheckoutQueryHandler', - 'PrefixHandler', - 'RegexHandler', - 'ShippingQueryHandler', - 'StringCommandHandler', - 'StringRegexHandler', - 'TypeHandler', - 'UpdateFilter', - 'Updater', - 'run_async', -) diff --git a/telegramer/include/telegram/ext/basepersistence.py b/telegramer/include/telegram/ext/basepersistence.py deleted file mode 100644 index 20afa1d..0000000 --- a/telegramer/include/telegram/ext/basepersistence.py +++ /dev/null @@ -1,566 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the BasePersistence class.""" -import warnings -from sys import version_info as py_ver -from abc import ABC, abstractmethod -from copy import copy -from typing import Dict, Optional, Tuple, cast, ClassVar, Generic, DefaultDict - -from telegram.utils.deprecate import set_new_attribute_deprecated - -from telegram import Bot -import telegram.ext.extbot - -from telegram.ext.utils.types import UD, CD, BD, ConversationDict, CDCData - - -class BasePersistence(Generic[UD, CD, BD], ABC): - """Interface class for adding persistence to your bot. - Subclass this object for different implementations of a persistent bot. - - All relevant methods must be overwritten. This includes: - - * :meth:`get_bot_data` - * :meth:`update_bot_data` - * :meth:`refresh_bot_data` - * :meth:`get_chat_data` - * :meth:`update_chat_data` - * :meth:`refresh_chat_data` - * :meth:`get_user_data` - * :meth:`update_user_data` - * :meth:`refresh_user_data` - * :meth:`get_callback_data` - * :meth:`update_callback_data` - * :meth:`get_conversations` - * :meth:`update_conversation` - * :meth:`flush` - - If you don't actually need one of those methods, a simple ``pass`` is enough. For example, if - ``store_bot_data=False``, you don't need :meth:`get_bot_data`, :meth:`update_bot_data` or - :meth:`refresh_bot_data`. - - Warning: - Persistence will try to replace :class:`telegram.Bot` instances by :attr:`REPLACED_BOT` and - insert the bot set with :meth:`set_bot` upon loading of the data. This is to ensure that - changes to the bot apply to the saved objects, too. If you change the bots token, this may - lead to e.g. ``Chat not found`` errors. For the limitations on replacing bots see - :meth:`replace_bot` and :meth:`insert_bot`. - - Note: - :meth:`replace_bot` and :meth:`insert_bot` are used *independently* of the implementation - of the :meth:`update/get_*` methods, i.e. you don't need to worry about it while - implementing a custom persistence subclass. - - Args: - store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this - persistence class. Default is :obj:`True`. - store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this - persistence class. Default is :obj:`True` . - store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this - persistence class. Default is :obj:`True`. - store_callback_data (:obj:`bool`, optional): Whether callback_data should be saved by this - persistence class. Default is :obj:`False`. - - .. versionadded:: 13.6 - - Attributes: - store_user_data (:obj:`bool`): Optional, Whether user_data should be saved by this - persistence class. - store_chat_data (:obj:`bool`): Optional. Whether chat_data should be saved by this - persistence class. - store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this - persistence class. - store_callback_data (:obj:`bool`): Optional. Whether callback_data should be saved by this - persistence class. - - .. versionadded:: 13.6 - """ - - # Apparently Py 3.7 and below have '__dict__' in ABC - if py_ver < (3, 7): - __slots__ = ( - 'store_user_data', - 'store_chat_data', - 'store_bot_data', - 'store_callback_data', - 'bot', - ) - else: - __slots__ = ( - 'store_user_data', # type: ignore[assignment] - 'store_chat_data', - 'store_bot_data', - 'store_callback_data', - 'bot', - '__dict__', - ) - - def __new__( - cls, *args: object, **kwargs: object # pylint: disable=W0613 - ) -> 'BasePersistence': - """This overrides the get_* and update_* methods to use insert/replace_bot. - That has the side effect that we always pass deepcopied data to those methods, so in - Pickle/DictPersistence we don't have to worry about copying the data again. - - Note: This doesn't hold for second tuple-entry of callback_data. That's a Dict[str, str], - so no bots to replace anyway. - """ - instance = super().__new__(cls) - get_user_data = instance.get_user_data - get_chat_data = instance.get_chat_data - get_bot_data = instance.get_bot_data - get_callback_data = instance.get_callback_data - update_user_data = instance.update_user_data - update_chat_data = instance.update_chat_data - update_bot_data = instance.update_bot_data - update_callback_data = instance.update_callback_data - - def get_user_data_insert_bot() -> DefaultDict[int, UD]: - return instance.insert_bot(get_user_data()) - - def get_chat_data_insert_bot() -> DefaultDict[int, CD]: - return instance.insert_bot(get_chat_data()) - - def get_bot_data_insert_bot() -> BD: - return instance.insert_bot(get_bot_data()) - - def get_callback_data_insert_bot() -> Optional[CDCData]: - cdc_data = get_callback_data() - if cdc_data is None: - return None - return instance.insert_bot(cdc_data[0]), cdc_data[1] - - def update_user_data_replace_bot(user_id: int, data: UD) -> None: - return update_user_data(user_id, instance.replace_bot(data)) - - def update_chat_data_replace_bot(chat_id: int, data: CD) -> None: - return update_chat_data(chat_id, instance.replace_bot(data)) - - def update_bot_data_replace_bot(data: BD) -> None: - return update_bot_data(instance.replace_bot(data)) - - def update_callback_data_replace_bot(data: CDCData) -> None: - obj_data, queue = data - return update_callback_data((instance.replace_bot(obj_data), queue)) - - # We want to ignore TGDeprecation warnings so we use obj.__setattr__. Adds to __dict__ - object.__setattr__(instance, 'get_user_data', get_user_data_insert_bot) - object.__setattr__(instance, 'get_chat_data', get_chat_data_insert_bot) - object.__setattr__(instance, 'get_bot_data', get_bot_data_insert_bot) - object.__setattr__(instance, 'get_callback_data', get_callback_data_insert_bot) - object.__setattr__(instance, 'update_user_data', update_user_data_replace_bot) - object.__setattr__(instance, 'update_chat_data', update_chat_data_replace_bot) - object.__setattr__(instance, 'update_bot_data', update_bot_data_replace_bot) - object.__setattr__(instance, 'update_callback_data', update_callback_data_replace_bot) - return instance - - def __init__( - self, - store_user_data: bool = True, - store_chat_data: bool = True, - store_bot_data: bool = True, - store_callback_data: bool = False, - ): - self.store_user_data = store_user_data - self.store_chat_data = store_chat_data - self.store_bot_data = store_bot_data - self.store_callback_data = store_callback_data - self.bot: Bot = None # type: ignore[assignment] - - def __setattr__(self, key: str, value: object) -> None: - # Allow user defined subclasses to have custom attributes. - if issubclass(self.__class__, BasePersistence) and self.__class__.__name__ not in { - 'DictPersistence', - 'PicklePersistence', - }: - object.__setattr__(self, key, value) - return - set_new_attribute_deprecated(self, key, value) - - def set_bot(self, bot: Bot) -> None: - """Set the Bot to be used by this persistence instance. - - Args: - bot (:class:`telegram.Bot`): The bot. - """ - if self.store_callback_data and not isinstance(bot, telegram.ext.extbot.ExtBot): - raise TypeError('store_callback_data can only be used with telegram.ext.ExtBot.') - - self.bot = bot - - @classmethod - def replace_bot(cls, obj: object) -> object: - """ - Replaces all instances of :class:`telegram.Bot` that occur within the passed object with - :attr:`REPLACED_BOT`. Currently, this handles objects of type ``list``, ``tuple``, ``set``, - ``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or - ``__slots__`` attribute, excluding classes and objects that can't be copied with - ``copy.copy``. If the parsing of an object fails, the object will be returned unchanged and - the error will be logged. - - Args: - obj (:obj:`object`): The object - - Returns: - :obj:`obj`: Copy of the object with Bot instances replaced. - """ - return cls._replace_bot(obj, {}) - - @classmethod - def _replace_bot(cls, obj: object, memo: Dict[int, object]) -> object: # pylint: disable=R0911 - obj_id = id(obj) - if obj_id in memo: - return memo[obj_id] - - if isinstance(obj, Bot): - memo[obj_id] = cls.REPLACED_BOT - return cls.REPLACED_BOT - if isinstance(obj, (list, set)): - # We copy the iterable here for thread safety, i.e. make sure the object we iterate - # over doesn't change its length during the iteration - temp_iterable = obj.copy() - new_iterable = obj.__class__(cls._replace_bot(item, memo) for item in temp_iterable) - memo[obj_id] = new_iterable - return new_iterable - if isinstance(obj, (tuple, frozenset)): - # tuples and frozensets are immutable so we don't need to worry about thread safety - new_immutable = obj.__class__(cls._replace_bot(item, memo) for item in obj) - memo[obj_id] = new_immutable - return new_immutable - if isinstance(obj, type): - # classes usually do have a __dict__, but it's not writable - warnings.warn( - 'BasePersistence.replace_bot does not handle classes. See ' - 'the docs of BasePersistence.replace_bot for more information.', - RuntimeWarning, - ) - return obj - - try: - new_obj = copy(obj) - memo[obj_id] = new_obj - except Exception: - warnings.warn( - 'BasePersistence.replace_bot does not handle objects that can not be copied. See ' - 'the docs of BasePersistence.replace_bot for more information.', - RuntimeWarning, - ) - memo[obj_id] = obj - return obj - - if isinstance(obj, dict): - # We handle dicts via copy(obj) so we don't have to make a - # difference between dict and defaultdict - new_obj = cast(dict, new_obj) - # We can't iterate over obj.items() due to thread safety, i.e. the dicts length may - # change during the iteration - temp_dict = new_obj.copy() - new_obj.clear() - for k, val in temp_dict.items(): - new_obj[cls._replace_bot(k, memo)] = cls._replace_bot(val, memo) - memo[obj_id] = new_obj - return new_obj - try: - if hasattr(obj, '__slots__'): - for attr_name in new_obj.__slots__: - setattr( - new_obj, - attr_name, - cls._replace_bot( - cls._replace_bot(getattr(new_obj, attr_name), memo), memo - ), - ) - if '__dict__' in obj.__slots__: - # In this case, we have already covered the case that obj has __dict__ - # Note that obj may have a __dict__ even if it's not in __slots__! - memo[obj_id] = new_obj - return new_obj - if hasattr(obj, '__dict__'): - for attr_name, attr in new_obj.__dict__.items(): - setattr(new_obj, attr_name, cls._replace_bot(attr, memo)) - memo[obj_id] = new_obj - return new_obj - except Exception as exception: - warnings.warn( - f'Parsing of an object failed with the following exception: {exception}. ' - f'See the docs of BasePersistence.replace_bot for more information.', - RuntimeWarning, - ) - - memo[obj_id] = obj - return obj - - def insert_bot(self, obj: object) -> object: - """ - Replaces all instances of :attr:`REPLACED_BOT` that occur within the passed object with - :attr:`bot`. Currently, this handles objects of type ``list``, ``tuple``, ``set``, - ``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or - ``__slots__`` attribute, excluding classes and objects that can't be copied with - ``copy.copy``. If the parsing of an object fails, the object will be returned unchanged and - the error will be logged. - - Args: - obj (:obj:`object`): The object - - Returns: - :obj:`obj`: Copy of the object with Bot instances inserted. - """ - return self._insert_bot(obj, {}) - - def _insert_bot(self, obj: object, memo: Dict[int, object]) -> object: # pylint: disable=R0911 - obj_id = id(obj) - if obj_id in memo: - return memo[obj_id] - - if isinstance(obj, Bot): - memo[obj_id] = self.bot - return self.bot - if isinstance(obj, str) and obj == self.REPLACED_BOT: - memo[obj_id] = self.bot - return self.bot - if isinstance(obj, (list, set)): - # We copy the iterable here for thread safety, i.e. make sure the object we iterate - # over doesn't change its length during the iteration - temp_iterable = obj.copy() - new_iterable = obj.__class__(self._insert_bot(item, memo) for item in temp_iterable) - memo[obj_id] = new_iterable - return new_iterable - if isinstance(obj, (tuple, frozenset)): - # tuples and frozensets are immutable so we don't need to worry about thread safety - new_immutable = obj.__class__(self._insert_bot(item, memo) for item in obj) - memo[obj_id] = new_immutable - return new_immutable - if isinstance(obj, type): - # classes usually do have a __dict__, but it's not writable - warnings.warn( - 'BasePersistence.insert_bot does not handle classes. See ' - 'the docs of BasePersistence.insert_bot for more information.', - RuntimeWarning, - ) - return obj - - try: - new_obj = copy(obj) - except Exception: - warnings.warn( - 'BasePersistence.insert_bot does not handle objects that can not be copied. See ' - 'the docs of BasePersistence.insert_bot for more information.', - RuntimeWarning, - ) - memo[obj_id] = obj - return obj - - if isinstance(obj, dict): - # We handle dicts via copy(obj) so we don't have to make a - # difference between dict and defaultdict - new_obj = cast(dict, new_obj) - # We can't iterate over obj.items() due to thread safety, i.e. the dicts length may - # change during the iteration - temp_dict = new_obj.copy() - new_obj.clear() - for k, val in temp_dict.items(): - new_obj[self._insert_bot(k, memo)] = self._insert_bot(val, memo) - memo[obj_id] = new_obj - return new_obj - try: - if hasattr(obj, '__slots__'): - for attr_name in obj.__slots__: - setattr( - new_obj, - attr_name, - self._insert_bot( - self._insert_bot(getattr(new_obj, attr_name), memo), memo - ), - ) - if '__dict__' in obj.__slots__: - # In this case, we have already covered the case that obj has __dict__ - # Note that obj may have a __dict__ even if it's not in __slots__! - memo[obj_id] = new_obj - return new_obj - if hasattr(obj, '__dict__'): - for attr_name, attr in new_obj.__dict__.items(): - setattr(new_obj, attr_name, self._insert_bot(attr, memo)) - memo[obj_id] = new_obj - return new_obj - except Exception as exception: - warnings.warn( - f'Parsing of an object failed with the following exception: {exception}. ' - f'See the docs of BasePersistence.insert_bot for more information.', - RuntimeWarning, - ) - - memo[obj_id] = obj - return obj - - @abstractmethod - def get_user_data(self) -> DefaultDict[int, UD]: - """Will be called by :class:`telegram.ext.Dispatcher` upon creation with a - persistence object. It should return the ``user_data`` if stored, or an empty - :obj:`defaultdict(telegram.ext.utils.types.UD)` with integer keys. - - Returns: - DefaultDict[:obj:`int`, :class:`telegram.ext.utils.types.UD`]: The restored user data. - """ - - @abstractmethod - def get_chat_data(self) -> DefaultDict[int, CD]: - """Will be called by :class:`telegram.ext.Dispatcher` upon creation with a - persistence object. It should return the ``chat_data`` if stored, or an empty - :obj:`defaultdict(telegram.ext.utils.types.CD)` with integer keys. - - Returns: - DefaultDict[:obj:`int`, :class:`telegram.ext.utils.types.CD`]: The restored chat data. - """ - - @abstractmethod - def get_bot_data(self) -> BD: - """Will be called by :class:`telegram.ext.Dispatcher` upon creation with a - persistence object. It should return the ``bot_data`` if stored, or an empty - :class:`telegram.ext.utils.types.BD`. - - Returns: - :class:`telegram.ext.utils.types.BD`: The restored bot data. - """ - - def get_callback_data(self) -> Optional[CDCData]: - """Will be called by :class:`telegram.ext.Dispatcher` upon creation with a - persistence object. If callback data was stored, it should be returned. - - .. versionadded:: 13.6 - - Returns: - Optional[:class:`telegram.ext.utils.types.CDCData`]: The restored meta data or - :obj:`None`, if no data was stored. - """ - raise NotImplementedError - - @abstractmethod - def get_conversations(self, name: str) -> ConversationDict: - """Will be called by :class:`telegram.ext.Dispatcher` when a - :class:`telegram.ext.ConversationHandler` is added if - :attr:`telegram.ext.ConversationHandler.persistent` is :obj:`True`. - It should return the conversations for the handler with `name` or an empty :obj:`dict` - - Args: - name (:obj:`str`): The handlers name. - - Returns: - :obj:`dict`: The restored conversations for the handler. - """ - - @abstractmethod - def update_conversation( - self, name: str, key: Tuple[int, ...], new_state: Optional[object] - ) -> None: - """Will be called when a :class:`telegram.ext.ConversationHandler` changes states. - This allows the storage of the new state in the persistence. - - Args: - name (:obj:`str`): The handler's name. - key (:obj:`tuple`): The key the state is changed for. - new_state (:obj:`tuple` | :obj:`any`): The new state for the given key. - """ - - @abstractmethod - def update_user_data(self, user_id: int, data: UD) -> None: - """Will be called by the :class:`telegram.ext.Dispatcher` after a handler has - handled an update. - - Args: - user_id (:obj:`int`): The user the data might have been changed for. - data (:class:`telegram.ext.utils.types.UD`): The - :attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``. - """ - - @abstractmethod - def update_chat_data(self, chat_id: int, data: CD) -> None: - """Will be called by the :class:`telegram.ext.Dispatcher` after a handler has - handled an update. - - Args: - chat_id (:obj:`int`): The chat the data might have been changed for. - data (:class:`telegram.ext.utils.types.CD`): The - :attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``. - """ - - @abstractmethod - def update_bot_data(self, data: BD) -> None: - """Will be called by the :class:`telegram.ext.Dispatcher` after a handler has - handled an update. - - Args: - data (:class:`telegram.ext.utils.types.BD`): The - :attr:`telegram.ext.Dispatcher.bot_data`. - """ - - def refresh_user_data(self, user_id: int, user_data: UD) -> None: - """Will be called by the :class:`telegram.ext.Dispatcher` before passing the - :attr:`user_data` to a callback. Can be used to update data stored in :attr:`user_data` - from an external source. - - .. versionadded:: 13.6 - - Args: - user_id (:obj:`int`): The user ID this :attr:`user_data` is associated with. - user_data (:class:`telegram.ext.utils.types.UD`): The ``user_data`` of a single user. - """ - - def refresh_chat_data(self, chat_id: int, chat_data: CD) -> None: - """Will be called by the :class:`telegram.ext.Dispatcher` before passing the - :attr:`chat_data` to a callback. Can be used to update data stored in :attr:`chat_data` - from an external source. - - .. versionadded:: 13.6 - - Args: - chat_id (:obj:`int`): The chat ID this :attr:`chat_data` is associated with. - chat_data (:class:`telegram.ext.utils.types.CD`): The ``chat_data`` of a single chat. - """ - - def refresh_bot_data(self, bot_data: BD) -> None: - """Will be called by the :class:`telegram.ext.Dispatcher` before passing the - :attr:`bot_data` to a callback. Can be used to update data stored in :attr:`bot_data` - from an external source. - - .. versionadded:: 13.6 - - Args: - bot_data (:class:`telegram.ext.utils.types.BD`): The ``bot_data``. - """ - - def update_callback_data(self, data: CDCData) -> None: - """Will be called by the :class:`telegram.ext.Dispatcher` after a handler has - handled an update. - - .. versionadded:: 13.6 - - Args: - data (:class:`telegram.ext.utils.types.CDCData`): The relevant data to restore - :class:`telegram.ext.CallbackDataCache`. - """ - raise NotImplementedError - - def flush(self) -> None: - """Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the - persistence a chance to finish up saving or close a database connection gracefully. - """ - - REPLACED_BOT: ClassVar[str] = 'bot_instance_replaced_by_ptb_persistence' - """:obj:`str`: Placeholder for :class:`telegram.Bot` instances replaced in saved data.""" diff --git a/telegramer/include/telegram/ext/callbackcontext.py b/telegramer/include/telegram/ext/callbackcontext.py deleted file mode 100644 index 607ca92..0000000 --- a/telegramer/include/telegram/ext/callbackcontext.py +++ /dev/null @@ -1,361 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=R0201 -"""This module contains the CallbackContext class.""" -from queue import Queue -from typing import ( - TYPE_CHECKING, - Dict, - List, - Match, - NoReturn, - Optional, - Tuple, - Union, - Generic, - Type, - TypeVar, -) - -from telegram import Update, CallbackQuery -from telegram.ext import ExtBot -from telegram.ext.utils.types import UD, CD, BD - -if TYPE_CHECKING: - from telegram import Bot - from telegram.ext import Dispatcher, Job, JobQueue - -CC = TypeVar('CC', bound='CallbackContext') - - -class CallbackContext(Generic[UD, CD, BD]): - """ - This is a context object passed to the callback called by :class:`telegram.ext.Handler` - or by the :class:`telegram.ext.Dispatcher` in an error handler added by - :attr:`telegram.ext.Dispatcher.add_error_handler` or to the callback of a - :class:`telegram.ext.Job`. - - Note: - :class:`telegram.ext.Dispatcher` will create a single context for an entire update. This - means that if you got 2 handlers in different groups and they both get called, they will - get passed the same `CallbackContext` object (of course with proper attributes like - `.matches` differing). This allows you to add custom attributes in a lower handler group - callback, and then subsequently access those attributes in a higher handler group callback. - Note that the attributes on `CallbackContext` might change in the future, so make sure to - use a fairly unique name for the attributes. - - Warning: - Do not combine custom attributes and ``@run_async``/ - :meth:`telegram.ext.Disptacher.run_async`. Due to how ``run_async`` works, it will - almost certainly execute the callbacks for an update out of order, and the attributes - that you think you added will not be present. - - Args: - dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this context. - - Attributes: - matches (List[:obj:`re match object`]): Optional. If the associated update originated from - a regex-supported handler or had a :class:`Filters.regex`, this will contain a list of - match objects for every pattern where ``re.search(pattern, string)`` returned a match. - Note that filters short circuit, so combined regex filters will not always - be evaluated. - args (List[:obj:`str`]): Optional. Arguments passed to a command if the associated update - is handled by :class:`telegram.ext.CommandHandler`, :class:`telegram.ext.PrefixHandler` - or :class:`telegram.ext.StringCommandHandler`. It contains a list of the words in the - text after the command, using any whitespace string as a delimiter. - error (:obj:`Exception`): Optional. The error that was raised. Only present when passed - to a error handler registered with :attr:`telegram.ext.Dispatcher.add_error_handler`. - async_args (List[:obj:`object`]): Optional. Positional arguments of the function that - raised the error. Only present when the raising function was run asynchronously using - :meth:`telegram.ext.Dispatcher.run_async`. - async_kwargs (Dict[:obj:`str`, :obj:`object`]): Optional. Keyword arguments of the function - that raised the error. Only present when the raising function was run asynchronously - using :meth:`telegram.ext.Dispatcher.run_async`. - job (:class:`telegram.ext.Job`): Optional. The job which originated this callback. - Only present when passed to the callback of :class:`telegram.ext.Job`. - - """ - - __slots__ = ( - '_dispatcher', - '_chat_id_and_data', - '_user_id_and_data', - 'args', - 'matches', - 'error', - 'job', - 'async_args', - 'async_kwargs', - '__dict__', - ) - - def __init__(self, dispatcher: 'Dispatcher'): - """ - Args: - dispatcher (:class:`telegram.ext.Dispatcher`): - """ - if not dispatcher.use_context: - raise ValueError( - 'CallbackContext should not be used with a non context aware ' 'dispatcher!' - ) - self._dispatcher = dispatcher - self._chat_id_and_data: Optional[Tuple[int, CD]] = None - self._user_id_and_data: Optional[Tuple[int, UD]] = None - self.args: Optional[List[str]] = None - self.matches: Optional[List[Match]] = None - self.error: Optional[Exception] = None - self.job: Optional['Job'] = None - self.async_args: Optional[Union[List, Tuple]] = None - self.async_kwargs: Optional[Dict[str, object]] = None - - @property - def dispatcher(self) -> 'Dispatcher': - """:class:`telegram.ext.Dispatcher`: The dispatcher associated with this context.""" - return self._dispatcher - - @property - def bot_data(self) -> BD: - """:obj:`dict`: Optional. A dict that can be used to keep any data in. For each - update it will be the same ``dict``. - """ - return self.dispatcher.bot_data - - @bot_data.setter - def bot_data(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to bot_data, see https://git.io/Jt6ic" - ) - - @property - def chat_data(self) -> Optional[CD]: - """:obj:`dict`: Optional. A dict that can be used to keep any data in. For each - update from the same chat id it will be the same ``dict``. - - Warning: - When a group chat migrates to a supergroup, its chat id will change and the - ``chat_data`` needs to be transferred. For details see our `wiki page - `_. - """ - if self._chat_id_and_data: - return self._chat_id_and_data[1] - return None - - @chat_data.setter - def chat_data(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to chat_data, see https://git.io/Jt6ic" - ) - - @property - def user_data(self) -> Optional[UD]: - """:obj:`dict`: Optional. A dict that can be used to keep any data in. For each - update from the same user it will be the same ``dict``. - """ - if self._user_id_and_data: - return self._user_id_and_data[1] - return None - - @user_data.setter - def user_data(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to user_data, see https://git.io/Jt6ic" - ) - - def refresh_data(self) -> None: - """If :attr:`dispatcher` uses persistence, calls - :meth:`telegram.ext.BasePersistence.refresh_bot_data` on :attr:`bot_data`, - :meth:`telegram.ext.BasePersistence.refresh_chat_data` on :attr:`chat_data` and - :meth:`telegram.ext.BasePersistence.refresh_user_data` on :attr:`user_data`, if - appropriate. - - .. versionadded:: 13.6 - """ - if self.dispatcher.persistence: - if self.dispatcher.persistence.store_bot_data: - self.dispatcher.persistence.refresh_bot_data(self.bot_data) - if self.dispatcher.persistence.store_chat_data and self._chat_id_and_data is not None: - self.dispatcher.persistence.refresh_chat_data(*self._chat_id_and_data) - if self.dispatcher.persistence.store_user_data and self._user_id_and_data is not None: - self.dispatcher.persistence.refresh_user_data(*self._user_id_and_data) - - def drop_callback_data(self, callback_query: CallbackQuery) -> None: - """ - Deletes the cached data for the specified callback query. - - .. versionadded:: 13.6 - - Note: - Will *not* raise exceptions in case the data is not found in the cache. - *Will* raise :class:`KeyError` in case the callback query can not be found in the - cache. - - Args: - callback_query (:class:`telegram.CallbackQuery`): The callback query. - - Raises: - KeyError | RuntimeError: :class:`KeyError`, if the callback query can not be found in - the cache and :class:`RuntimeError`, if the bot doesn't allow for arbitrary - callback data. - """ - if isinstance(self.bot, ExtBot): - if not self.bot.arbitrary_callback_data: - raise RuntimeError( - 'This telegram.ext.ExtBot instance does not use arbitrary callback data.' - ) - self.bot.callback_data_cache.drop_data(callback_query) - else: - raise RuntimeError('telegram.Bot does not allow for arbitrary callback data.') - - @classmethod - def from_error( - cls: Type[CC], - update: object, - error: Exception, - dispatcher: 'Dispatcher', - async_args: Union[List, Tuple] = None, - async_kwargs: Dict[str, object] = None, - ) -> CC: - """ - Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the error - handlers. - - .. seealso:: :meth:`telegram.ext.Dispatcher.add_error_handler` - - Args: - update (:obj:`object` | :class:`telegram.Update`): The update associated with the - error. May be :obj:`None`, e.g. for errors in job callbacks. - error (:obj:`Exception`): The error. - dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this - context. - async_args (List[:obj:`object`]): Optional. Positional arguments of the function that - raised the error. Pass only when the raising function was run asynchronously using - :meth:`telegram.ext.Dispatcher.run_async`. - async_kwargs (Dict[:obj:`str`, :obj:`object`]): Optional. Keyword arguments of the - function that raised the error. Pass only when the raising function was run - asynchronously using :meth:`telegram.ext.Dispatcher.run_async`. - - Returns: - :class:`telegram.ext.CallbackContext` - """ - self = cls.from_update(update, dispatcher) - self.error = error - self.async_args = async_args - self.async_kwargs = async_kwargs - return self - - @classmethod - def from_update(cls: Type[CC], update: object, dispatcher: 'Dispatcher') -> CC: - """ - Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the - handlers. - - .. seealso:: :meth:`telegram.ext.Dispatcher.add_handler` - - Args: - update (:obj:`object` | :class:`telegram.Update`): The update. - dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this - context. - - Returns: - :class:`telegram.ext.CallbackContext` - """ - self = cls(dispatcher) - - if update is not None and isinstance(update, Update): - chat = update.effective_chat - user = update.effective_user - - if chat: - self._chat_id_and_data = ( - chat.id, - dispatcher.chat_data[chat.id], # pylint: disable=W0212 - ) - if user: - self._user_id_and_data = ( - user.id, - dispatcher.user_data[user.id], # pylint: disable=W0212 - ) - return self - - @classmethod - def from_job(cls: Type[CC], job: 'Job', dispatcher: 'Dispatcher') -> CC: - """ - Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to a - job callback. - - .. seealso:: :meth:`telegram.ext.JobQueue` - - Args: - job (:class:`telegram.ext.Job`): The job. - dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this - context. - - Returns: - :class:`telegram.ext.CallbackContext` - """ - self = cls(dispatcher) - self.job = job - return self - - def update(self, data: Dict[str, object]) -> None: - """Updates ``self.__slots__`` with the passed data. - - Args: - data (Dict[:obj:`str`, :obj:`object`]): The data. - """ - for key, value in data.items(): - setattr(self, key, value) - - @property - def bot(self) -> 'Bot': - """:class:`telegram.Bot`: The bot associated with this context.""" - return self._dispatcher.bot - - @property - def job_queue(self) -> Optional['JobQueue']: - """ - :class:`telegram.ext.JobQueue`: The ``JobQueue`` used by the - :class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater` - associated with this context. - - """ - return self._dispatcher.job_queue - - @property - def update_queue(self) -> Queue: - """ - :class:`queue.Queue`: The ``Queue`` instance used by the - :class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater` - associated with this context. - - """ - return self._dispatcher.update_queue - - @property - def match(self) -> Optional[Match[str]]: - """ - `Regex match type`: The first match from :attr:`matches`. - Useful if you are only filtering using a single regex filter. - Returns `None` if :attr:`matches` is empty. - """ - try: - return self.matches[0] # type: ignore[index] # pylint: disable=unsubscriptable-object - except (IndexError, TypeError): - return None diff --git a/telegramer/include/telegram/ext/callbackdatacache.py b/telegramer/include/telegram/ext/callbackdatacache.py deleted file mode 100644 index df64005..0000000 --- a/telegramer/include/telegram/ext/callbackdatacache.py +++ /dev/null @@ -1,408 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the CallbackDataCache class.""" -import logging -import time -from datetime import datetime -from threading import Lock -from typing import Dict, Tuple, Union, Optional, MutableMapping, TYPE_CHECKING, cast -from uuid import uuid4 - -from cachetools import LRUCache # pylint: disable=E0401 - -from telegram import ( - InlineKeyboardMarkup, - InlineKeyboardButton, - TelegramError, - CallbackQuery, - Message, - User, -) -from telegram.utils.helpers import to_float_timestamp -from telegram.ext.utils.types import CDCData - -if TYPE_CHECKING: - from telegram.ext import ExtBot - - -class InvalidCallbackData(TelegramError): - """ - Raised when the received callback data has been tempered with or deleted from cache. - - .. versionadded:: 13.6 - - Args: - callback_data (:obj:`int`, optional): The button data of which the callback data could not - be found. - - Attributes: - callback_data (:obj:`int`): Optional. The button data of which the callback data could not - be found. - """ - - __slots__ = ('callback_data',) - - def __init__(self, callback_data: str = None) -> None: - super().__init__( - 'The object belonging to this callback_data was deleted or the callback_data was ' - 'manipulated.' - ) - self.callback_data = callback_data - - def __reduce__(self) -> Tuple[type, Tuple[Optional[str]]]: # type: ignore[override] - return self.__class__, (self.callback_data,) - - -class _KeyboardData: - __slots__ = ('keyboard_uuid', 'button_data', 'access_time') - - def __init__( - self, keyboard_uuid: str, access_time: float = None, button_data: Dict[str, object] = None - ): - self.keyboard_uuid = keyboard_uuid - self.button_data = button_data or {} - self.access_time = access_time or time.time() - - def update_access_time(self) -> None: - """Updates the access time with the current time.""" - self.access_time = time.time() - - def to_tuple(self) -> Tuple[str, float, Dict[str, object]]: - """Gives a tuple representation consisting of the keyboard uuid, the access time and the - button data. - """ - return self.keyboard_uuid, self.access_time, self.button_data - - -class CallbackDataCache: - """A custom cache for storing the callback data of a :class:`telegram.ext.ExtBot`. Internally, - it keeps two mappings with fixed maximum size: - - * One for mapping the data received in callback queries to the cached objects - * One for mapping the IDs of received callback queries to the cached objects - - The second mapping allows to manually drop data that has been cached for keyboards of messages - sent via inline mode. - If necessary, will drop the least recently used items. - - .. versionadded:: 13.6 - - Args: - bot (:class:`telegram.ext.ExtBot`): The bot this cache is for. - maxsize (:obj:`int`, optional): Maximum number of items in each of the internal mappings. - Defaults to 1024. - persistent_data (:obj:`telegram.ext.utils.types.CDCData`, optional): Data to initialize - the cache with, as returned by :meth:`telegram.ext.BasePersistence.get_callback_data`. - - Attributes: - bot (:class:`telegram.ext.ExtBot`): The bot this cache is for. - maxsize (:obj:`int`): maximum size of the cache. - - """ - - __slots__ = ('bot', 'maxsize', '_keyboard_data', '_callback_queries', '__lock', 'logger') - - def __init__( - self, - bot: 'ExtBot', - maxsize: int = 1024, - persistent_data: CDCData = None, - ): - self.logger = logging.getLogger(__name__) - - self.bot = bot - self.maxsize = maxsize - self._keyboard_data: MutableMapping[str, _KeyboardData] = LRUCache(maxsize=maxsize) - self._callback_queries: MutableMapping[str, str] = LRUCache(maxsize=maxsize) - self.__lock = Lock() - - if persistent_data: - keyboard_data, callback_queries = persistent_data - for key, value in callback_queries.items(): - self._callback_queries[key] = value - for uuid, access_time, data in keyboard_data: - self._keyboard_data[uuid] = _KeyboardData( - keyboard_uuid=uuid, access_time=access_time, button_data=data - ) - - @property - def persistence_data(self) -> CDCData: - """:obj:`telegram.ext.utils.types.CDCData`: The data that needs to be persisted to allow - caching callback data across bot reboots. - """ - # While building a list/dict from the LRUCaches has linear runtime (in the number of - # entries), the runtime is bounded by maxsize and it has the big upside of not throwing a - # highly customized data structure at users trying to implement a custom persistence class - with self.__lock: - return [data.to_tuple() for data in self._keyboard_data.values()], dict( - self._callback_queries.items() - ) - - def process_keyboard(self, reply_markup: InlineKeyboardMarkup) -> InlineKeyboardMarkup: - """Registers the reply markup to the cache. If any of the buttons have - :attr:`callback_data`, stores that data and builds a new keyboard with the correspondingly - replaced buttons. Otherwise does nothing and returns the original reply markup. - - Args: - reply_markup (:class:`telegram.InlineKeyboardMarkup`): The keyboard. - - Returns: - :class:`telegram.InlineKeyboardMarkup`: The keyboard to be passed to Telegram. - - """ - with self.__lock: - return self.__process_keyboard(reply_markup) - - def __process_keyboard(self, reply_markup: InlineKeyboardMarkup) -> InlineKeyboardMarkup: - keyboard_uuid = uuid4().hex - keyboard_data = _KeyboardData(keyboard_uuid) - - # Built a new nested list of buttons by replacing the callback data if needed - buttons = [ - [ - # We create a new button instead of replacing callback_data in case the - # same object is used elsewhere - InlineKeyboardButton( - btn.text, - callback_data=self.__put_button(btn.callback_data, keyboard_data), - ) - if btn.callback_data - else btn - for btn in column - ] - for column in reply_markup.inline_keyboard - ] - - if not keyboard_data.button_data: - # If we arrive here, no data had to be replaced and we can return the input - return reply_markup - - self._keyboard_data[keyboard_uuid] = keyboard_data - return InlineKeyboardMarkup(buttons) - - @staticmethod - def __put_button(callback_data: object, keyboard_data: _KeyboardData) -> str: - """Stores the data for a single button in :attr:`keyboard_data`. - Returns the string that should be passed instead of the callback_data, which is - ``keyboard_uuid + button_uuids``. - """ - uuid = uuid4().hex - keyboard_data.button_data[uuid] = callback_data - return f'{keyboard_data.keyboard_uuid}{uuid}' - - def __get_keyboard_uuid_and_button_data( - self, callback_data: str - ) -> Union[Tuple[str, object], Tuple[None, InvalidCallbackData]]: - keyboard, button = self.extract_uuids(callback_data) - try: - # we get the values before calling update() in case KeyErrors are raised - # we don't want to update in that case - keyboard_data = self._keyboard_data[keyboard] - button_data = keyboard_data.button_data[button] - # Update the timestamp for the LRU - keyboard_data.update_access_time() - return keyboard, button_data - except KeyError: - return None, InvalidCallbackData(callback_data) - - @staticmethod - def extract_uuids(callback_data: str) -> Tuple[str, str]: - """Extracts the keyboard uuid and the button uuid from the given ``callback_data``. - - Args: - callback_data (:obj:`str`): The ``callback_data`` as present in the button. - - Returns: - (:obj:`str`, :obj:`str`): Tuple of keyboard and button uuid - - """ - # Extract the uuids as put in __put_button - return callback_data[:32], callback_data[32:] - - def process_message(self, message: Message) -> None: - """Replaces the data in the inline keyboard attached to the message with the cached - objects, if necessary. If the data could not be found, - :class:`telegram.ext.InvalidCallbackData` will be inserted. - - Note: - Checks :attr:`telegram.Message.via_bot` and :attr:`telegram.Message.from_user` to check - if the reply markup (if any) was actually sent by this caches bot. If it was not, the - message will be returned unchanged. - - Note that this will fail for channel posts, as :attr:`telegram.Message.from_user` is - :obj:`None` for those! In the corresponding reply markups the callback data will be - replaced by :class:`telegram.ext.InvalidCallbackData`. - - Warning: - * Does *not* consider :attr:`telegram.Message.reply_to_message` and - :attr:`telegram.Message.pinned_message`. Pass them to these method separately. - * *In place*, i.e. the passed :class:`telegram.Message` will be changed! - - Args: - message (:class:`telegram.Message`): The message. - - """ - with self.__lock: - self.__process_message(message) - - def __process_message(self, message: Message) -> Optional[str]: - """As documented in process_message, but returns the uuid of the attached keyboard, if any, - which is relevant for process_callback_query. - - **IN PLACE** - """ - if not message.reply_markup: - return None - - if message.via_bot: - sender: Optional[User] = message.via_bot - elif message.from_user: - sender = message.from_user - else: - sender = None - - if sender is not None and sender != self.bot.bot: - return None - - keyboard_uuid = None - - for row in message.reply_markup.inline_keyboard: - for button in row: - if button.callback_data: - button_data = cast(str, button.callback_data) - keyboard_id, callback_data = self.__get_keyboard_uuid_and_button_data( - button_data - ) - # update_callback_data makes sure that the _id_attrs are updated - button.update_callback_data(callback_data) - - # This is lazy loaded. The firsts time we find a button - # we load the associated keyboard - afterwards, there is - if not keyboard_uuid and not isinstance(callback_data, InvalidCallbackData): - keyboard_uuid = keyboard_id - - return keyboard_uuid - - def process_callback_query(self, callback_query: CallbackQuery) -> None: - """Replaces the data in the callback query and the attached messages keyboard with the - cached objects, if necessary. If the data could not be found, - :class:`telegram.ext.InvalidCallbackData` will be inserted. - If :attr:`callback_query.data` or :attr:`callback_query.message` is present, this also - saves the callback queries ID in order to be able to resolve it to the stored data. - - Note: - Also considers inserts data into the buttons of - :attr:`telegram.Message.reply_to_message` and :attr:`telegram.Message.pinned_message` - if necessary. - - Warning: - *In place*, i.e. the passed :class:`telegram.CallbackQuery` will be changed! - - Args: - callback_query (:class:`telegram.CallbackQuery`): The callback query. - - """ - with self.__lock: - mapped = False - - if callback_query.data: - data = callback_query.data - - # Get the cached callback data for the CallbackQuery - keyboard_uuid, button_data = self.__get_keyboard_uuid_and_button_data(data) - callback_query.data = button_data # type: ignore[assignment] - - # Map the callback queries ID to the keyboards UUID for later use - if not mapped and not isinstance(button_data, InvalidCallbackData): - self._callback_queries[callback_query.id] = keyboard_uuid # type: ignore - mapped = True - - # Get the cached callback data for the inline keyboard attached to the - # CallbackQuery. - if callback_query.message: - self.__process_message(callback_query.message) - for message in ( - callback_query.message.pinned_message, - callback_query.message.reply_to_message, - ): - if message: - self.__process_message(message) - - def drop_data(self, callback_query: CallbackQuery) -> None: - """Deletes the data for the specified callback query. - - Note: - Will *not* raise exceptions in case the callback data is not found in the cache. - *Will* raise :class:`KeyError` in case the callback query can not be found in the - cache. - - Args: - callback_query (:class:`telegram.CallbackQuery`): The callback query. - - Raises: - KeyError: If the callback query can not be found in the cache - """ - with self.__lock: - try: - keyboard_uuid = self._callback_queries.pop(callback_query.id) - self.__drop_keyboard(keyboard_uuid) - except KeyError as exc: - raise KeyError('CallbackQuery was not found in cache.') from exc - - def __drop_keyboard(self, keyboard_uuid: str) -> None: - try: - self._keyboard_data.pop(keyboard_uuid) - except KeyError: - return - - def clear_callback_data(self, time_cutoff: Union[float, datetime] = None) -> None: - """Clears the stored callback data. - - Args: - time_cutoff (:obj:`float` | :obj:`datetime.datetime`, optional): Pass a UNIX timestamp - or a :obj:`datetime.datetime` to clear only entries which are older. - For timezone naive :obj:`datetime.datetime` objects, the default timezone of the - bot will be used. - - """ - with self.__lock: - self.__clear(self._keyboard_data, time_cutoff=time_cutoff) - - def clear_callback_queries(self) -> None: - """Clears the stored callback query IDs.""" - with self.__lock: - self.__clear(self._callback_queries) - - def __clear(self, mapping: MutableMapping, time_cutoff: Union[float, datetime] = None) -> None: - if not time_cutoff: - mapping.clear() - return - - if isinstance(time_cutoff, datetime): - effective_cutoff = to_float_timestamp( - time_cutoff, tzinfo=self.bot.defaults.tzinfo if self.bot.defaults else None - ) - else: - effective_cutoff = time_cutoff - - # We need a list instead of a generator here, as the list doesn't change it's size - # during the iteration - to_drop = [key for key, data in mapping.items() if data.access_time < effective_cutoff] - for key in to_drop: - mapping.pop(key) diff --git a/telegramer/include/telegram/ext/callbackqueryhandler.py b/telegramer/include/telegram/ext/callbackqueryhandler.py deleted file mode 100644 index bb19fa7..0000000 --- a/telegramer/include/telegram/ext/callbackqueryhandler.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the CallbackQueryHandler class.""" - -import re -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Match, - Optional, - Pattern, - TypeVar, - Union, - cast, -) - -from telegram import Update -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE - -from .handler import Handler -from .utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') - - -class CallbackQueryHandler(Handler[Update, CCT]): - """Handler class to handle Telegram callback queries. Optionally based on a regex. - - Read the documentation of the ``re`` module for more information. - - Note: - * :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same - user or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - * If your bot allows arbitrary objects as ``callback_data``, it may happen that the - original ``callback_data`` for the incoming :class:`telegram.CallbackQuery`` can not be - found. This is the case when either a malicious client tempered with the - ``callback_data`` or the data was simply dropped from cache or not persisted. In these - cases, an instance of :class:`telegram.ext.InvalidCallbackData` will be set as - ``callback_data``. - - .. versionadded:: 13.6 - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pattern (:obj:`str` | `Pattern` | :obj:`callable` | :obj:`type`, optional): - Pattern to test :attr:`telegram.CallbackQuery.data` against. If a string or a regex - pattern is passed, :meth:`re.match` is used on :attr:`telegram.CallbackQuery.data` to - determine if an update should be handled by this handler. If your bot allows arbitrary - objects as ``callback_data``, non-strings will be accepted. To filter arbitrary - objects you may pass - - * a callable, accepting exactly one argument, namely the - :attr:`telegram.CallbackQuery.data`. It must return :obj:`True` or - :obj:`False`/:obj:`None` to indicate, whether the update should be handled. - * a :obj:`type`. If :attr:`telegram.CallbackQuery.data` is an instance of that type - (or a subclass), the update will be handled. - - If :attr:`telegram.CallbackQuery.data` is :obj:`None`, the - :class:`telegram.CallbackQuery` update will not be handled. - - .. versionchanged:: 13.6 - Added support for arbitrary callback data. - pass_groups (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. - Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``. - Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pattern (`Pattern` | :obj:`callable` | :obj:`type`): Optional. Regex pattern, callback or - type to test :attr:`telegram.CallbackQuery.data` against. - - .. versionchanged:: 13.6 - Added support for arbitrary callback data. - pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the - callback function. - pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('pattern', 'pass_groups', 'pass_groupdict') - - def __init__( - self, - callback: Callable[[Update, CCT], RT], - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None, - pass_groups: bool = False, - pass_groupdict: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - run_async=run_async, - ) - - if isinstance(pattern, str): - pattern = re.compile(pattern) - - self.pattern = pattern - self.pass_groups = pass_groups - self.pass_groupdict = pass_groupdict - - def check_update(self, update: object) -> Optional[Union[bool, object]]: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - if isinstance(update, Update) and update.callback_query: - callback_data = update.callback_query.data - if self.pattern: - if callback_data is None: - return False - if isinstance(self.pattern, type): - return isinstance(callback_data, self.pattern) - if callable(self.pattern): - return self.pattern(callback_data) - match = re.match(self.pattern, callback_data) - if match: - return match - else: - return True - return None - - def collect_optional_args( - self, - dispatcher: 'Dispatcher', - update: Update = None, - check_result: Union[bool, Match] = None, - ) -> Dict[str, object]: - """Pass the results of ``re.match(pattern, data).{groups(), groupdict()}`` to the - callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if - needed. - """ - optional_args = super().collect_optional_args(dispatcher, update, check_result) - if self.pattern and not callable(self.pattern): - check_result = cast(Match, check_result) - if self.pass_groups: - optional_args['groups'] = check_result.groups() - if self.pass_groupdict: - optional_args['groupdict'] = check_result.groupdict() - return optional_args - - def collect_additional_context( - self, - context: CCT, - update: Update, - dispatcher: 'Dispatcher', - check_result: Union[bool, Match], - ) -> None: - """Add the result of ``re.match(pattern, update.callback_query.data)`` to - :attr:`CallbackContext.matches` as list with one element. - """ - if self.pattern: - check_result = cast(Match, check_result) - context.matches = [check_result] diff --git a/telegramer/include/telegram/ext/chatjoinrequesthandler.py b/telegramer/include/telegram/ext/chatjoinrequesthandler.py deleted file mode 100644 index aafed54..0000000 --- a/telegramer/include/telegram/ext/chatjoinrequesthandler.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the ChatJoinRequestHandler class.""" - - -from telegram import Update - -from .handler import Handler -from .utils.types import CCT - - -class ChatJoinRequestHandler(Handler[Update, CCT]): - """Handler class to handle Telegram updates that contain a chat join request. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - .. versionadded:: 13.8 - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = () - - def check_update(self, update: object) -> bool: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - return isinstance(update, Update) and bool(update.chat_join_request) diff --git a/telegramer/include/telegram/ext/chatmemberhandler.py b/telegramer/include/telegram/ext/chatmemberhandler.py deleted file mode 100644 index eb9d91b..0000000 --- a/telegramer/include/telegram/ext/chatmemberhandler.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the ChatMemberHandler classes.""" -from typing import ClassVar, TypeVar, Union, Callable - -from telegram import Update -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE -from .handler import Handler -from .utils.types import CCT - -RT = TypeVar('RT') - - -class ChatMemberHandler(Handler[Update, CCT]): - """Handler class to handle Telegram updates that contain a chat member update. - - .. versionadded:: 13.4 - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - chat_member_types (:obj:`int`, optional): Pass one of :attr:`MY_CHAT_MEMBER`, - :attr:`CHAT_MEMBER` or :attr:`ANY_CHAT_MEMBER` to specify if this handler should handle - only updates with :attr:`telegram.Update.my_chat_member`, - :attr:`telegram.Update.chat_member` or both. Defaults to :attr:`MY_CHAT_MEMBER`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - chat_member_types (:obj:`int`, optional): Specifies if this handler should handle - only updates with :attr:`telegram.Update.my_chat_member`, - :attr:`telegram.Update.chat_member` or both. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('chat_member_types',) - MY_CHAT_MEMBER: ClassVar[int] = -1 - """:obj:`int`: Used as a constant to handle only :attr:`telegram.Update.my_chat_member`.""" - CHAT_MEMBER: ClassVar[int] = 0 - """:obj:`int`: Used as a constant to handle only :attr:`telegram.Update.chat_member`.""" - ANY_CHAT_MEMBER: ClassVar[int] = 1 - """:obj:`int`: Used as a constant to handle bot :attr:`telegram.Update.my_chat_member` - and :attr:`telegram.Update.chat_member`.""" - - def __init__( - self, - callback: Callable[[Update, CCT], RT], - chat_member_types: int = MY_CHAT_MEMBER, - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - run_async=run_async, - ) - - self.chat_member_types = chat_member_types - - def check_update(self, update: object) -> bool: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - if isinstance(update, Update): - if not (update.my_chat_member or update.chat_member): - return False - if self.chat_member_types == self.ANY_CHAT_MEMBER: - return True - if self.chat_member_types == self.CHAT_MEMBER: - return bool(update.chat_member) - return bool(update.my_chat_member) - return False diff --git a/telegramer/include/telegram/ext/choseninlineresulthandler.py b/telegramer/include/telegram/ext/choseninlineresulthandler.py deleted file mode 100644 index 1d94b79..0000000 --- a/telegramer/include/telegram/ext/choseninlineresulthandler.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the ChosenInlineResultHandler class.""" -import re -from typing import Optional, TypeVar, Union, Callable, TYPE_CHECKING, Pattern, Match, cast - -from telegram import Update - -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE -from .handler import Handler -from .utils.types import CCT - -RT = TypeVar('RT') - -if TYPE_CHECKING: - from telegram.ext import CallbackContext, Dispatcher - - -class ChosenInlineResultHandler(Handler[Update, CCT]): - """Handler class to handle Telegram updates that contain a chosen inline result. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - pattern (:obj:`str` | `Pattern`, optional): Regex pattern. If not :obj:`None`, ``re.match`` - is used on :attr:`telegram.ChosenInlineResult.result_id` to determine if an update - should be handled by this handler. This is accessible in the callback as - :attr:`telegram.ext.CallbackContext.matches`. - - .. versionadded:: 13.6 - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - pattern (`Pattern`): Optional. Regex pattern to test - :attr:`telegram.ChosenInlineResult.result_id` against. - - .. versionadded:: 13.6 - - """ - - __slots__ = ('pattern',) - - def __init__( - self, - callback: Callable[[Update, 'CallbackContext'], RT], - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - pattern: Union[str, Pattern] = None, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - run_async=run_async, - ) - - if isinstance(pattern, str): - pattern = re.compile(pattern) - - self.pattern = pattern - - def check_update(self, update: object) -> Optional[Union[bool, object]]: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - if isinstance(update, Update) and update.chosen_inline_result: - if self.pattern: - match = re.match(self.pattern, update.chosen_inline_result.result_id) - if match: - return match - else: - return True - return None - - def collect_additional_context( - self, - context: 'CallbackContext', - update: Update, - dispatcher: 'Dispatcher', - check_result: Union[bool, Match], - ) -> None: - """This function adds the matched regex pattern result to - :attr:`telegram.ext.CallbackContext.matches`. - """ - if self.pattern: - check_result = cast(Match, check_result) - context.matches = [check_result] diff --git a/telegramer/include/telegram/ext/commandhandler.py b/telegramer/include/telegram/ext/commandhandler.py deleted file mode 100644 index 6f53d23..0000000 --- a/telegramer/include/telegram/ext/commandhandler.py +++ /dev/null @@ -1,456 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the CommandHandler and PrefixHandler classes.""" -import re -import warnings -from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, TypeVar, Union - -from telegram import MessageEntity, Update -from telegram.ext import BaseFilter, Filters -from telegram.utils.deprecate import TelegramDeprecationWarning -from telegram.utils.types import SLT -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE - -from .utils.types import CCT -from .handler import Handler - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') - - -class CommandHandler(Handler[Update, CCT]): - """Handler class to handle Telegram commands. - - Commands are Telegram messages that start with ``/``, optionally followed by an ``@`` and the - bot's name and/or some additional text. The handler will add a ``list`` to the - :class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings, - which is the text following the command split on single or consecutive whitespace characters. - - By default the handler listens to messages as well as edited messages. To change this behavior - use ``~Filters.update.edited_message`` in the filter argument. - - Note: - * :class:`CommandHandler` does *not* handle (edited) channel posts. - * :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a :obj:`dict` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same - user or in the same chat, it will be the same :obj:`dict`. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - command (:class:`telegram.utils.types.SLT[str]`): - The command or list of commands this handler should listen for. - Limitations are the same as described here https://core.telegram.org/bots#commands - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from - :class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in - :class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise - operators (& for and, | for or, ~ for not). - allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept - edited messages. Default is :obj:`False`. - DEPRECATED: Edited is allowed by default. To change this behavior use - ``~Filters.update.edited_message``. - pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the - arguments passed to the command as a keyword argument called ``args``. It will contain - a list of strings, which is the text following the command split on single or - consecutive whitespace characters. Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Raises: - ValueError: when command is too long or has illegal chars. - - Attributes: - command (:class:`telegram.utils.types.SLT[str]`): - The command or list of commands this handler should listen for. - Limitations are the same as described here https://core.telegram.org/bots#commands - callback (:obj:`callable`): The callback function for this handler. - filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these - Filters. - allow_edited (:obj:`bool`): Determines whether the handler should also accept - edited messages. - pass_args (:obj:`bool`): Determines whether the handler should be passed - ``args``. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - """ - - __slots__ = ('command', 'filters', 'pass_args') - - def __init__( - self, - command: SLT[str], - callback: Callable[[Update, CCT], RT], - filters: BaseFilter = None, - allow_edited: bool = None, - pass_args: bool = False, - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - run_async=run_async, - ) - - if isinstance(command, str): - self.command = [command.lower()] - else: - self.command = [x.lower() for x in command] - for comm in self.command: - if not re.match(r'^[\da-z_]{1,32}$', comm): - raise ValueError('Command is not a valid bot command') - - if filters: - self.filters = Filters.update.messages & filters - else: - self.filters = Filters.update.messages - - if allow_edited is not None: - warnings.warn( - 'allow_edited is deprecated. See https://git.io/fxJuV for more info', - TelegramDeprecationWarning, - stacklevel=2, - ) - if not allow_edited: - self.filters &= ~Filters.update.edited_message - self.pass_args = pass_args - - def check_update( - self, update: object - ) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`list`: The list of args for the handler. - - """ - if isinstance(update, Update) and update.effective_message: - message = update.effective_message - - if ( - message.entities - and message.entities[0].type == MessageEntity.BOT_COMMAND - and message.entities[0].offset == 0 - and message.text - and message.bot - ): - command = message.text[1 : message.entities[0].length] - args = message.text.split()[1:] - command_parts = command.split('@') - command_parts.append(message.bot.username) - - if not ( - command_parts[0].lower() in self.command - and command_parts[1].lower() == message.bot.username.lower() - ): - return None - - filter_result = self.filters(update) - if filter_result: - return args, filter_result - return False - return None - - def collect_optional_args( - self, - dispatcher: 'Dispatcher', - update: Update = None, - check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]] = None, - ) -> Dict[str, object]: - """Provide text after the command to the callback the ``args`` argument as list, split on - single whitespaces. - """ - optional_args = super().collect_optional_args(dispatcher, update) - if self.pass_args and isinstance(check_result, tuple): - optional_args['args'] = check_result[0] - return optional_args - - def collect_additional_context( - self, - context: CCT, - update: Update, - dispatcher: 'Dispatcher', - check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]], - ) -> None: - """Add text after the command to :attr:`CallbackContext.args` as list, split on single - whitespaces and add output of data filters to :attr:`CallbackContext` as well. - """ - if isinstance(check_result, tuple): - context.args = check_result[0] - if isinstance(check_result[1], dict): - context.update(check_result[1]) - - -class PrefixHandler(CommandHandler): - """Handler class to handle custom prefix commands. - - This is a intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`. - It supports configurable commands with the same options as CommandHandler. It will respond to - every combination of :attr:`prefix` and :attr:`command`. It will add a ``list`` to the - :class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings, - which is the text following the command split on single or consecutive whitespace characters. - - Examples: - - Single prefix and command: - - .. code:: python - - PrefixHandler('!', 'test', callback) # will respond to '!test'. - - Multiple prefixes, single command: - - .. code:: python - - PrefixHandler(['!', '#'], 'test', callback) # will respond to '!test' and '#test'. - - Multiple prefixes and commands: - - .. code:: python - - PrefixHandler(['!', '#'], ['test', 'help'], callback) # will respond to '!test', \ - '#test', '!help' and '#help'. - - - By default the handler listens to messages as well as edited messages. To change this behavior - use ``~Filters.update.edited_message``. - - Note: - * :class:`PrefixHandler` does *not* handle (edited) channel posts. - * :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a :obj:`dict` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same - user or in the same chat, it will be the same :obj:`dict`. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - prefix (:class:`telegram.utils.types.SLT[str]`): - The prefix(es) that will precede :attr:`command`. - command (:class:`telegram.utils.types.SLT[str]`): - The command or list of commands this handler should listen for. - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from - :class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in - :class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise - operators (& for and, | for or, ~ for not). - pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the - arguments passed to the command as a keyword argument called ``args``. It will contain - a list of strings, which is the text following the command split on single or - consecutive whitespace characters. Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these - Filters. - pass_args (:obj:`bool`): Determines whether the handler should be passed - ``args``. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - # 'prefix' is a class property, & 'command' is included in the superclass, so they're left out. - __slots__ = ('_prefix', '_command', '_commands') - - def __init__( - self, - prefix: SLT[str], - command: SLT[str], - callback: Callable[[Update, CCT], RT], - filters: BaseFilter = None, - pass_args: bool = False, - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - - self._prefix: List[str] = [] - self._command: List[str] = [] - self._commands: List[str] = [] - - super().__init__( - 'nocommand', - callback, - filters=filters, - allow_edited=None, - pass_args=pass_args, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - run_async=run_async, - ) - - self.prefix = prefix # type: ignore[assignment] - self.command = command # type: ignore[assignment] - self._build_commands() - - @property - def prefix(self) -> List[str]: - """ - The prefixes that will precede :attr:`command`. - - Returns: - List[:obj:`str`] - """ - return self._prefix - - @prefix.setter - def prefix(self, prefix: Union[str, List[str]]) -> None: - if isinstance(prefix, str): - self._prefix = [prefix.lower()] - else: - self._prefix = prefix - self._build_commands() - - @property # type: ignore[override] - def command(self) -> List[str]: # type: ignore[override] - """ - The list of commands this handler should listen for. - - Returns: - List[:obj:`str`] - """ - return self._command - - @command.setter - def command(self, command: Union[str, List[str]]) -> None: - if isinstance(command, str): - self._command = [command.lower()] - else: - self._command = command - self._build_commands() - - def _build_commands(self) -> None: - self._commands = [x.lower() + y.lower() for x in self.prefix for y in self.command] - - def check_update( - self, update: object - ) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`list`: The list of args for the handler. - - """ - if isinstance(update, Update) and update.effective_message: - message = update.effective_message - - if message.text: - text_list = message.text.split() - if text_list[0].lower() not in self._commands: - return None - filter_result = self.filters(update) - if filter_result: - return text_list[1:], filter_result - return False - return None diff --git a/telegramer/include/telegram/ext/contexttypes.py b/telegramer/include/telegram/ext/contexttypes.py deleted file mode 100644 index ee03037..0000000 --- a/telegramer/include/telegram/ext/contexttypes.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=R0201 -"""This module contains the auxiliary class ContextTypes.""" -from typing import Type, Generic, overload, Dict # pylint: disable=W0611 - -from telegram.ext.callbackcontext import CallbackContext -from telegram.ext.utils.types import CCT, UD, CD, BD - - -class ContextTypes(Generic[CCT, UD, CD, BD]): - """ - Convenience class to gather customizable types of the :class:`telegram.ext.CallbackContext` - interface. - - .. versionadded:: 13.6 - - Args: - context (:obj:`type`, optional): Determines the type of the ``context`` argument of all - (error-)handler callbacks and job callbacks. Must be a subclass of - :class:`telegram.ext.CallbackContext`. Defaults to - :class:`telegram.ext.CallbackContext`. - bot_data (:obj:`type`, optional): Determines the type of ``context.bot_data`` of all - (error-)handler callbacks and job callbacks. Defaults to :obj:`dict`. Must support - instantiating without arguments. - chat_data (:obj:`type`, optional): Determines the type of ``context.chat_data`` of all - (error-)handler callbacks and job callbacks. Defaults to :obj:`dict`. Must support - instantiating without arguments. - user_data (:obj:`type`, optional): Determines the type of ``context.user_data`` of all - (error-)handler callbacks and job callbacks. Defaults to :obj:`dict`. Must support - instantiating without arguments. - - """ - - __slots__ = ('_context', '_bot_data', '_chat_data', '_user_data') - - # overload signatures generated with https://git.io/JtJPj - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[Dict, Dict, Dict], Dict, Dict, Dict]', - ): - ... - - @overload - def __init__(self: 'ContextTypes[CCT, Dict, Dict, Dict]', context: Type[CCT]): - ... - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[UD, Dict, Dict], UD, Dict, Dict]', user_data: Type[UD] - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[Dict, CD, Dict], Dict, CD, Dict]', chat_data: Type[CD] - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[Dict, Dict, BD], Dict, Dict, BD]', bot_data: Type[BD] - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CCT, UD, Dict, Dict]', context: Type[CCT], user_data: Type[UD] - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CCT, Dict, CD, Dict]', context: Type[CCT], chat_data: Type[CD] - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CCT, Dict, Dict, BD]', context: Type[CCT], bot_data: Type[BD] - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[UD, CD, Dict], UD, CD, Dict]', - user_data: Type[UD], - chat_data: Type[CD], - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[UD, Dict, BD], UD, Dict, BD]', - user_data: Type[UD], - bot_data: Type[BD], - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[Dict, CD, BD], Dict, CD, BD]', - chat_data: Type[CD], - bot_data: Type[BD], - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CCT, UD, CD, Dict]', - context: Type[CCT], - user_data: Type[UD], - chat_data: Type[CD], - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CCT, UD, Dict, BD]', - context: Type[CCT], - user_data: Type[UD], - bot_data: Type[BD], - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CCT, Dict, CD, BD]', - context: Type[CCT], - chat_data: Type[CD], - bot_data: Type[BD], - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CallbackContext[UD, CD, BD], UD, CD, BD]', - user_data: Type[UD], - chat_data: Type[CD], - bot_data: Type[BD], - ): - ... - - @overload - def __init__( - self: 'ContextTypes[CCT, UD, CD, BD]', - context: Type[CCT], - user_data: Type[UD], - chat_data: Type[CD], - bot_data: Type[BD], - ): - ... - - def __init__( # type: ignore[no-untyped-def] - self, - context=CallbackContext, - bot_data=dict, - chat_data=dict, - user_data=dict, - ): - if not issubclass(context, CallbackContext): - raise ValueError('context must be a subclass of CallbackContext.') - - # We make all those only accessible via properties because we don't currently support - # changing this at runtime, so overriding the attributes doesn't make sense - self._context = context - self._bot_data = bot_data - self._chat_data = chat_data - self._user_data = user_data - - @property - def context(self) -> Type[CCT]: - return self._context - - @property - def bot_data(self) -> Type[BD]: - return self._bot_data - - @property - def chat_data(self) -> Type[CD]: - return self._chat_data - - @property - def user_data(self) -> Type[UD]: - return self._user_data diff --git a/telegramer/include/telegram/ext/conversationhandler.py b/telegramer/include/telegram/ext/conversationhandler.py deleted file mode 100644 index 23edf2f..0000000 --- a/telegramer/include/telegram/ext/conversationhandler.py +++ /dev/null @@ -1,725 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=R0201 -"""This module contains the ConversationHandler.""" - -import logging -import warnings -import functools -import datetime -from threading import Lock -from typing import TYPE_CHECKING, Dict, List, NoReturn, Optional, Union, Tuple, cast, ClassVar - -from telegram import Update -from telegram.ext import ( - BasePersistence, - CallbackContext, - CallbackQueryHandler, - ChosenInlineResultHandler, - DispatcherHandlerStop, - Handler, - InlineQueryHandler, -) -from telegram.ext.utils.promise import Promise -from telegram.ext.utils.types import ConversationDict -from telegram.ext.utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher, Job -CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]] - - -class _ConversationTimeoutContext: - # '__dict__' is not included since this a private class - __slots__ = ('conversation_key', 'update', 'dispatcher', 'callback_context') - - def __init__( - self, - conversation_key: Tuple[int, ...], - update: Update, - dispatcher: 'Dispatcher', - callback_context: Optional[CallbackContext], - ): - self.conversation_key = conversation_key - self.update = update - self.dispatcher = dispatcher - self.callback_context = callback_context - - -class ConversationHandler(Handler[Update, CCT]): - """ - A handler to hold a conversation with a single or multiple users through Telegram updates by - managing four collections of other handlers. - - Note: - ``ConversationHandler`` will only accept updates that are (subclass-)instances of - :class:`telegram.Update`. This is, because depending on the :attr:`per_user` and - :attr:`per_chat` ``ConversationHandler`` relies on - :attr:`telegram.Update.effective_user` and/or :attr:`telegram.Update.effective_chat` in - order to determine which conversation an update should belong to. For ``per_message=True``, - ``ConversationHandler`` uses ``update.callback_query.message.message_id`` when - ``per_chat=True`` and ``update.callback_query.inline_message_id`` when ``per_chat=False``. - For a more detailed explanation, please see our `FAQ`_. - - Finally, ``ConversationHandler``, does *not* handle (edited) channel posts. - - .. _`FAQ`: https://git.io/JtcyU - - The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the - conversation, for example with a :class:`telegram.ext.CommandHandler` or - :class:`telegram.ext.MessageHandler`. - - The second collection, a ``dict`` named :attr:`states`, contains the different conversation - steps and one or more associated handlers that should be used if the user sends a message when - the conversation with them is currently in that state. Here you can also define a state for - :attr:`TIMEOUT` to define the behavior when :attr:`conversation_timeout` is exceeded, and a - state for :attr:`WAITING` to define behavior when a new update is received while the previous - ``@run_async`` decorated handler is not finished. - - The third collection, a ``list`` named :attr:`fallbacks`, is used if the user is currently in a - conversation but the state has either no associated handler or the handler that is associated - to the state is inappropriate for the update, for example if the update contains a command, but - a regular text message is expected. You could use this for a ``/cancel`` command or to let the - user know their message was not recognized. - - To change the state of conversation, the callback function of a handler must return the new - state after responding to the user. If it does not return anything (returning :obj:`None` by - default), the state will not change. If an entry point callback function returns :obj:`None`, - the conversation ends immediately after the execution of this callback function. - To end the conversation, the callback function must return :attr:`END` or ``-1``. To - handle the conversation timeout, use handler :attr:`TIMEOUT` or ``-2``. - Finally, :class:`telegram.ext.DispatcherHandlerStop` can be used in conversations as described - in the corresponding documentation. - - Note: - In each of the described collections of handlers, a handler may in turn be a - :class:`ConversationHandler`. In that case, the nested :class:`ConversationHandler` should - have the attribute :attr:`map_to_parent` which allows to return to the parent conversation - at specified states within the nested conversation. - - Note that the keys in :attr:`map_to_parent` must not appear as keys in :attr:`states` - attribute or else the latter will be ignored. You may map :attr:`END` to one of the parents - states to continue the parent conversation after this has ended or even map a state to - :attr:`END` to end the *parent* conversation from within the nested one. For an example on - nested :class:`ConversationHandler` s, see our `examples`_. - - .. _`examples`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples - - Args: - entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can - trigger the start of the conversation. The first handler which :attr:`check_update` - method returns :obj:`True` will be used. If all return :obj:`False`, the update is not - handled. - states (Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]): A :obj:`dict` that - defines the different states of conversation a user can be in and one or more - associated ``Handler`` objects that should be used in that state. The first handler - which :attr:`check_update` method returns :obj:`True` will be used. - fallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that might be used if - the user is in a conversation, but every handler for their current state returned - :obj:`False` on :attr:`check_update`. The first handler which :attr:`check_update` - method returns :obj:`True` will be used. If all return :obj:`False`, the update is not - handled. - allow_reentry (:obj:`bool`, optional): If set to :obj:`True`, a user that is currently in a - conversation can restart the conversation by triggering one of the entry points. - per_chat (:obj:`bool`, optional): If the conversationkey should contain the Chat's ID. - Default is :obj:`True`. - per_user (:obj:`bool`, optional): If the conversationkey should contain the User's ID. - Default is :obj:`True`. - per_message (:obj:`bool`, optional): If the conversationkey should contain the Message's - ID. Default is :obj:`False`. - conversation_timeout (:obj:`float` | :obj:`datetime.timedelta`, optional): When this - handler is inactive more than this timeout (in seconds), it will be automatically - ended. If this value is 0 or :obj:`None` (default), there will be no timeout. The last - received update and the corresponding ``context`` will be handled by ALL the handler's - who's :attr:`check_update` method returns :obj:`True` that are in the state - :attr:`ConversationHandler.TIMEOUT`. - - Note: - Using `conversation_timeout` with nested conversations is currently not - supported. You can still try to use it, but it will likely behave differently - from what you expect. - - - name (:obj:`str`, optional): The name for this conversationhandler. Required for - persistence. - persistent (:obj:`bool`, optional): If the conversations dict for this handler should be - saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater` - map_to_parent (Dict[:obj:`object`, :obj:`object`], optional): A :obj:`dict` that can be - used to instruct a nested conversationhandler to transition into a mapped state on - its parent conversationhandler in place of a specified nested state. - run_async (:obj:`bool`, optional): Pass :obj:`True` to *override* the - :attr:`Handler.run_async` setting of all handlers (in :attr:`entry_points`, - :attr:`states` and :attr:`fallbacks`). - - Note: - If set to :obj:`True`, you should not pass a handler instance, that needs to be - run synchronously in another context. - - .. versionadded:: 13.2 - - Raises: - ValueError - - Attributes: - persistent (:obj:`bool`): Optional. If the conversations dict for this handler should be - saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater` - run_async (:obj:`bool`): If :obj:`True`, will override the - :attr:`Handler.run_async` setting of all internal handlers on initialization. - - .. versionadded:: 13.2 - - """ - - __slots__ = ( - '_entry_points', - '_states', - '_fallbacks', - '_allow_reentry', - '_per_user', - '_per_chat', - '_per_message', - '_conversation_timeout', - '_name', - 'persistent', - '_persistence', - '_map_to_parent', - 'timeout_jobs', - '_timeout_jobs_lock', - '_conversations', - '_conversations_lock', - 'logger', - ) - - END: ClassVar[int] = -1 - """:obj:`int`: Used as a constant to return when a conversation is ended.""" - TIMEOUT: ClassVar[int] = -2 - """:obj:`int`: Used as a constant to handle state when a conversation is timed out.""" - WAITING: ClassVar[int] = -3 - """:obj:`int`: Used as a constant to handle state when a conversation is still waiting on the - previous ``@run_sync`` decorated running handler to finish.""" - # pylint: disable=W0231 - def __init__( - self, - entry_points: List[Handler[Update, CCT]], - states: Dict[object, List[Handler[Update, CCT]]], - fallbacks: List[Handler[Update, CCT]], - allow_reentry: bool = False, - per_chat: bool = True, - per_user: bool = True, - per_message: bool = False, - conversation_timeout: Union[float, datetime.timedelta] = None, - name: str = None, - persistent: bool = False, - map_to_parent: Dict[object, object] = None, - run_async: bool = False, - ): - self.run_async = run_async - - self._entry_points = entry_points - self._states = states - self._fallbacks = fallbacks - - self._allow_reentry = allow_reentry - self._per_user = per_user - self._per_chat = per_chat - self._per_message = per_message - self._conversation_timeout = conversation_timeout - self._name = name - if persistent and not self.name: - raise ValueError("Conversations can't be persistent when handler is unnamed.") - self.persistent: bool = persistent - self._persistence: Optional[BasePersistence] = None - """:obj:`telegram.ext.BasePersistence`: The persistence used to store conversations. - Set by dispatcher""" - self._map_to_parent = map_to_parent - - self.timeout_jobs: Dict[Tuple[int, ...], 'Job'] = {} - self._timeout_jobs_lock = Lock() - self._conversations: ConversationDict = {} - self._conversations_lock = Lock() - - self.logger = logging.getLogger(__name__) - - if not any((self.per_user, self.per_chat, self.per_message)): - raise ValueError("'per_user', 'per_chat' and 'per_message' can't all be 'False'") - - if self.per_message and not self.per_chat: - warnings.warn( - "If 'per_message=True' is used, 'per_chat=True' should also be used, " - "since message IDs are not globally unique." - ) - - all_handlers: List[Handler] = [] - all_handlers.extend(entry_points) - all_handlers.extend(fallbacks) - - for state_handlers in states.values(): - all_handlers.extend(state_handlers) - - if self.per_message: - for handler in all_handlers: - if not isinstance(handler, CallbackQueryHandler): - warnings.warn( - "If 'per_message=True', all entry points and state handlers" - " must be 'CallbackQueryHandler', since no other handlers " - "have a message context." - ) - break - else: - for handler in all_handlers: - if isinstance(handler, CallbackQueryHandler): - warnings.warn( - "If 'per_message=False', 'CallbackQueryHandler' will not be " - "tracked for every message." - ) - break - - if self.per_chat: - for handler in all_handlers: - if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)): - warnings.warn( - "If 'per_chat=True', 'InlineQueryHandler' can not be used, " - "since inline queries have no chat context." - ) - break - - if self.conversation_timeout: - for handler in all_handlers: - if isinstance(handler, self.__class__): - warnings.warn( - "Using `conversation_timeout` with nested conversations is currently not " - "supported. You can still try to use it, but it will likely behave " - "differently from what you expect." - ) - break - - if self.run_async: - for handler in all_handlers: - handler.run_async = True - - @property - def entry_points(self) -> List[Handler]: - """List[:class:`telegram.ext.Handler`]: A list of ``Handler`` objects that can trigger the - start of the conversation. - """ - return self._entry_points - - @entry_points.setter - def entry_points(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to entry_points after initialization.') - - @property - def states(self) -> Dict[object, List[Handler]]: - """Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]: A :obj:`dict` that - defines the different states of conversation a user can be in and one or more - associated ``Handler`` objects that should be used in that state. - """ - return self._states - - @states.setter - def states(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to states after initialization.') - - @property - def fallbacks(self) -> List[Handler]: - """List[:class:`telegram.ext.Handler`]: A list of handlers that might be used if - the user is in a conversation, but every handler for their current state returned - :obj:`False` on :attr:`check_update`. - """ - return self._fallbacks - - @fallbacks.setter - def fallbacks(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to fallbacks after initialization.') - - @property - def allow_reentry(self) -> bool: - """:obj:`bool`: Determines if a user can restart a conversation with an entry point.""" - return self._allow_reentry - - @allow_reentry.setter - def allow_reentry(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to allow_reentry after initialization.') - - @property - def per_user(self) -> bool: - """:obj:`bool`: If the conversation key should contain the User's ID.""" - return self._per_user - - @per_user.setter - def per_user(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to per_user after initialization.') - - @property - def per_chat(self) -> bool: - """:obj:`bool`: If the conversation key should contain the Chat's ID.""" - return self._per_chat - - @per_chat.setter - def per_chat(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to per_chat after initialization.') - - @property - def per_message(self) -> bool: - """:obj:`bool`: If the conversation key should contain the message's ID.""" - return self._per_message - - @per_message.setter - def per_message(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to per_message after initialization.') - - @property - def conversation_timeout( - self, - ) -> Optional[Union[float, datetime.timedelta]]: - """:obj:`float` | :obj:`datetime.timedelta`: Optional. When this - handler is inactive more than this timeout (in seconds), it will be automatically - ended. - """ - return self._conversation_timeout - - @conversation_timeout.setter - def conversation_timeout(self, value: object) -> NoReturn: - raise ValueError( - 'You can not assign a new value to conversation_timeout after initialization.' - ) - - @property - def name(self) -> Optional[str]: - """:obj:`str`: Optional. The name for this :class:`ConversationHandler`.""" - return self._name - - @name.setter - def name(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to name after initialization.') - - @property - def map_to_parent(self) -> Optional[Dict[object, object]]: - """Dict[:obj:`object`, :obj:`object`]: Optional. A :obj:`dict` that can be - used to instruct a nested :class:`ConversationHandler` to transition into a mapped state on - its parent :class:`ConversationHandler` in place of a specified nested state. - """ - return self._map_to_parent - - @map_to_parent.setter - def map_to_parent(self, value: object) -> NoReturn: - raise ValueError('You can not assign a new value to map_to_parent after initialization.') - - @property - def persistence(self) -> Optional[BasePersistence]: - """The persistence class as provided by the :class:`Dispatcher`.""" - return self._persistence - - @persistence.setter - def persistence(self, persistence: BasePersistence) -> None: - self._persistence = persistence - # Set persistence for nested conversations - for handlers in self.states.values(): - for handler in handlers: - if isinstance(handler, ConversationHandler): - handler.persistence = self.persistence - - @property - def conversations(self) -> ConversationDict: # skipcq: PY-D0003 - return self._conversations - - @conversations.setter - def conversations(self, value: ConversationDict) -> None: - self._conversations = value - # Set conversations for nested conversations - for handlers in self.states.values(): - for handler in handlers: - if isinstance(handler, ConversationHandler) and self.persistence and handler.name: - handler.conversations = self.persistence.get_conversations(handler.name) - - def _get_key(self, update: Update) -> Tuple[int, ...]: - chat = update.effective_chat - user = update.effective_user - - key = [] - - if self.per_chat: - key.append(chat.id) # type: ignore[union-attr] - - if self.per_user and user is not None: - key.append(user.id) - - if self.per_message: - key.append( - update.callback_query.inline_message_id # type: ignore[union-attr] - or update.callback_query.message.message_id # type: ignore[union-attr] - ) - - return tuple(key) - - def _resolve_promise(self, state: Tuple) -> object: - old_state, new_state = state - try: - res = new_state.result(0) - res = res if res is not None else old_state - except Exception as exc: - self.logger.exception("Promise function raised exception") - self.logger.exception("%s", exc) - res = old_state - finally: - if res is None and old_state is None: - res = self.END - return res - - def _schedule_job( - self, - new_state: object, - dispatcher: 'Dispatcher', - update: Update, - context: Optional[CallbackContext], - conversation_key: Tuple[int, ...], - ) -> None: - if new_state != self.END: - try: - # both job_queue & conversation_timeout are checked before calling _schedule_job - j_queue = dispatcher.job_queue - self.timeout_jobs[conversation_key] = j_queue.run_once( # type: ignore[union-attr] - self._trigger_timeout, - self.conversation_timeout, # type: ignore[arg-type] - context=_ConversationTimeoutContext( - conversation_key, update, dispatcher, context - ), - ) - except Exception as exc: - self.logger.exception( - "Failed to schedule timeout job due to the following exception:" - ) - self.logger.exception("%s", exc) - - def check_update(self, update: object) -> CheckUpdateType: # pylint: disable=R0911 - """ - Determines whether an update should be handled by this conversationhandler, and if so in - which state the conversation currently is. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - if not isinstance(update, Update): - return None - # Ignore messages in channels - if update.channel_post or update.edited_channel_post: - return None - if self.per_chat and not update.effective_chat: - return None - if self.per_message and not update.callback_query: - return None - if update.callback_query and self.per_chat and not update.callback_query.message: - return None - - key = self._get_key(update) - with self._conversations_lock: - state = self.conversations.get(key) - - # Resolve promises - if isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], Promise): - self.logger.debug('waiting for promise...') - - # check if promise is finished or not - if state[1].done.wait(0): - res = self._resolve_promise(state) - self._update_state(res, key) - with self._conversations_lock: - state = self.conversations.get(key) - - # if not then handle WAITING state instead - else: - hdlrs = self.states.get(self.WAITING, []) - for hdlr in hdlrs: - check = hdlr.check_update(update) - if check is not None and check is not False: - return key, hdlr, check - return None - - self.logger.debug('selecting conversation %s with state %s', str(key), str(state)) - - handler = None - - # Search entry points for a match - if state is None or self.allow_reentry: - for entry_point in self.entry_points: - check = entry_point.check_update(update) - if check is not None and check is not False: - handler = entry_point - break - - else: - if state is None: - return None - - # Get the handler list for current state, if we didn't find one yet and we're still here - if state is not None and not handler: - handlers = self.states.get(state) - - for candidate in handlers or []: - check = candidate.check_update(update) - if check is not None and check is not False: - handler = candidate - break - - # Find a fallback handler if all other handlers fail - else: - for fallback in self.fallbacks: - check = fallback.check_update(update) - if check is not None and check is not False: - handler = fallback - break - - else: - return None - - return key, handler, check # type: ignore[return-value] - - def handle_update( # type: ignore[override] - self, - update: Update, - dispatcher: 'Dispatcher', - check_result: CheckUpdateType, - context: CallbackContext = None, - ) -> Optional[object]: - """Send the update to the callback for the current state and Handler - - Args: - check_result: The result from check_update. For this handler it's a tuple of key, - handler, and the handler's check result. - update (:class:`telegram.Update`): Incoming telegram update. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update. - context (:class:`telegram.ext.CallbackContext`, optional): The context as provided by - the dispatcher. - - """ - update = cast(Update, update) # for mypy - conversation_key, handler, check_result = check_result # type: ignore[assignment,misc] - raise_dp_handler_stop = False - - with self._timeout_jobs_lock: - # Remove the old timeout job (if present) - timeout_job = self.timeout_jobs.pop(conversation_key, None) - - if timeout_job is not None: - timeout_job.schedule_removal() - try: - new_state = handler.handle_update(update, dispatcher, check_result, context) - except DispatcherHandlerStop as exception: - new_state = exception.state - raise_dp_handler_stop = True - with self._timeout_jobs_lock: - if self.conversation_timeout: - if dispatcher.job_queue is not None: - # Add the new timeout job - if isinstance(new_state, Promise): - new_state.add_done_callback( - functools.partial( - self._schedule_job, - dispatcher=dispatcher, - update=update, - context=context, - conversation_key=conversation_key, - ) - ) - elif new_state != self.END: - self._schedule_job( - new_state, dispatcher, update, context, conversation_key - ) - else: - self.logger.warning( - "Ignoring `conversation_timeout` because the Dispatcher has no JobQueue." - ) - - if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent: - self._update_state(self.END, conversation_key) - if raise_dp_handler_stop: - raise DispatcherHandlerStop(self.map_to_parent.get(new_state)) - return self.map_to_parent.get(new_state) - - self._update_state(new_state, conversation_key) - if raise_dp_handler_stop: - # Don't pass the new state here. If we're in a nested conversation, the parent is - # expecting None as return value. - raise DispatcherHandlerStop() - return None - - def _update_state(self, new_state: object, key: Tuple[int, ...]) -> None: - if new_state == self.END: - with self._conversations_lock: - if key in self.conversations: - # If there is no key in conversations, nothing is done. - del self.conversations[key] - if self.persistent and self.persistence and self.name: - self.persistence.update_conversation(self.name, key, None) - - elif isinstance(new_state, Promise): - with self._conversations_lock: - self.conversations[key] = (self.conversations.get(key), new_state) - if self.persistent and self.persistence and self.name: - self.persistence.update_conversation( - self.name, key, (self.conversations.get(key), new_state) - ) - - elif new_state is not None: - if new_state not in self.states: - warnings.warn( - f"Handler returned state {new_state} which is unknown to the " - f"ConversationHandler{' ' + self.name if self.name is not None else ''}." - ) - with self._conversations_lock: - self.conversations[key] = new_state - if self.persistent and self.persistence and self.name: - self.persistence.update_conversation(self.name, key, new_state) - - def _trigger_timeout(self, context: CallbackContext, job: 'Job' = None) -> None: - self.logger.debug('conversation timeout was triggered!') - - # Backward compatibility with bots that do not use CallbackContext - if isinstance(context, CallbackContext): - job = context.job - ctxt = cast(_ConversationTimeoutContext, job.context) # type: ignore[union-attr] - else: - ctxt = cast(_ConversationTimeoutContext, job.context) - - callback_context = ctxt.callback_context - - with self._timeout_jobs_lock: - found_job = self.timeout_jobs[ctxt.conversation_key] - if found_job is not job: - # The timeout has been cancelled in handle_update - return - del self.timeout_jobs[ctxt.conversation_key] - - handlers = self.states.get(self.TIMEOUT, []) - for handler in handlers: - check = handler.check_update(ctxt.update) - if check is not None and check is not False: - try: - handler.handle_update(ctxt.update, ctxt.dispatcher, check, callback_context) - except DispatcherHandlerStop: - self.logger.warning( - 'DispatcherHandlerStop in TIMEOUT state of ' - 'ConversationHandler has no effect. Ignoring.' - ) - - self._update_state(self.END, ctxt.conversation_key) diff --git a/telegramer/include/telegram/ext/defaults.py b/telegramer/include/telegram/ext/defaults.py deleted file mode 100644 index 1e08255..0000000 --- a/telegramer/include/telegram/ext/defaults.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=R0201 -"""This module contains the class Defaults, which allows to pass default values to Updater.""" -from typing import NoReturn, Optional, Dict, Any - -import pytz - -from telegram.utils.deprecate import set_new_attribute_deprecated -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - - -class Defaults: - """Convenience Class to gather all parameters with a (user defined) default value - - Parameters: - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or URLs in your bot's message. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will - receive a notification with no sound. - disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this - message. - allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the - read timeout from the server (instead of the one specified during creation of the - connection pool). - - Note: - Will *not* be used for :meth:`telegram.Bot.get_updates`! - quote (:obj:`bool`, optional): If set to :obj:`True`, the reply is sent as an actual reply - to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will - be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats. - tzinfo (:obj:`tzinfo`, optional): A timezone to be used for all date(time) inputs - appearing throughout PTB, i.e. if a timezone naive date(time) object is passed - somewhere, it will be assumed to be in ``tzinfo``. Must be a timezone provided by the - ``pytz`` module. Defaults to UTC. - run_async (:obj:`bool`, optional): Default setting for the ``run_async`` parameter of - handlers and error handlers registered through :meth:`Dispatcher.add_handler` and - :meth:`Dispatcher.add_error_handler`. Defaults to :obj:`False`. - """ - - __slots__ = ( - '_timeout', - '_tzinfo', - '_disable_web_page_preview', - '_run_async', - '_quote', - '_disable_notification', - '_allow_sending_without_reply', - '_parse_mode', - '_api_defaults', - '__dict__', - ) - - def __init__( - self, - parse_mode: str = None, - disable_notification: bool = None, - disable_web_page_preview: bool = None, - # Timeout needs special treatment, since the bot methods have two different - # default values for timeout (None and 20s) - timeout: ODVInput[float] = DEFAULT_NONE, - quote: bool = None, - tzinfo: pytz.BaseTzInfo = pytz.utc, - run_async: bool = False, - allow_sending_without_reply: bool = None, - ): - self._parse_mode = parse_mode - self._disable_notification = disable_notification - self._disable_web_page_preview = disable_web_page_preview - self._allow_sending_without_reply = allow_sending_without_reply - self._timeout = timeout - self._quote = quote - self._tzinfo = tzinfo - self._run_async = run_async - - # Gather all defaults that actually have a default value - self._api_defaults = {} - for kwarg in ( - 'parse_mode', - 'explanation_parse_mode', - 'disable_notification', - 'disable_web_page_preview', - 'allow_sending_without_reply', - ): - value = getattr(self, kwarg) - if value not in [None, DEFAULT_NONE]: - self._api_defaults[kwarg] = value - # Special casing, as None is a valid default value - if self._timeout != DEFAULT_NONE: - self._api_defaults['timeout'] = self._timeout - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - @property - def api_defaults(self) -> Dict[str, Any]: # skip-cq: PY-D0003 - return self._api_defaults - - @property - def parse_mode(self) -> Optional[str]: - """:obj:`str`: Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or URLs in your bot's message. - """ - return self._parse_mode - - @parse_mode.setter - def parse_mode(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def explanation_parse_mode(self) -> Optional[str]: - """:obj:`str`: Optional. Alias for :attr:`parse_mode`, used for - the corresponding parameter of :meth:`telegram.Bot.send_poll`. - """ - return self._parse_mode - - @explanation_parse_mode.setter - def explanation_parse_mode(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def disable_notification(self) -> Optional[bool]: - """:obj:`bool`: Optional. Sends the message silently. Users will - receive a notification with no sound. - """ - return self._disable_notification - - @disable_notification.setter - def disable_notification(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def disable_web_page_preview(self) -> Optional[bool]: - """:obj:`bool`: Optional. Disables link previews for links in this - message. - """ - return self._disable_web_page_preview - - @disable_web_page_preview.setter - def disable_web_page_preview(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def allow_sending_without_reply(self) -> Optional[bool]: - """:obj:`bool`: Optional. Pass :obj:`True`, if the message - should be sent even if the specified replied-to message is not found. - """ - return self._allow_sending_without_reply - - @allow_sending_without_reply.setter - def allow_sending_without_reply(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def timeout(self) -> ODVInput[float]: - """:obj:`int` | :obj:`float`: Optional. If this value is specified, use it as the - read timeout from the server (instead of the one specified during creation of the - connection pool). - """ - return self._timeout - - @timeout.setter - def timeout(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def quote(self) -> Optional[bool]: - """:obj:`bool`: Optional. If set to :obj:`True`, the reply is sent as an actual reply - to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will - be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats. - """ - return self._quote - - @quote.setter - def quote(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def tzinfo(self) -> pytz.BaseTzInfo: - """:obj:`tzinfo`: A timezone to be used for all date(time) objects appearing - throughout PTB. - """ - return self._tzinfo - - @tzinfo.setter - def tzinfo(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - @property - def run_async(self) -> bool: - """:obj:`bool`: Optional. Default setting for the ``run_async`` parameter of - handlers and error handlers registered through :meth:`Dispatcher.add_handler` and - :meth:`Dispatcher.add_error_handler`. - """ - return self._run_async - - @run_async.setter - def run_async(self, value: object) -> NoReturn: - raise AttributeError( - "You can not assign a new value to defaults after because it would " - "not have any effect." - ) - - def __hash__(self) -> int: - return hash( - ( - self._parse_mode, - self._disable_notification, - self._disable_web_page_preview, - self._allow_sending_without_reply, - self._timeout, - self._quote, - self._tzinfo, - self._run_async, - ) - ) - - def __eq__(self, other: object) -> bool: - if isinstance(other, Defaults): - return all(getattr(self, attr) == getattr(other, attr) for attr in self.__slots__) - return False - - def __ne__(self, other: object) -> bool: - return not self == other diff --git a/telegramer/include/telegram/ext/dictpersistence.py b/telegramer/include/telegram/ext/dictpersistence.py deleted file mode 100644 index e307123..0000000 --- a/telegramer/include/telegram/ext/dictpersistence.py +++ /dev/null @@ -1,404 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the DictPersistence class.""" - -from typing import DefaultDict, Dict, Optional, Tuple, cast -from collections import defaultdict - -from telegram.utils.helpers import ( - decode_conversations_from_json, - decode_user_chat_data_from_json, - encode_conversations_to_json, -) -from telegram.ext import BasePersistence -from telegram.ext.utils.types import ConversationDict, CDCData - -try: - import ujson as json -except ImportError: - import json # type: ignore[no-redef] - - -class DictPersistence(BasePersistence): - """Using Python's :obj:`dict` and ``json`` for making your bot persistent. - - Note: - This class does *not* implement a :meth:`flush` method, meaning that data managed by - ``DictPersistence`` is in-memory only and will be lost when the bot shuts down. This is, - because ``DictPersistence`` is mainly intended as starting point for custom persistence - classes that need to JSON-serialize the stored data before writing them to file/database. - - Warning: - :class:`DictPersistence` will try to replace :class:`telegram.Bot` instances by - :attr:`REPLACED_BOT` and insert the bot set with - :meth:`telegram.ext.BasePersistence.set_bot` upon loading of the data. This is to ensure - that changes to the bot apply to the saved objects, too. If you change the bots token, this - may lead to e.g. ``Chat not found`` errors. For the limitations on replacing bots see - :meth:`telegram.ext.BasePersistence.replace_bot` and - :meth:`telegram.ext.BasePersistence.insert_bot`. - - Args: - store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this - persistence class. Default is :obj:`True`. - store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this - persistence class. Default is :obj:`True`. - store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this - persistence class. Default is :obj:`True`. - store_callback_data (:obj:`bool`, optional): Whether callback_data should be saved by this - persistence class. Default is :obj:`False`. - - .. versionadded:: 13.6 - user_data_json (:obj:`str`, optional): JSON string that will be used to reconstruct - user_data on creating this persistence. Default is ``""``. - chat_data_json (:obj:`str`, optional): JSON string that will be used to reconstruct - chat_data on creating this persistence. Default is ``""``. - bot_data_json (:obj:`str`, optional): JSON string that will be used to reconstruct - bot_data on creating this persistence. Default is ``""``. - callback_data_json (:obj:`str`, optional): Json string that will be used to reconstruct - callback_data on creating this persistence. Default is ``""``. - - .. versionadded:: 13.6 - conversations_json (:obj:`str`, optional): JSON string that will be used to reconstruct - conversation on creating this persistence. Default is ``""``. - - Attributes: - store_user_data (:obj:`bool`): Whether user_data should be saved by this - persistence class. - store_chat_data (:obj:`bool`): Whether chat_data should be saved by this - persistence class. - store_bot_data (:obj:`bool`): Whether bot_data should be saved by this - persistence class. - store_callback_data (:obj:`bool`): Whether callback_data be saved by this - persistence class. - - .. versionadded:: 13.6 - """ - - __slots__ = ( - '_user_data', - '_chat_data', - '_bot_data', - '_callback_data', - '_conversations', - '_user_data_json', - '_chat_data_json', - '_bot_data_json', - '_callback_data_json', - '_conversations_json', - ) - - def __init__( - self, - store_user_data: bool = True, - store_chat_data: bool = True, - store_bot_data: bool = True, - user_data_json: str = '', - chat_data_json: str = '', - bot_data_json: str = '', - conversations_json: str = '', - store_callback_data: bool = False, - callback_data_json: str = '', - ): - super().__init__( - store_user_data=store_user_data, - store_chat_data=store_chat_data, - store_bot_data=store_bot_data, - store_callback_data=store_callback_data, - ) - self._user_data = None - self._chat_data = None - self._bot_data = None - self._callback_data = None - self._conversations = None - self._user_data_json = None - self._chat_data_json = None - self._bot_data_json = None - self._callback_data_json = None - self._conversations_json = None - if user_data_json: - try: - self._user_data = decode_user_chat_data_from_json(user_data_json) - self._user_data_json = user_data_json - except (ValueError, AttributeError) as exc: - raise TypeError("Unable to deserialize user_data_json. Not valid JSON") from exc - if chat_data_json: - try: - self._chat_data = decode_user_chat_data_from_json(chat_data_json) - self._chat_data_json = chat_data_json - except (ValueError, AttributeError) as exc: - raise TypeError("Unable to deserialize chat_data_json. Not valid JSON") from exc - if bot_data_json: - try: - self._bot_data = json.loads(bot_data_json) - self._bot_data_json = bot_data_json - except (ValueError, AttributeError) as exc: - raise TypeError("Unable to deserialize bot_data_json. Not valid JSON") from exc - if not isinstance(self._bot_data, dict): - raise TypeError("bot_data_json must be serialized dict") - if callback_data_json: - try: - data = json.loads(callback_data_json) - except (ValueError, AttributeError) as exc: - raise TypeError( - "Unable to deserialize callback_data_json. Not valid JSON" - ) from exc - # We are a bit more thorough with the checking of the format here, because it's - # more complicated than for the other things - try: - if data is None: - self._callback_data = None - else: - self._callback_data = cast( - CDCData, - ([(one, float(two), three) for one, two, three in data[0]], data[1]), - ) - self._callback_data_json = callback_data_json - except (ValueError, IndexError) as exc: - raise TypeError("callback_data_json is not in the required format") from exc - if self._callback_data is not None and ( - not all( - isinstance(entry[2], dict) and isinstance(entry[0], str) - for entry in self._callback_data[0] - ) - or not isinstance(self._callback_data[1], dict) - ): - raise TypeError("callback_data_json is not in the required format") - - if conversations_json: - try: - self._conversations = decode_conversations_from_json(conversations_json) - self._conversations_json = conversations_json - except (ValueError, AttributeError) as exc: - raise TypeError( - "Unable to deserialize conversations_json. Not valid JSON" - ) from exc - - @property - def user_data(self) -> Optional[DefaultDict[int, Dict]]: - """:obj:`dict`: The user_data as a dict.""" - return self._user_data - - @property - def user_data_json(self) -> str: - """:obj:`str`: The user_data serialized as a JSON-string.""" - if self._user_data_json: - return self._user_data_json - return json.dumps(self.user_data) - - @property - def chat_data(self) -> Optional[DefaultDict[int, Dict]]: - """:obj:`dict`: The chat_data as a dict.""" - return self._chat_data - - @property - def chat_data_json(self) -> str: - """:obj:`str`: The chat_data serialized as a JSON-string.""" - if self._chat_data_json: - return self._chat_data_json - return json.dumps(self.chat_data) - - @property - def bot_data(self) -> Optional[Dict]: - """:obj:`dict`: The bot_data as a dict.""" - return self._bot_data - - @property - def bot_data_json(self) -> str: - """:obj:`str`: The bot_data serialized as a JSON-string.""" - if self._bot_data_json: - return self._bot_data_json - return json.dumps(self.bot_data) - - @property - def callback_data(self) -> Optional[CDCData]: - """:class:`telegram.ext.utils.types.CDCData`: The meta data on the stored callback data. - - .. versionadded:: 13.6 - """ - return self._callback_data - - @property - def callback_data_json(self) -> str: - """:obj:`str`: The meta data on the stored callback data as a JSON-string. - - .. versionadded:: 13.6 - """ - if self._callback_data_json: - return self._callback_data_json - return json.dumps(self.callback_data) - - @property - def conversations(self) -> Optional[Dict[str, ConversationDict]]: - """:obj:`dict`: The conversations as a dict.""" - return self._conversations - - @property - def conversations_json(self) -> str: - """:obj:`str`: The conversations serialized as a JSON-string.""" - if self._conversations_json: - return self._conversations_json - return encode_conversations_to_json(self.conversations) # type: ignore[arg-type] - - def get_user_data(self) -> DefaultDict[int, Dict[object, object]]: - """Returns the user_data created from the ``user_data_json`` or an empty - :obj:`defaultdict`. - - Returns: - :obj:`defaultdict`: The restored user data. - """ - if self.user_data is None: - self._user_data = defaultdict(dict) - return self.user_data # type: ignore[return-value] - - def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]: - """Returns the chat_data created from the ``chat_data_json`` or an empty - :obj:`defaultdict`. - - Returns: - :obj:`defaultdict`: The restored chat data. - """ - if self.chat_data is None: - self._chat_data = defaultdict(dict) - return self.chat_data # type: ignore[return-value] - - def get_bot_data(self) -> Dict[object, object]: - """Returns the bot_data created from the ``bot_data_json`` or an empty :obj:`dict`. - - Returns: - :obj:`dict`: The restored bot data. - """ - if self.bot_data is None: - self._bot_data = {} - return self.bot_data # type: ignore[return-value] - - def get_callback_data(self) -> Optional[CDCData]: - """Returns the callback_data created from the ``callback_data_json`` or :obj:`None`. - - .. versionadded:: 13.6 - - Returns: - Optional[:class:`telegram.ext.utils.types.CDCData`]: The restored meta data or - :obj:`None`, if no data was stored. - """ - if self.callback_data is None: - self._callback_data = None - return None - return self.callback_data[0], self.callback_data[1].copy() - - def get_conversations(self, name: str) -> ConversationDict: - """Returns the conversations created from the ``conversations_json`` or an empty - :obj:`dict`. - - Returns: - :obj:`dict`: The restored conversations data. - """ - if self.conversations is None: - self._conversations = {} - return self.conversations.get(name, {}).copy() # type: ignore[union-attr] - - def update_conversation( - self, name: str, key: Tuple[int, ...], new_state: Optional[object] - ) -> None: - """Will update the conversations for the given handler. - - Args: - name (:obj:`str`): The handler's name. - key (:obj:`tuple`): The key the state is changed for. - new_state (:obj:`tuple` | :obj:`any`): The new state for the given key. - """ - if not self._conversations: - self._conversations = {} - if self._conversations.setdefault(name, {}).get(key) == new_state: - return - self._conversations[name][key] = new_state - self._conversations_json = None - - def update_user_data(self, user_id: int, data: Dict) -> None: - """Will update the user_data (if changed). - - Args: - user_id (:obj:`int`): The user the data might have been changed for. - data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``. - """ - if self._user_data is None: - self._user_data = defaultdict(dict) - if self._user_data.get(user_id) == data: - return - self._user_data[user_id] = data - self._user_data_json = None - - def update_chat_data(self, chat_id: int, data: Dict) -> None: - """Will update the chat_data (if changed). - - Args: - chat_id (:obj:`int`): The chat the data might have been changed for. - data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``. - """ - if self._chat_data is None: - self._chat_data = defaultdict(dict) - if self._chat_data.get(chat_id) == data: - return - self._chat_data[chat_id] = data - self._chat_data_json = None - - def update_bot_data(self, data: Dict) -> None: - """Will update the bot_data (if changed). - - Args: - data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.bot_data`. - """ - if self._bot_data == data: - return - self._bot_data = data - self._bot_data_json = None - - def update_callback_data(self, data: CDCData) -> None: - """Will update the callback_data (if changed). - - .. versionadded:: 13.6 - - Args: - data (:class:`telegram.ext.utils.types.CDCData`): The relevant data to restore - :class:`telegram.ext.CallbackDataCache`. - """ - if self._callback_data == data: - return - self._callback_data = (data[0], data[1].copy()) - self._callback_data_json = None - - def refresh_user_data(self, user_id: int, user_data: Dict) -> None: - """Does nothing. - - .. versionadded:: 13.6 - .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_user_data` - """ - - def refresh_chat_data(self, chat_id: int, chat_data: Dict) -> None: - """Does nothing. - - .. versionadded:: 13.6 - .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_chat_data` - """ - - def refresh_bot_data(self, bot_data: Dict) -> None: - """Does nothing. - - .. versionadded:: 13.6 - .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_bot_data` - """ diff --git a/telegramer/include/telegram/ext/dispatcher.py b/telegramer/include/telegram/ext/dispatcher.py deleted file mode 100644 index af24188..0000000 --- a/telegramer/include/telegram/ext/dispatcher.py +++ /dev/null @@ -1,820 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the Dispatcher class.""" - -import logging -import warnings -import weakref -from collections import defaultdict -from functools import wraps -from queue import Empty, Queue -from threading import BoundedSemaphore, Event, Lock, Thread, current_thread -from time import sleep -from typing import ( - TYPE_CHECKING, - Callable, - DefaultDict, - Dict, - List, - Optional, - Set, - Union, - Generic, - TypeVar, - overload, - cast, -) -from uuid import uuid4 - -from telegram import TelegramError, Update -from telegram.ext import BasePersistence, ContextTypes -from telegram.ext.callbackcontext import CallbackContext -from telegram.ext.handler import Handler -import telegram.ext.extbot -from telegram.ext.callbackdatacache import CallbackDataCache -from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated -from telegram.ext.utils.promise import Promise -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE -from telegram.ext.utils.types import CCT, UD, CD, BD - -if TYPE_CHECKING: - from telegram import Bot - from telegram.ext import JobQueue - -DEFAULT_GROUP: int = 0 - -UT = TypeVar('UT') - - -def run_async( - func: Callable[[Update, CallbackContext], object] -) -> Callable[[Update, CallbackContext], object]: - """ - Function decorator that will run the function in a new thread. - - Will run :attr:`telegram.ext.Dispatcher.run_async`. - - Using this decorator is only possible when only a single Dispatcher exist in the system. - - Note: - DEPRECATED. Use :attr:`telegram.ext.Dispatcher.run_async` directly instead or the - :attr:`Handler.run_async` parameter. - - Warning: - If you're using ``@run_async`` you cannot rely on adding custom attributes to - :class:`telegram.ext.CallbackContext`. See its docs for more info. - """ - - @wraps(func) - def async_func(*args: object, **kwargs: object) -> object: - warnings.warn( - 'The @run_async decorator is deprecated. Use the `run_async` parameter of ' - 'your Handler or `Dispatcher.run_async` instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - return Dispatcher.get_instance()._run_async( # pylint: disable=W0212 - func, *args, update=None, error_handling=False, **kwargs - ) - - return async_func - - -class DispatcherHandlerStop(Exception): - """ - Raise this in handler to prevent execution of any other handler (even in different group). - - In order to use this exception in a :class:`telegram.ext.ConversationHandler`, pass the - optional ``state`` parameter instead of returning the next state: - - .. code-block:: python - - def callback(update, context): - ... - raise DispatcherHandlerStop(next_state) - - Attributes: - state (:obj:`object`): Optional. The next state of the conversation. - - Args: - state (:obj:`object`, optional): The next state of the conversation. - """ - - __slots__ = ('state',) - - def __init__(self, state: object = None) -> None: - super().__init__() - self.state = state - - -class Dispatcher(Generic[CCT, UD, CD, BD]): - """This class dispatches all kinds of updates to its registered handlers. - - Args: - bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers. - update_queue (:obj:`Queue`): The synchronized queue that will contain the updates. - job_queue (:class:`telegram.ext.JobQueue`, optional): The :class:`telegram.ext.JobQueue` - instance to pass onto handler callbacks. - workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the - ``@run_async`` decorator and :meth:`run_async`. Defaults to 4. - persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to - store data that should be persistent over restarts. - use_context (:obj:`bool`, optional): If set to :obj:`True` uses the context based callback - API (ignored if `dispatcher` argument is used). Defaults to :obj:`True`. - **New users**: set this to :obj:`True`. - context_types (:class:`telegram.ext.ContextTypes`, optional): Pass an instance - of :class:`telegram.ext.ContextTypes` to customize the types used in the - ``context`` interface. If not passed, the defaults documented in - :class:`telegram.ext.ContextTypes` will be used. - - .. versionadded:: 13.6 - - Attributes: - bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers. - update_queue (:obj:`Queue`): The synchronized queue that will contain the updates. - job_queue (:class:`telegram.ext.JobQueue`): Optional. The :class:`telegram.ext.JobQueue` - instance to pass onto handler callbacks. - workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the - ``@run_async`` decorator and :meth:`run_async`. - user_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the user. - chat_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the chat. - bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot. - persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to - store data that should be persistent over restarts. - context_types (:class:`telegram.ext.ContextTypes`): Container for the types used - in the ``context`` interface. - - .. versionadded:: 13.6 - - """ - - # Allowing '__weakref__' creation here since we need it for the singleton - __slots__ = ( - 'workers', - 'persistence', - 'use_context', - 'update_queue', - 'job_queue', - 'user_data', - 'chat_data', - 'bot_data', - '_update_persistence_lock', - 'handlers', - 'groups', - 'error_handlers', - 'running', - '__stop_event', - '__exception_event', - '__async_queue', - '__async_threads', - 'bot', - '__dict__', - '__weakref__', - 'context_types', - ) - - __singleton_lock = Lock() - __singleton_semaphore = BoundedSemaphore() - __singleton = None - logger = logging.getLogger(__name__) - - @overload - def __init__( - self: 'Dispatcher[CallbackContext[Dict, Dict, Dict], Dict, Dict, Dict]', - bot: 'Bot', - update_queue: Queue, - workers: int = 4, - exception_event: Event = None, - job_queue: 'JobQueue' = None, - persistence: BasePersistence = None, - use_context: bool = True, - ): - ... - - @overload - def __init__( - self: 'Dispatcher[CCT, UD, CD, BD]', - bot: 'Bot', - update_queue: Queue, - workers: int = 4, - exception_event: Event = None, - job_queue: 'JobQueue' = None, - persistence: BasePersistence = None, - use_context: bool = True, - context_types: ContextTypes[CCT, UD, CD, BD] = None, - ): - ... - - def __init__( - self, - bot: 'Bot', - update_queue: Queue, - workers: int = 4, - exception_event: Event = None, - job_queue: 'JobQueue' = None, - persistence: BasePersistence = None, - use_context: bool = True, - context_types: ContextTypes[CCT, UD, CD, BD] = None, - ): - self.bot = bot - self.update_queue = update_queue - self.job_queue = job_queue - self.workers = workers - self.use_context = use_context - self.context_types = cast(ContextTypes[CCT, UD, CD, BD], context_types or ContextTypes()) - - if not use_context: - warnings.warn( - 'Old Handler API is deprecated - see https://git.io/fxJuV for details', - TelegramDeprecationWarning, - stacklevel=3, - ) - - if self.workers < 1: - warnings.warn( - 'Asynchronous callbacks can not be processed without at least one worker thread.' - ) - - self.user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data) - self.chat_data: DefaultDict[int, CD] = defaultdict(self.context_types.chat_data) - self.bot_data = self.context_types.bot_data() - self.persistence: Optional[BasePersistence] = None - self._update_persistence_lock = Lock() - if persistence: - if not isinstance(persistence, BasePersistence): - raise TypeError("persistence must be based on telegram.ext.BasePersistence") - self.persistence = persistence - self.persistence.set_bot(self.bot) - if self.persistence.store_user_data: - self.user_data = self.persistence.get_user_data() - if not isinstance(self.user_data, defaultdict): - raise ValueError("user_data must be of type defaultdict") - if self.persistence.store_chat_data: - self.chat_data = self.persistence.get_chat_data() - if not isinstance(self.chat_data, defaultdict): - raise ValueError("chat_data must be of type defaultdict") - if self.persistence.store_bot_data: - self.bot_data = self.persistence.get_bot_data() - if not isinstance(self.bot_data, self.context_types.bot_data): - raise ValueError( - f"bot_data must be of type {self.context_types.bot_data.__name__}" - ) - if self.persistence.store_callback_data: - self.bot = cast(telegram.ext.extbot.ExtBot, self.bot) - persistent_data = self.persistence.get_callback_data() - if persistent_data is not None: - if not isinstance(persistent_data, tuple) and len(persistent_data) != 2: - raise ValueError('callback_data must be a 2-tuple') - self.bot.callback_data_cache = CallbackDataCache( - self.bot, - self.bot.callback_data_cache.maxsize, - persistent_data=persistent_data, - ) - else: - self.persistence = None - - self.handlers: Dict[int, List[Handler]] = {} - """Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group.""" - self.groups: List[int] = [] - """List[:obj:`int`]: A list with all groups.""" - self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {} - """Dict[:obj:`callable`, :obj:`bool`]: A dict, where the keys are error handlers and the - values indicate whether they are to be run asynchronously.""" - - self.running = False - """:obj:`bool`: Indicates if this dispatcher is running.""" - self.__stop_event = Event() - self.__exception_event = exception_event or Event() - self.__async_queue: Queue = Queue() - self.__async_threads: Set[Thread] = set() - - # For backward compatibility, we allow a "singleton" mode for the dispatcher. When there's - # only one instance of Dispatcher, it will be possible to use the `run_async` decorator. - with self.__singleton_lock: - if self.__singleton_semaphore.acquire(blocking=False): # pylint: disable=R1732 - self._set_singleton(self) - else: - self._set_singleton(None) - - def __setattr__(self, key: str, value: object) -> None: - # Mangled names don't automatically apply in __setattr__ (see - # https://docs.python.org/3/tutorial/classes.html#private-variables), so we have to make - # it mangled so they don't raise TelegramDeprecationWarning unnecessarily - if key.startswith('__'): - key = f"_{self.__class__.__name__}{key}" - if issubclass(self.__class__, Dispatcher) and self.__class__ is not Dispatcher: - object.__setattr__(self, key, value) - return - set_new_attribute_deprecated(self, key, value) - - @property - def exception_event(self) -> Event: # skipcq: PY-D0003 - return self.__exception_event - - def _init_async_threads(self, base_name: str, workers: int) -> None: - base_name = f'{base_name}_' if base_name else '' - - for i in range(workers): - thread = Thread(target=self._pooled, name=f'Bot:{self.bot.id}:worker:{base_name}{i}') - self.__async_threads.add(thread) - thread.start() - - @classmethod - def _set_singleton(cls, val: Optional['Dispatcher']) -> None: - cls.logger.debug('Setting singleton dispatcher as %s', val) - cls.__singleton = weakref.ref(val) if val else None - - @classmethod - def get_instance(cls) -> 'Dispatcher': - """Get the singleton instance of this class. - - Returns: - :class:`telegram.ext.Dispatcher` - - Raises: - RuntimeError - - """ - if cls.__singleton is not None: - return cls.__singleton() # type: ignore[return-value] # pylint: disable=not-callable - raise RuntimeError(f'{cls.__name__} not initialized or multiple instances exist') - - def _pooled(self) -> None: - thr_name = current_thread().name - while 1: - promise = self.__async_queue.get() - - # If unpacking fails, the thread pool is being closed from Updater._join_async_threads - if not isinstance(promise, Promise): - self.logger.debug( - "Closing run_async thread %s/%d", thr_name, len(self.__async_threads) - ) - break - - promise.run() - - if not promise.exception: - self.update_persistence(update=promise.update) - continue - - if isinstance(promise.exception, DispatcherHandlerStop): - self.logger.warning( - 'DispatcherHandlerStop is not supported with async functions; func: %s', - promise.pooled_function.__name__, - ) - continue - - # Avoid infinite recursion of error handlers. - if promise.pooled_function in self.error_handlers: - self.logger.error('An uncaught error was raised while handling the error.') - continue - - # Don't perform error handling for a `Promise` with deactivated error handling. This - # should happen only via the deprecated `@run_async` decorator or `Promises` created - # within error handlers - if not promise.error_handling: - self.logger.error('A promise with deactivated error handling raised an error.') - continue - - # If we arrive here, an exception happened in the promise and was neither - # DispatcherHandlerStop nor raised by an error handler. So we can and must handle it - try: - self.dispatch_error(promise.update, promise.exception, promise=promise) - except Exception: - self.logger.exception('An uncaught error was raised while handling the error.') - - def run_async( - self, func: Callable[..., object], *args: object, update: object = None, **kwargs: object - ) -> Promise: - """ - Queue a function (with given args/kwargs) to be run asynchronously. Exceptions raised - by the function will be handled by the error handlers registered with - :meth:`add_error_handler`. - - Warning: - * If you're using ``@run_async``/:meth:`run_async` you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - * Calling a function through :meth:`run_async` from within an error handler can lead to - an infinite error handling loop. - - Args: - func (:obj:`callable`): The function to run in the thread. - *args (:obj:`tuple`, optional): Arguments to ``func``. - update (:class:`telegram.Update` | :obj:`object`, optional): The update associated with - the functions call. If passed, it will be available in the error handlers, in case - an exception is raised by :attr:`func`. - **kwargs (:obj:`dict`, optional): Keyword arguments to ``func``. - - Returns: - Promise - - """ - return self._run_async(func, *args, update=update, error_handling=True, **kwargs) - - def _run_async( - self, - func: Callable[..., object], - *args: object, - update: object = None, - error_handling: bool = True, - **kwargs: object, - ) -> Promise: - # TODO: Remove error_handling parameter once we drop the @run_async decorator - promise = Promise(func, args, kwargs, update=update, error_handling=error_handling) - self.__async_queue.put(promise) - return promise - - def start(self, ready: Event = None) -> None: - """Thread target of thread 'dispatcher'. - - Runs in background and processes the update queue. - - Args: - ready (:obj:`threading.Event`, optional): If specified, the event will be set once the - dispatcher is ready. - - """ - if self.running: - self.logger.warning('already running') - if ready is not None: - ready.set() - return - - if self.__exception_event.is_set(): - msg = 'reusing dispatcher after exception event is forbidden' - self.logger.error(msg) - raise TelegramError(msg) - - self._init_async_threads(str(uuid4()), self.workers) - self.running = True - self.logger.debug('Dispatcher started') - - if ready is not None: - ready.set() - - while 1: - try: - # Pop update from update queue. - update = self.update_queue.get(True, 1) - except Empty: - if self.__stop_event.is_set(): - self.logger.debug('orderly stopping') - break - if self.__exception_event.is_set(): - self.logger.critical('stopping due to exception in another thread') - break - continue - - self.logger.debug('Processing Update: %s', update) - self.process_update(update) - self.update_queue.task_done() - - self.running = False - self.logger.debug('Dispatcher thread stopped') - - def stop(self) -> None: - """Stops the thread.""" - if self.running: - self.__stop_event.set() - while self.running: - sleep(0.1) - self.__stop_event.clear() - - # async threads must be join()ed only after the dispatcher thread was joined, - # otherwise we can still have new async threads dispatched - threads = list(self.__async_threads) - total = len(threads) - - # Stop all threads in the thread pool by put()ting one non-tuple per thread - for i in range(total): - self.__async_queue.put(None) - - for i, thr in enumerate(threads): - self.logger.debug('Waiting for async thread %s/%s to end', i + 1, total) - thr.join() - self.__async_threads.remove(thr) - self.logger.debug('async thread %s/%s has ended', i + 1, total) - - @property - def has_running_threads(self) -> bool: # skipcq: PY-D0003 - return self.running or bool(self.__async_threads) - - def process_update(self, update: object) -> None: - """Processes a single update and updates the persistence. - - Note: - If the update is handled by least one synchronously running handlers (i.e. - ``run_async=False``), :meth:`update_persistence` is called *once* after all handlers - synchronous handlers are done. Each asynchronously running handler will trigger - :meth:`update_persistence` on its own. - - Args: - update (:class:`telegram.Update` | :obj:`object` | \ - :class:`telegram.error.TelegramError`): - The update to process. - - """ - # An error happened while polling - if isinstance(update, TelegramError): - try: - self.dispatch_error(None, update) - except Exception: - self.logger.exception('An uncaught error was raised while handling the error.') - return - - context = None - handled = False - sync_modes = [] - - for group in self.groups: - try: - for handler in self.handlers[group]: - check = handler.check_update(update) - if check is not None and check is not False: - if not context and self.use_context: - context = self.context_types.context.from_update(update, self) - context.refresh_data() - handled = True - sync_modes.append(handler.run_async) - handler.handle_update(update, self, check, context) - break - - # Stop processing with any other handler. - except DispatcherHandlerStop: - self.logger.debug('Stopping further handlers due to DispatcherHandlerStop') - self.update_persistence(update=update) - break - - # Dispatch any error. - except Exception as exc: - try: - self.dispatch_error(update, exc) - except DispatcherHandlerStop: - self.logger.debug('Error handler stopped further handlers') - break - # Errors should not stop the thread. - except Exception: - self.logger.exception('An uncaught error was raised while handling the error.') - - # Update persistence, if handled - handled_only_async = all(sync_modes) - if handled: - # Respect default settings - if all(mode is DEFAULT_FALSE for mode in sync_modes) and self.bot.defaults: - handled_only_async = self.bot.defaults.run_async - # If update was only handled by async handlers, we don't need to update here - if not handled_only_async: - self.update_persistence(update=update) - - def add_handler(self, handler: Handler[UT, CCT], group: int = DEFAULT_GROUP) -> None: - """Register a handler. - - TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of - update with :class:`telegram.ext.DispatcherHandlerStop`. - - A handler must be an instance of a subclass of :class:`telegram.ext.Handler`. All handlers - are organized in groups with a numeric value. The default group is 0. All groups will be - evaluated for handling an update, but only 0 or 1 handler per group will be used. If - :class:`telegram.ext.DispatcherHandlerStop` is raised from one of the handlers, no further - handlers (regardless of the group) will be called. - - The priority/order of handlers is determined as follows: - - * Priority of the group (lower group number == higher priority) - * The first handler in a group which should handle an update (see - :attr:`telegram.ext.Handler.check_update`) will be used. Other handlers from the - group will not be used. The order in which handlers were added to the group defines the - priority. - - Args: - handler (:class:`telegram.ext.Handler`): A Handler instance. - group (:obj:`int`, optional): The group identifier. Default is 0. - - """ - # Unfortunately due to circular imports this has to be here - from .conversationhandler import ConversationHandler # pylint: disable=C0415 - - if not isinstance(handler, Handler): - raise TypeError(f'handler is not an instance of {Handler.__name__}') - if not isinstance(group, int): - raise TypeError('group is not int') - # For some reason MyPy infers the type of handler is here, - # so for now we just ignore all the errors - if ( - isinstance(handler, ConversationHandler) - and handler.persistent # type: ignore[attr-defined] - and handler.name # type: ignore[attr-defined] - ): - if not self.persistence: - raise ValueError( - f"ConversationHandler {handler.name} " # type: ignore[attr-defined] - f"can not be persistent if dispatcher has no persistence" - ) - handler.persistence = self.persistence # type: ignore[attr-defined] - handler.conversations = ( # type: ignore[attr-defined] - self.persistence.get_conversations(handler.name) # type: ignore[attr-defined] - ) - - if group not in self.handlers: - self.handlers[group] = [] - self.groups.append(group) - self.groups = sorted(self.groups) - - self.handlers[group].append(handler) - - def remove_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None: - """Remove a handler from the specified group. - - Args: - handler (:class:`telegram.ext.Handler`): A Handler instance. - group (:obj:`object`, optional): The group identifier. Default is 0. - - """ - if handler in self.handlers[group]: - self.handlers[group].remove(handler) - if not self.handlers[group]: - del self.handlers[group] - self.groups.remove(group) - - def update_persistence(self, update: object = None) -> None: - """Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`. - - Args: - update (:class:`telegram.Update`, optional): The update to process. If passed, only the - corresponding ``user_data`` and ``chat_data`` will be updated. - """ - with self._update_persistence_lock: - self.__update_persistence(update) - - def __update_persistence(self, update: object = None) -> None: - if self.persistence: - # We use list() here in order to decouple chat_ids from self.chat_data, as dict view - # objects will change, when the dict does and we want to loop over chat_ids - chat_ids = list(self.chat_data.keys()) - user_ids = list(self.user_data.keys()) - - if isinstance(update, Update): - if update.effective_chat: - chat_ids = [update.effective_chat.id] - else: - chat_ids = [] - if update.effective_user: - user_ids = [update.effective_user.id] - else: - user_ids = [] - - if self.persistence.store_callback_data: - self.bot = cast(telegram.ext.extbot.ExtBot, self.bot) - try: - self.persistence.update_callback_data( - self.bot.callback_data_cache.persistence_data - ) - except Exception as exc: - try: - self.dispatch_error(update, exc) - except Exception: - message = ( - 'Saving callback data raised an error and an ' - 'uncaught error was raised while handling ' - 'the error with an error_handler' - ) - self.logger.exception(message) - if self.persistence.store_bot_data: - try: - self.persistence.update_bot_data(self.bot_data) - except Exception as exc: - try: - self.dispatch_error(update, exc) - except Exception: - message = ( - 'Saving bot data raised an error and an ' - 'uncaught error was raised while handling ' - 'the error with an error_handler' - ) - self.logger.exception(message) - if self.persistence.store_chat_data: - for chat_id in chat_ids: - try: - self.persistence.update_chat_data(chat_id, self.chat_data[chat_id]) - except Exception as exc: - try: - self.dispatch_error(update, exc) - except Exception: - message = ( - 'Saving chat data raised an error and an ' - 'uncaught error was raised while handling ' - 'the error with an error_handler' - ) - self.logger.exception(message) - if self.persistence.store_user_data: - for user_id in user_ids: - try: - self.persistence.update_user_data(user_id, self.user_data[user_id]) - except Exception as exc: - try: - self.dispatch_error(update, exc) - except Exception: - message = ( - 'Saving user data raised an error and an ' - 'uncaught error was raised while handling ' - 'the error with an error_handler' - ) - self.logger.exception(message) - - def add_error_handler( - self, - callback: Callable[[object, CCT], None], - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, # pylint: disable=W0621 - ) -> None: - """Registers an error handler in the Dispatcher. This handler will receive every error - which happens in your bot. - - Note: - Attempts to add the same callback multiple times will be ignored. - - Warning: - The errors handled within these handlers won't show up in the logger, so you - need to make sure that you reraise the error. - - Args: - callback (:obj:`callable`): The callback function for this error handler. Will be - called when an error is raised. Callback signature for context based API: - - ``def callback(update: object, context: CallbackContext)`` - - The error that happened will be present in context.error. - run_async (:obj:`bool`, optional): Whether this handlers callback should be run - asynchronously using :meth:`run_async`. Defaults to :obj:`False`. - - Note: - See https://git.io/fxJuV for more info about switching to context based API. - """ - if callback in self.error_handlers: - self.logger.debug('The callback is already registered as an error handler. Ignoring.') - return - - if run_async is DEFAULT_FALSE and self.bot.defaults and self.bot.defaults.run_async: - run_async = True - - self.error_handlers[callback] = run_async - - def remove_error_handler(self, callback: Callable[[object, CCT], None]) -> None: - """Removes an error handler. - - Args: - callback (:obj:`callable`): The error handler to remove. - - """ - self.error_handlers.pop(callback, None) - - def dispatch_error( - self, update: Optional[object], error: Exception, promise: Promise = None - ) -> None: - """Dispatches an error. - - Args: - update (:obj:`object` | :class:`telegram.Update`): The update that caused the error. - error (:obj:`Exception`): The error that was raised. - promise (:class:`telegram.utils.Promise`, optional): The promise whose pooled function - raised the error. - - """ - async_args = None if not promise else promise.args - async_kwargs = None if not promise else promise.kwargs - - if self.error_handlers: - for callback, run_async in self.error_handlers.items(): # pylint: disable=W0621 - if self.use_context: - context = self.context_types.context.from_error( - update, error, self, async_args=async_args, async_kwargs=async_kwargs - ) - if run_async: - self.run_async(callback, update, context, update=update) - else: - callback(update, context) - else: - if run_async: - self.run_async(callback, self.bot, update, error, update=update) - else: - callback(self.bot, update, error) - - else: - self.logger.exception( - 'No error handlers are registered, logging exception.', exc_info=error - ) diff --git a/telegramer/include/telegram/ext/extbot.py b/telegramer/include/telegram/ext/extbot.py deleted file mode 100644 index f026191..0000000 --- a/telegramer/include/telegram/ext/extbot.py +++ /dev/null @@ -1,341 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=E0611,E0213,E1102,C0103,E1101,R0913,R0904 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Bot with convenience extensions.""" -from copy import copy -from typing import Union, cast, List, Callable, Optional, Tuple, TypeVar, TYPE_CHECKING, Sequence - -import telegram.bot -from telegram import ( - ReplyMarkup, - Message, - InlineKeyboardMarkup, - Poll, - MessageId, - Update, - Chat, - CallbackQuery, -) - -from telegram.ext.callbackdatacache import CallbackDataCache -from telegram.utils.types import JSONDict, ODVInput, DVInput -from ..utils.helpers import DEFAULT_NONE - -if TYPE_CHECKING: - from telegram import InlineQueryResult, MessageEntity - from telegram.utils.request import Request - from .defaults import Defaults - -HandledTypes = TypeVar('HandledTypes', bound=Union[Message, CallbackQuery, Chat]) - - -class ExtBot(telegram.bot.Bot): - """This object represents a Telegram Bot with convenience extensions. - - Warning: - Not to be confused with :class:`telegram.Bot`. - - For the documentation of the arguments, methods and attributes, please see - :class:`telegram.Bot`. - - .. versionadded:: 13.6 - - Args: - defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to - be used if not set explicitly in the bot methods. - arbitrary_callback_data (:obj:`bool` | :obj:`int`, optional): Whether to - allow arbitrary objects as callback data for :class:`telegram.InlineKeyboardButton`. - Pass an integer to specify the maximum number of objects cached in memory. For more - details, please see our `wiki `_. Defaults to :obj:`False`. - - Attributes: - arbitrary_callback_data (:obj:`bool` | :obj:`int`): Whether this bot instance - allows to use arbitrary objects as callback data for - :class:`telegram.InlineKeyboardButton`. - callback_data_cache (:class:`telegram.ext.CallbackDataCache`): The cache for objects passed - as callback data for :class:`telegram.InlineKeyboardButton`. - - """ - - __slots__ = ('arbitrary_callback_data', 'callback_data_cache') - - # The ext_bot argument is a little hack to get warnings handled correctly. - # It's not very clean, but the warnings will be dropped at some point anyway. - def __setattr__(self, key: str, value: object, ext_bot: bool = True) -> None: - if issubclass(self.__class__, ExtBot) and self.__class__ is not ExtBot: - object.__setattr__(self, key, value) - return - super().__setattr__(key, value, ext_bot=ext_bot) # type: ignore[call-arg] - - def __init__( - self, - token: str, - base_url: str = None, - base_file_url: str = None, - request: 'Request' = None, - private_key: bytes = None, - private_key_password: bytes = None, - defaults: 'Defaults' = None, - arbitrary_callback_data: Union[bool, int] = False, - ): - super().__init__( - token=token, - base_url=base_url, - base_file_url=base_file_url, - request=request, - private_key=private_key, - private_key_password=private_key_password, - ) - # We don't pass this to super().__init__ to avoid the deprecation warning - self.defaults = defaults - - # set up callback_data - if not isinstance(arbitrary_callback_data, bool): - maxsize = cast(int, arbitrary_callback_data) - self.arbitrary_callback_data = True - else: - maxsize = 1024 - self.arbitrary_callback_data = arbitrary_callback_data - self.callback_data_cache: CallbackDataCache = CallbackDataCache(bot=self, maxsize=maxsize) - - def _replace_keyboard(self, reply_markup: Optional[ReplyMarkup]) -> Optional[ReplyMarkup]: - # If the reply_markup is an inline keyboard and we allow arbitrary callback data, let the - # CallbackDataCache build a new keyboard with the data replaced. Otherwise return the input - if isinstance(reply_markup, InlineKeyboardMarkup) and self.arbitrary_callback_data: - return self.callback_data_cache.process_keyboard(reply_markup) - - return reply_markup - - def insert_callback_data(self, update: Update) -> None: - """If this bot allows for arbitrary callback data, this inserts the cached data into all - corresponding buttons within this update. - - Note: - Checks :attr:`telegram.Message.via_bot` and :attr:`telegram.Message.from_user` to check - if the reply markup (if any) was actually sent by this caches bot. If it was not, the - message will be returned unchanged. - - Note that this will fail for channel posts, as :attr:`telegram.Message.from_user` is - :obj:`None` for those! In the corresponding reply markups the callback data will be - replaced by :class:`telegram.ext.InvalidCallbackData`. - - Warning: - *In place*, i.e. the passed :class:`telegram.Message` will be changed! - - Args: - update (:class`telegram.Update`): The update. - - """ - # The only incoming updates that can directly contain a message sent by the bot itself are: - # * CallbackQueries - # * Messages where the pinned_message is sent by the bot - # * Messages where the reply_to_message is sent by the bot - # * Messages where via_bot is the bot - # Finally there is effective_chat.pinned message, but that's only returned in get_chat - if update.callback_query: - self._insert_callback_data(update.callback_query) - # elif instead of if, as effective_message includes callback_query.message - # and that has already been processed - elif update.effective_message: - self._insert_callback_data(update.effective_message) - - def _insert_callback_data(self, obj: HandledTypes) -> HandledTypes: - if not self.arbitrary_callback_data: - return obj - - if isinstance(obj, CallbackQuery): - self.callback_data_cache.process_callback_query(obj) - return obj # type: ignore[return-value] - - if isinstance(obj, Message): - if obj.reply_to_message: - # reply_to_message can't contain further reply_to_messages, so no need to check - self.callback_data_cache.process_message(obj.reply_to_message) - if obj.reply_to_message.pinned_message: - # pinned messages can't contain reply_to_message, no need to check - self.callback_data_cache.process_message(obj.reply_to_message.pinned_message) - if obj.pinned_message: - # pinned messages can't contain reply_to_message, no need to check - self.callback_data_cache.process_message(obj.pinned_message) - - # Finally, handle the message itself - self.callback_data_cache.process_message(message=obj) - return obj # type: ignore[return-value] - - if isinstance(obj, Chat) and obj.pinned_message: - self.callback_data_cache.process_message(obj.pinned_message) - - return obj - - def _message( - self, - endpoint: str, - data: JSONDict, - reply_to_message_id: int = None, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_markup: ReplyMarkup = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> Union[bool, Message]: - # We override this method to call self._replace_keyboard and self._insert_callback_data. - # This covers most methods that have a reply_markup - result = super()._message( - endpoint=endpoint, - data=data, - reply_to_message_id=reply_to_message_id, - disable_notification=disable_notification, - reply_markup=self._replace_keyboard(reply_markup), - allow_sending_without_reply=allow_sending_without_reply, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - if isinstance(result, Message): - self._insert_callback_data(result) - return result - - def get_updates( - self, - offset: int = None, - limit: int = 100, - timeout: float = 0, - read_latency: float = 2.0, - allowed_updates: List[str] = None, - api_kwargs: JSONDict = None, - ) -> List[Update]: - updates = super().get_updates( - offset=offset, - limit=limit, - timeout=timeout, - read_latency=read_latency, - allowed_updates=allowed_updates, - api_kwargs=api_kwargs, - ) - - for update in updates: - self.insert_callback_data(update) - - return updates - - def _effective_inline_results( # pylint: disable=R0201 - self, - results: Union[ - Sequence['InlineQueryResult'], Callable[[int], Optional[Sequence['InlineQueryResult']]] - ], - next_offset: str = None, - current_offset: str = None, - ) -> Tuple[Sequence['InlineQueryResult'], Optional[str]]: - """ - This method is called by Bot.answer_inline_query to build the actual results list. - Overriding this to call self._replace_keyboard suffices - """ - effective_results, next_offset = super()._effective_inline_results( - results=results, next_offset=next_offset, current_offset=current_offset - ) - - # Process arbitrary callback - if not self.arbitrary_callback_data: - return effective_results, next_offset - results = [] - for result in effective_results: - # All currently existingInlineQueryResults have a reply_markup, but future ones - # might not have. Better be save than sorry - if not hasattr(result, 'reply_markup'): - results.append(result) - else: - # We build a new result in case the user wants to use the same object in - # different places - new_result = copy(result) - markup = self._replace_keyboard(result.reply_markup) # type: ignore[attr-defined] - new_result.reply_markup = markup - results.append(new_result) - - return results, next_offset - - def stop_poll( - self, - chat_id: Union[int, str], - message_id: int, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Poll: - # We override this method to call self._replace_keyboard - return super().stop_poll( - chat_id=chat_id, - message_id=message_id, - reply_markup=self._replace_keyboard(reply_markup), - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def copy_message( - self, - chat_id: Union[int, str], - from_chat_id: Union[str, int], - message_id: int, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> MessageId: - # We override this method to call self._replace_keyboard - return super().copy_message( - chat_id=chat_id, - from_chat_id=from_chat_id, - message_id=message_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=self._replace_keyboard(reply_markup), - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def get_chat( - self, - chat_id: Union[str, int], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Chat: - # We override this method to call self._insert_callback_data - result = super().get_chat(chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs) - return self._insert_callback_data(result) - - # updated camelCase aliases - getChat = get_chat - """Alias for :meth:`get_chat`""" - copyMessage = copy_message - """Alias for :meth:`copy_message`""" - getUpdates = get_updates - """Alias for :meth:`get_updates`""" - stopPoll = stop_poll - """Alias for :meth:`stop_poll`""" diff --git a/telegramer/include/telegram/ext/filters.py b/telegramer/include/telegram/ext/filters.py deleted file mode 100644 index 519c73a..0000000 --- a/telegramer/include/telegram/ext/filters.py +++ /dev/null @@ -1,2342 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0112, C0103, W0221 -"""This module contains the Filters for use with the MessageHandler class.""" - -import re -import warnings - -from abc import ABC, abstractmethod -from sys import version_info as py_ver -from threading import Lock -from typing import ( - Dict, - FrozenSet, - List, - Match, - Optional, - Pattern, - Set, - Tuple, - Union, - cast, - NoReturn, -) - -from telegram import Chat, Message, MessageEntity, Update, User - -__all__ = [ - 'Filters', - 'BaseFilter', - 'MessageFilter', - 'UpdateFilter', - 'InvertedFilter', - 'MergedFilter', - 'XORFilter', -] - -from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated -from telegram.utils.types import SLT - -DataDict = Dict[str, list] - - -class BaseFilter(ABC): - """Base class for all Filters. - - Filters subclassing from this class can combined using bitwise operators: - - And: - - >>> (Filters.text & Filters.entity(MENTION)) - - Or: - - >>> (Filters.audio | Filters.video) - - Exclusive Or: - - >>> (Filters.regex('To Be') ^ Filters.regex('Not 2B')) - - Not: - - >>> ~ Filters.command - - Also works with more than two filters: - - >>> (Filters.text & (Filters.entity(URL) | Filters.entity(TEXT_LINK))) - >>> Filters.text & (~ Filters.forwarded) - - Note: - Filters use the same short circuiting logic as python's `and`, `or` and `not`. - This means that for example: - - >>> Filters.regex(r'(a?x)') | Filters.regex(r'(b?x)') - - With ``message.text == x``, will only ever return the matches for the first filter, - since the second one is never evaluated. - - - If you want to create your own filters create a class inheriting from either - :class:`MessageFilter` or :class:`UpdateFilter` and implement a :meth:`filter` method that - returns a boolean: :obj:`True` if the message should be - handled, :obj:`False` otherwise. - Note that the filters work only as class instances, not - actual class objects (so remember to - initialize your filter classes). - - By default the filters name (what will get printed when converted to a string for display) - will be the class name. If you want to overwrite this assign a better name to the :attr:`name` - class variable. - - Attributes: - name (:obj:`str`): Name for this filter. Defaults to the type of filter. - data_filter (:obj:`bool`): Whether this filter is a data filter. A data filter should - return a dict with lists. The dict will be merged with - :class:`telegram.ext.CallbackContext`'s internal dict in most cases - (depends on the handler). - """ - - if py_ver < (3, 7): - __slots__ = ('_name', '_data_filter') - else: - __slots__ = ('_name', '_data_filter', '__dict__') # type: ignore[assignment] - - def __new__(cls, *args: object, **kwargs: object) -> 'BaseFilter': # pylint: disable=W0613 - instance = super().__new__(cls) - instance._name = None - instance._data_filter = False - - return instance - - @abstractmethod - def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]: - ... - - def __and__(self, other: 'BaseFilter') -> 'BaseFilter': - return MergedFilter(self, and_filter=other) - - def __or__(self, other: 'BaseFilter') -> 'BaseFilter': - return MergedFilter(self, or_filter=other) - - def __xor__(self, other: 'BaseFilter') -> 'BaseFilter': - return XORFilter(self, other) - - def __invert__(self) -> 'BaseFilter': - return InvertedFilter(self) - - def __setattr__(self, key: str, value: object) -> None: - # Allow setting custom attributes w/o warning for user defined custom filters. - # To differentiate between a custom and a PTB filter, we use this hacky but - # simple way of checking the module name where the class is defined from. - if ( - issubclass(self.__class__, (UpdateFilter, MessageFilter)) - and self.__class__.__module__ != __name__ - ): # __name__ is telegram.ext.filters - object.__setattr__(self, key, value) - return - set_new_attribute_deprecated(self, key, value) - - @property - def data_filter(self) -> bool: - return self._data_filter - - @data_filter.setter - def data_filter(self, value: bool) -> None: - self._data_filter = value - - @property - def name(self) -> Optional[str]: - return self._name - - @name.setter - def name(self, name: Optional[str]) -> None: - self._name = name # pylint: disable=E0237 - - def __repr__(self) -> str: - # We do this here instead of in a __init__ so filter don't have to call __init__ or super() - if self.name is None: - self.name = self.__class__.__name__ - return self.name - - -class MessageFilter(BaseFilter): - """Base class for all Message Filters. In contrast to :class:`UpdateFilter`, the object passed - to :meth:`filter` is ``update.effective_message``. - - Please see :class:`telegram.ext.filters.BaseFilter` for details on how to create custom - filters. - - Attributes: - name (:obj:`str`): Name for this filter. Defaults to the type of filter. - data_filter (:obj:`bool`): Whether this filter is a data filter. A data filter should - return a dict with lists. The dict will be merged with - :class:`telegram.ext.CallbackContext`'s internal dict in most cases - (depends on the handler). - - """ - - __slots__ = () - - def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]: - return self.filter(update.effective_message) - - @abstractmethod - def filter(self, message: Message) -> Optional[Union[bool, DataDict]]: - """This method must be overwritten. - - Args: - message (:class:`telegram.Message`): The message that is tested. - - Returns: - :obj:`dict` or :obj:`bool` - - """ - - -class UpdateFilter(BaseFilter): - """Base class for all Update Filters. In contrast to :class:`MessageFilter`, the object - passed to :meth:`filter` is ``update``, which allows to create filters like - :attr:`Filters.update.edited_message`. - - Please see :class:`telegram.ext.filters.BaseFilter` for details on how to create custom - filters. - - Attributes: - name (:obj:`str`): Name for this filter. Defaults to the type of filter. - data_filter (:obj:`bool`): Whether this filter is a data filter. A data filter should - return a dict with lists. The dict will be merged with - :class:`telegram.ext.CallbackContext`'s internal dict in most cases - (depends on the handler). - - """ - - __slots__ = () - - def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]: - return self.filter(update) - - @abstractmethod - def filter(self, update: Update) -> Optional[Union[bool, DataDict]]: - """This method must be overwritten. - - Args: - update (:class:`telegram.Update`): The update that is tested. - - Returns: - :obj:`dict` or :obj:`bool`. - - """ - - -class InvertedFilter(UpdateFilter): - """Represents a filter that has been inverted. - - Args: - f: The filter to invert. - - """ - - __slots__ = ('f',) - - def __init__(self, f: BaseFilter): - self.f = f - - def filter(self, update: Update) -> bool: - return not bool(self.f(update)) - - @property - def name(self) -> str: - return f"" - - @name.setter - def name(self, name: str) -> NoReturn: - raise RuntimeError('Cannot set name for InvertedFilter') - - -class MergedFilter(UpdateFilter): - """Represents a filter consisting of two other filters. - - Args: - base_filter: Filter 1 of the merged filter. - and_filter: Optional filter to "and" with base_filter. Mutually exclusive with or_filter. - or_filter: Optional filter to "or" with base_filter. Mutually exclusive with and_filter. - - """ - - __slots__ = ('base_filter', 'and_filter', 'or_filter') - - def __init__( - self, base_filter: BaseFilter, and_filter: BaseFilter = None, or_filter: BaseFilter = None - ): - self.base_filter = base_filter - if self.base_filter.data_filter: - self.data_filter = True - self.and_filter = and_filter - if ( - self.and_filter - and not isinstance(self.and_filter, bool) - and self.and_filter.data_filter - ): - self.data_filter = True - self.or_filter = or_filter - if self.or_filter and not isinstance(self.and_filter, bool) and self.or_filter.data_filter: - self.data_filter = True - - @staticmethod - def _merge(base_output: Union[bool, Dict], comp_output: Union[bool, Dict]) -> DataDict: - base = base_output if isinstance(base_output, dict) else {} - comp = comp_output if isinstance(comp_output, dict) else {} - for k in comp.keys(): - # Make sure comp values are lists - comp_value = comp[k] if isinstance(comp[k], list) else [] - try: - # If base is a list then merge - if isinstance(base[k], list): - base[k] += comp_value - else: - base[k] = [base[k]] + comp_value - except KeyError: - base[k] = comp_value - return base - - def filter(self, update: Update) -> Union[bool, DataDict]: # pylint: disable=R0911 - base_output = self.base_filter(update) - # We need to check if the filters are data filters and if so return the merged data. - # If it's not a data filter or an or_filter but no matches return bool - if self.and_filter: - # And filter needs to short circuit if base is falsey - if base_output: - comp_output = self.and_filter(update) - if comp_output: - if self.data_filter: - merged = self._merge(base_output, comp_output) - if merged: - return merged - return True - elif self.or_filter: - # Or filter needs to short circuit if base is truthey - if base_output: - if self.data_filter: - return base_output - return True - - comp_output = self.or_filter(update) - if comp_output: - if self.data_filter: - return comp_output - return True - return False - - @property - def name(self) -> str: - return ( - f"<{self.base_filter} {'and' if self.and_filter else 'or'} " - f"{self.and_filter or self.or_filter}>" - ) - - @name.setter - def name(self, name: str) -> NoReturn: - raise RuntimeError('Cannot set name for MergedFilter') - - -class XORFilter(UpdateFilter): - """Convenience filter acting as wrapper for :class:`MergedFilter` representing the an XOR gate - for two filters. - - Args: - base_filter: Filter 1 of the merged filter. - xor_filter: Filter 2 of the merged filter. - - """ - - __slots__ = ('base_filter', 'xor_filter', 'merged_filter') - - def __init__(self, base_filter: BaseFilter, xor_filter: BaseFilter): - self.base_filter = base_filter - self.xor_filter = xor_filter - self.merged_filter = (base_filter & ~xor_filter) | (~base_filter & xor_filter) - - def filter(self, update: Update) -> Optional[Union[bool, DataDict]]: - return self.merged_filter(update) - - @property - def name(self) -> str: - return f'<{self.base_filter} xor {self.xor_filter}>' - - @name.setter - def name(self, name: str) -> NoReturn: - raise RuntimeError('Cannot set name for XORFilter') - - -class _DiceEmoji(MessageFilter): - __slots__ = ('emoji',) - - def __init__(self, emoji: str = None, name: str = None): - self.name = f'Filters.dice.{name}' if name else 'Filters.dice' - self.emoji = emoji - - class _DiceValues(MessageFilter): - __slots__ = ('values', 'emoji') - - def __init__( - self, - values: SLT[int], - name: str, - emoji: str = None, - ): - self.values = [values] if isinstance(values, int) else values - self.emoji = emoji - self.name = f'{name}({values})' - - def filter(self, message: Message) -> bool: - if message.dice and message.dice.value in self.values: - if self.emoji: - return message.dice.emoji == self.emoji - return True - return False - - def __call__( # type: ignore[override] - self, update: Union[Update, List[int], Tuple[int]] - ) -> Union[bool, '_DiceValues']: - if isinstance(update, Update): - return self.filter(update.effective_message) - return self._DiceValues(update, self.name, emoji=self.emoji) - - def filter(self, message: Message) -> bool: - if bool(message.dice): - if self.emoji: - return message.dice.emoji == self.emoji - return True - return False - - -class Filters: - """Predefined filters for use as the ``filter`` argument of - :class:`telegram.ext.MessageHandler`. - - Examples: - Use ``MessageHandler(Filters.video, callback_method)`` to filter all video - messages. Use ``MessageHandler(Filters.contact, callback_method)`` for all contacts. etc. - - """ - - __slots__ = ('__dict__',) - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - class _All(MessageFilter): - __slots__ = () - name = 'Filters.all' - - def filter(self, message: Message) -> bool: - return True - - all = _All() - """All Messages.""" - - class _Text(MessageFilter): - __slots__ = () - name = 'Filters.text' - - class _TextStrings(MessageFilter): - __slots__ = ('strings',) - - def __init__(self, strings: Union[List[str], Tuple[str]]): - self.strings = strings - self.name = f'Filters.text({strings})' - - def filter(self, message: Message) -> bool: - if message.text: - return message.text in self.strings - return False - - def __call__( # type: ignore[override] - self, update: Union[Update, List[str], Tuple[str]] - ) -> Union[bool, '_TextStrings']: - if isinstance(update, Update): - return self.filter(update.effective_message) - return self._TextStrings(update) - - def filter(self, message: Message) -> bool: - return bool(message.text) - - text = _Text() - """Text Messages. If a list of strings is passed, it filters messages to only allow those - whose text is appearing in the given list. - - Examples: - To allow any text message, simply use - ``MessageHandler(Filters.text, callback_method)``. - - A simple use case for passing a list is to allow only messages that were sent by a - custom :class:`telegram.ReplyKeyboardMarkup`:: - - buttons = ['Start', 'Settings', 'Back'] - markup = ReplyKeyboardMarkup.from_column(buttons) - ... - MessageHandler(Filters.text(buttons), callback_method) - - Note: - * Dice messages don't have text. If you want to filter either text or dice messages, use - ``Filters.text | Filters.dice``. - * Messages containing a command are accepted by this filter. Use - ``Filters.text & (~Filters.command)``, if you want to filter only text messages without - commands. - - Args: - update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which messages to allow. Only - exact matches are allowed. If not specified, will allow any text message. - """ - - class _Caption(MessageFilter): - __slots__ = () - name = 'Filters.caption' - - class _CaptionStrings(MessageFilter): - __slots__ = ('strings',) - - def __init__(self, strings: Union[List[str], Tuple[str]]): - self.strings = strings - self.name = f'Filters.caption({strings})' - - def filter(self, message: Message) -> bool: - if message.caption: - return message.caption in self.strings - return False - - def __call__( # type: ignore[override] - self, update: Union[Update, List[str], Tuple[str]] - ) -> Union[bool, '_CaptionStrings']: - if isinstance(update, Update): - return self.filter(update.effective_message) - return self._CaptionStrings(update) - - def filter(self, message: Message) -> bool: - return bool(message.caption) - - caption = _Caption() - """Messages with a caption. If a list of strings is passed, it filters messages to only - allow those whose caption is appearing in the given list. - - Examples: - ``MessageHandler(Filters.caption, callback_method)`` - - Args: - update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which captions to allow. Only - exact matches are allowed. If not specified, will allow any message with a caption. - """ - - class _Command(MessageFilter): - __slots__ = () - name = 'Filters.command' - - class _CommandOnlyStart(MessageFilter): - __slots__ = ('only_start',) - - def __init__(self, only_start: bool): - self.only_start = only_start - self.name = f'Filters.command({only_start})' - - def filter(self, message: Message) -> bool: - return bool( - message.entities - and any(e.type == MessageEntity.BOT_COMMAND for e in message.entities) - ) - - def __call__( # type: ignore[override] - self, update: Union[bool, Update] - ) -> Union[bool, '_CommandOnlyStart']: - if isinstance(update, Update): - return self.filter(update.effective_message) - return self._CommandOnlyStart(update) - - def filter(self, message: Message) -> bool: - return bool( - message.entities - and message.entities[0].type == MessageEntity.BOT_COMMAND - and message.entities[0].offset == 0 - ) - - command = _Command() - """ - Messages with a :attr:`telegram.MessageEntity.BOT_COMMAND`. By default only allows - messages `starting` with a bot command. Pass :obj:`False` to also allow messages that contain a - bot command `anywhere` in the text. - - Examples:: - - MessageHandler(Filters.command, command_at_start_callback) - MessageHandler(Filters.command(False), command_anywhere_callback) - - Note: - ``Filters.text`` also accepts messages containing a command. - - Args: - update (:obj:`bool`, optional): Whether to only allow messages that `start` with a bot - command. Defaults to :obj:`True`. - """ - - class regex(MessageFilter): - """ - Filters updates by searching for an occurrence of ``pattern`` in the message text. - The ``re.search()`` function is used to determine whether an update should be filtered. - - Refer to the documentation of the ``re`` module for more information. - - To get the groups and groupdict matched, see :attr:`telegram.ext.CallbackContext.matches`. - - Examples: - Use ``MessageHandler(Filters.regex(r'help'), callback)`` to capture all messages that - contain the word 'help'. You can also use - ``MessageHandler(Filters.regex(re.compile(r'help', re.IGNORECASE)), callback)`` if - you want your pattern to be case insensitive. This approach is recommended - if you need to specify flags on your pattern. - - Note: - Filters use the same short circuiting logic as python's `and`, `or` and `not`. - This means that for example: - - >>> Filters.regex(r'(a?x)') | Filters.regex(r'(b?x)') - - With a message.text of `x`, will only ever return the matches for the first filter, - since the second one is never evaluated. - - Args: - pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - """ - - __slots__ = ('pattern',) - data_filter = True - - def __init__(self, pattern: Union[str, Pattern]): - if isinstance(pattern, str): - pattern = re.compile(pattern) - pattern = cast(Pattern, pattern) - self.pattern: Pattern = pattern - self.name = f'Filters.regex({self.pattern})' - - def filter(self, message: Message) -> Optional[Dict[str, List[Match]]]: - """""" # remove method from docs - if message.text: - match = self.pattern.search(message.text) - if match: - return {'matches': [match]} - return {} - - class caption_regex(MessageFilter): - """ - Filters updates by searching for an occurrence of ``pattern`` in the message caption. - - This filter works similarly to :class:`Filters.regex`, with the only exception being that - it applies to the message caption instead of the text. - - Examples: - Use ``MessageHandler(Filters.photo & Filters.caption_regex(r'help'), callback)`` - to capture all photos with caption containing the word 'help'. - - Note: - This filter will not work on simple text messages, but only on media with caption. - - Args: - pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - """ - - __slots__ = ('pattern',) - data_filter = True - - def __init__(self, pattern: Union[str, Pattern]): - if isinstance(pattern, str): - pattern = re.compile(pattern) - pattern = cast(Pattern, pattern) - self.pattern: Pattern = pattern - self.name = f'Filters.caption_regex({self.pattern})' - - def filter(self, message: Message) -> Optional[Dict[str, List[Match]]]: - """""" # remove method from docs - if message.caption: - match = self.pattern.search(message.caption) - if match: - return {'matches': [match]} - return {} - - class _Reply(MessageFilter): - __slots__ = () - name = 'Filters.reply' - - def filter(self, message: Message) -> bool: - return bool(message.reply_to_message) - - reply = _Reply() - """Messages that are a reply to another message.""" - - class _Audio(MessageFilter): - __slots__ = () - name = 'Filters.audio' - - def filter(self, message: Message) -> bool: - return bool(message.audio) - - audio = _Audio() - """Messages that contain :class:`telegram.Audio`.""" - - class _Document(MessageFilter): - __slots__ = () - name = 'Filters.document' - - class category(MessageFilter): - """Filters documents by their category in the mime-type attribute. - - Note: - This Filter only filters by the mime_type of the document, - it doesn't check the validity of the document. - The user can manipulate the mime-type of a message and - send media with wrong types that don't fit to this handler. - - Example: - Filters.document.category('audio/') returns :obj:`True` for all types - of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'. - """ - - __slots__ = ('_category',) - - def __init__(self, category: Optional[str]): - """Initialize the category you want to filter - - Args: - category (str, optional): category of the media you want to filter - """ - self._category = category - self.name = f"Filters.document.category('{self._category}')" - - def filter(self, message: Message) -> bool: - """""" # remove method from docs - if message.document: - return message.document.mime_type.startswith(self._category) - return False - - application = category('application/') - audio = category('audio/') - image = category('image/') - video = category('video/') - text = category('text/') - - class mime_type(MessageFilter): - """This Filter filters documents by their mime-type attribute - - Note: - This Filter only filters by the mime_type of the document, - it doesn't check the validity of document. - The user can manipulate the mime-type of a message and - send media with wrong types that don't fit to this handler. - - Example: - ``Filters.document.mime_type('audio/mpeg')`` filters all audio in mp3 format. - """ - - __slots__ = ('mimetype',) - - def __init__(self, mimetype: Optional[str]): - self.mimetype = mimetype - self.name = f"Filters.document.mime_type('{self.mimetype}')" - - def filter(self, message: Message) -> bool: - """""" # remove method from docs - if message.document: - return message.document.mime_type == self.mimetype - return False - - apk = mime_type('application/vnd.android.package-archive') - doc = mime_type('application/msword') - docx = mime_type('application/vnd.openxmlformats-officedocument.wordprocessingml.document') - exe = mime_type('application/x-ms-dos-executable') - gif = mime_type('video/mp4') - jpg = mime_type('image/jpeg') - mp3 = mime_type('audio/mpeg') - pdf = mime_type('application/pdf') - py = mime_type('text/x-python') - svg = mime_type('image/svg+xml') - txt = mime_type('text/plain') - targz = mime_type('application/x-compressed-tar') - wav = mime_type('audio/x-wav') - xml = mime_type('application/xml') - zip = mime_type('application/zip') - - class file_extension(MessageFilter): - """This filter filters documents by their file ending/extension. - - Note: - * This Filter only filters by the file ending/extension of the document, - it doesn't check the validity of document. - * The user can manipulate the file extension of a document and - send media with wrong types that don't fit to this handler. - * Case insensitive by default, - you may change this with the flag ``case_sensitive=True``. - * Extension should be passed without leading dot - unless it's a part of the extension. - * Pass :obj:`None` to filter files with no extension, - i.e. without a dot in the filename. - - Example: - * ``Filters.document.file_extension("jpg")`` - filters files with extension ``".jpg"``. - * ``Filters.document.file_extension(".jpg")`` - filters files with extension ``"..jpg"``. - * ``Filters.document.file_extension("Dockerfile", case_sensitive=True)`` - filters files with extension ``".Dockerfile"`` minding the case. - * ``Filters.document.file_extension(None)`` - filters files without a dot in the filename. - """ - - __slots__ = ('_file_extension', 'is_case_sensitive') - - def __init__(self, file_extension: Optional[str], case_sensitive: bool = False): - """Initialize the extension you want to filter. - - Args: - file_extension (:obj:`str` | :obj:`None`): - media file extension you want to filter. - case_sensitive (:obj:bool, optional): - pass :obj:`True` to make the filter case sensitive. - Default: :obj:`False`. - """ - self.is_case_sensitive = case_sensitive - if file_extension is None: - self._file_extension = None - self.name = "Filters.document.file_extension(None)" - elif self.is_case_sensitive: - self._file_extension = f".{file_extension}" - self.name = ( - f"Filters.document.file_extension({file_extension!r}," - " case_sensitive=True)" - ) - else: - self._file_extension = f".{file_extension}".lower() - self.name = f"Filters.document.file_extension({file_extension.lower()!r})" - - def filter(self, message: Message) -> bool: - """""" # remove method from docs - if message.document is None: - return False - if self._file_extension is None: - return "." not in message.document.file_name - if self.is_case_sensitive: - filename = message.document.file_name - else: - filename = message.document.file_name.lower() - return filename.endswith(self._file_extension) - - def filter(self, message: Message) -> bool: - return bool(message.document) - - document = _Document() - """ - Subset for messages containing a document/file. - - Examples: - Use these filters like: ``Filters.document.mp3``, - ``Filters.document.mime_type("text/plain")`` etc. Or use just - ``Filters.document`` for all document messages. - - Attributes: - category: Filters documents by their category in the mime-type attribute - - Note: - This Filter only filters by the mime_type of the document, - it doesn't check the validity of the document. - The user can manipulate the mime-type of a message and - send media with wrong types that don't fit to this handler. - - Example: - ``Filters.document.category('audio/')`` filters all types - of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'. - application: Same as ``Filters.document.category("application")``. - audio: Same as ``Filters.document.category("audio")``. - image: Same as ``Filters.document.category("image")``. - video: Same as ``Filters.document.category("video")``. - text: Same as ``Filters.document.category("text")``. - mime_type: Filters documents by their mime-type attribute - - Note: - This Filter only filters by the mime_type of the document, - it doesn't check the validity of document. - - The user can manipulate the mime-type of a message and - send media with wrong types that don't fit to this handler. - - Example: - ``Filters.document.mime_type('audio/mpeg')`` filters all audio in mp3 format. - apk: Same as ``Filters.document.mime_type("application/vnd.android.package-archive")``. - doc: Same as ``Filters.document.mime_type("application/msword")``. - docx: Same as ``Filters.document.mime_type("application/vnd.openxmlformats-\ -officedocument.wordprocessingml.document")``. - exe: Same as ``Filters.document.mime_type("application/x-ms-dos-executable")``. - gif: Same as ``Filters.document.mime_type("video/mp4")``. - jpg: Same as ``Filters.document.mime_type("image/jpeg")``. - mp3: Same as ``Filters.document.mime_type("audio/mpeg")``. - pdf: Same as ``Filters.document.mime_type("application/pdf")``. - py: Same as ``Filters.document.mime_type("text/x-python")``. - svg: Same as ``Filters.document.mime_type("image/svg+xml")``. - txt: Same as ``Filters.document.mime_type("text/plain")``. - targz: Same as ``Filters.document.mime_type("application/x-compressed-tar")``. - wav: Same as ``Filters.document.mime_type("audio/x-wav")``. - xml: Same as ``Filters.document.mime_type("application/xml")``. - zip: Same as ``Filters.document.mime_type("application/zip")``. - file_extension: This filter filters documents by their file ending/extension. - - Note: - * This Filter only filters by the file ending/extension of the document, - it doesn't check the validity of document. - * The user can manipulate the file extension of a document and - send media with wrong types that don't fit to this handler. - * Case insensitive by default, - you may change this with the flag ``case_sensitive=True``. - * Extension should be passed without leading dot - unless it's a part of the extension. - * Pass :obj:`None` to filter files with no extension, - i.e. without a dot in the filename. - - Example: - * ``Filters.document.file_extension("jpg")`` - filters files with extension ``".jpg"``. - * ``Filters.document.file_extension(".jpg")`` - filters files with extension ``"..jpg"``. - * ``Filters.document.file_extension("Dockerfile", case_sensitive=True)`` - filters files with extension ``".Dockerfile"`` minding the case. - * ``Filters.document.file_extension(None)`` - filters files without a dot in the filename. - """ - - class _Animation(MessageFilter): - __slots__ = () - name = 'Filters.animation' - - def filter(self, message: Message) -> bool: - return bool(message.animation) - - animation = _Animation() - """Messages that contain :class:`telegram.Animation`.""" - - class _Photo(MessageFilter): - __slots__ = () - name = 'Filters.photo' - - def filter(self, message: Message) -> bool: - return bool(message.photo) - - photo = _Photo() - """Messages that contain :class:`telegram.PhotoSize`.""" - - class _Sticker(MessageFilter): - __slots__ = () - name = 'Filters.sticker' - - def filter(self, message: Message) -> bool: - return bool(message.sticker) - - sticker = _Sticker() - """Messages that contain :class:`telegram.Sticker`.""" - - class _Video(MessageFilter): - __slots__ = () - name = 'Filters.video' - - def filter(self, message: Message) -> bool: - return bool(message.video) - - video = _Video() - """Messages that contain :class:`telegram.Video`.""" - - class _Voice(MessageFilter): - __slots__ = () - name = 'Filters.voice' - - def filter(self, message: Message) -> bool: - return bool(message.voice) - - voice = _Voice() - """Messages that contain :class:`telegram.Voice`.""" - - class _VideoNote(MessageFilter): - __slots__ = () - name = 'Filters.video_note' - - def filter(self, message: Message) -> bool: - return bool(message.video_note) - - video_note = _VideoNote() - """Messages that contain :class:`telegram.VideoNote`.""" - - class _Contact(MessageFilter): - __slots__ = () - name = 'Filters.contact' - - def filter(self, message: Message) -> bool: - return bool(message.contact) - - contact = _Contact() - """Messages that contain :class:`telegram.Contact`.""" - - class _Location(MessageFilter): - __slots__ = () - name = 'Filters.location' - - def filter(self, message: Message) -> bool: - return bool(message.location) - - location = _Location() - """Messages that contain :class:`telegram.Location`.""" - - class _Venue(MessageFilter): - __slots__ = () - name = 'Filters.venue' - - def filter(self, message: Message) -> bool: - return bool(message.venue) - - venue = _Venue() - """Messages that contain :class:`telegram.Venue`.""" - - class _StatusUpdate(UpdateFilter): - """Subset for messages containing a status update. - - Examples: - Use these filters like: ``Filters.status_update.new_chat_members`` etc. Or use just - ``Filters.status_update`` for all status update messages. - - """ - - __slots__ = () - - class _NewChatMembers(MessageFilter): - __slots__ = () - name = 'Filters.status_update.new_chat_members' - - def filter(self, message: Message) -> bool: - return bool(message.new_chat_members) - - new_chat_members = _NewChatMembers() - """Messages that contain :attr:`telegram.Message.new_chat_members`.""" - - class _LeftChatMember(MessageFilter): - __slots__ = () - name = 'Filters.status_update.left_chat_member' - - def filter(self, message: Message) -> bool: - return bool(message.left_chat_member) - - left_chat_member = _LeftChatMember() - """Messages that contain :attr:`telegram.Message.left_chat_member`.""" - - class _NewChatTitle(MessageFilter): - __slots__ = () - name = 'Filters.status_update.new_chat_title' - - def filter(self, message: Message) -> bool: - return bool(message.new_chat_title) - - new_chat_title = _NewChatTitle() - """Messages that contain :attr:`telegram.Message.new_chat_title`.""" - - class _NewChatPhoto(MessageFilter): - __slots__ = () - name = 'Filters.status_update.new_chat_photo' - - def filter(self, message: Message) -> bool: - return bool(message.new_chat_photo) - - new_chat_photo = _NewChatPhoto() - """Messages that contain :attr:`telegram.Message.new_chat_photo`.""" - - class _DeleteChatPhoto(MessageFilter): - __slots__ = () - name = 'Filters.status_update.delete_chat_photo' - - def filter(self, message: Message) -> bool: - return bool(message.delete_chat_photo) - - delete_chat_photo = _DeleteChatPhoto() - """Messages that contain :attr:`telegram.Message.delete_chat_photo`.""" - - class _ChatCreated(MessageFilter): - __slots__ = () - name = 'Filters.status_update.chat_created' - - def filter(self, message: Message) -> bool: - return bool( - message.group_chat_created - or message.supergroup_chat_created - or message.channel_chat_created - ) - - chat_created = _ChatCreated() - """Messages that contain :attr:`telegram.Message.group_chat_created`, - :attr: `telegram.Message.supergroup_chat_created` or - :attr: `telegram.Message.channel_chat_created`.""" - - class _MessageAutoDeleteTimerChanged(MessageFilter): - __slots__ = () - name = 'MessageAutoDeleteTimerChanged' - - def filter(self, message: Message) -> bool: - return bool(message.message_auto_delete_timer_changed) - - message_auto_delete_timer_changed = _MessageAutoDeleteTimerChanged() - """Messages that contain :attr:`message_auto_delete_timer_changed`""" - - class _Migrate(MessageFilter): - __slots__ = () - name = 'Filters.status_update.migrate' - - def filter(self, message: Message) -> bool: - return bool(message.migrate_from_chat_id or message.migrate_to_chat_id) - - migrate = _Migrate() - """Messages that contain :attr:`telegram.Message.migrate_from_chat_id` or - :attr:`telegram.Message.migrate_to_chat_id`.""" - - class _PinnedMessage(MessageFilter): - __slots__ = () - name = 'Filters.status_update.pinned_message' - - def filter(self, message: Message) -> bool: - return bool(message.pinned_message) - - pinned_message = _PinnedMessage() - """Messages that contain :attr:`telegram.Message.pinned_message`.""" - - class _ConnectedWebsite(MessageFilter): - __slots__ = () - name = 'Filters.status_update.connected_website' - - def filter(self, message: Message) -> bool: - return bool(message.connected_website) - - connected_website = _ConnectedWebsite() - """Messages that contain :attr:`telegram.Message.connected_website`.""" - - class _ProximityAlertTriggered(MessageFilter): - __slots__ = () - name = 'Filters.status_update.proximity_alert_triggered' - - def filter(self, message: Message) -> bool: - return bool(message.proximity_alert_triggered) - - proximity_alert_triggered = _ProximityAlertTriggered() - """Messages that contain :attr:`telegram.Message.proximity_alert_triggered`.""" - - class _VoiceChatScheduled(MessageFilter): - __slots__ = () - name = 'Filters.status_update.voice_chat_scheduled' - - def filter(self, message: Message) -> bool: - return bool(message.voice_chat_scheduled) - - voice_chat_scheduled = _VoiceChatScheduled() - """Messages that contain :attr:`telegram.Message.voice_chat_scheduled`.""" - - class _VoiceChatStarted(MessageFilter): - __slots__ = () - name = 'Filters.status_update.voice_chat_started' - - def filter(self, message: Message) -> bool: - return bool(message.voice_chat_started) - - voice_chat_started = _VoiceChatStarted() - """Messages that contain :attr:`telegram.Message.voice_chat_started`.""" - - class _VoiceChatEnded(MessageFilter): - __slots__ = () - name = 'Filters.status_update.voice_chat_ended' - - def filter(self, message: Message) -> bool: - return bool(message.voice_chat_ended) - - voice_chat_ended = _VoiceChatEnded() - """Messages that contain :attr:`telegram.Message.voice_chat_ended`.""" - - class _VoiceChatParticipantsInvited(MessageFilter): - __slots__ = () - name = 'Filters.status_update.voice_chat_participants_invited' - - def filter(self, message: Message) -> bool: - return bool(message.voice_chat_participants_invited) - - voice_chat_participants_invited = _VoiceChatParticipantsInvited() - """Messages that contain :attr:`telegram.Message.voice_chat_participants_invited`.""" - - name = 'Filters.status_update' - - def filter(self, message: Update) -> bool: - return bool( - self.new_chat_members(message) - or self.left_chat_member(message) - or self.new_chat_title(message) - or self.new_chat_photo(message) - or self.delete_chat_photo(message) - or self.chat_created(message) - or self.message_auto_delete_timer_changed(message) - or self.migrate(message) - or self.pinned_message(message) - or self.connected_website(message) - or self.proximity_alert_triggered(message) - or self.voice_chat_scheduled(message) - or self.voice_chat_started(message) - or self.voice_chat_ended(message) - or self.voice_chat_participants_invited(message) - ) - - status_update = _StatusUpdate() - """Subset for messages containing a status update. - - Examples: - Use these filters like: ``Filters.status_update.new_chat_members`` etc. Or use just - ``Filters.status_update`` for all status update messages. - - Attributes: - chat_created: Messages that contain - :attr:`telegram.Message.group_chat_created`, - :attr:`telegram.Message.supergroup_chat_created` or - :attr:`telegram.Message.channel_chat_created`. - connected_website: Messages that contain - :attr:`telegram.Message.connected_website`. - delete_chat_photo: Messages that contain - :attr:`telegram.Message.delete_chat_photo`. - left_chat_member: Messages that contain - :attr:`telegram.Message.left_chat_member`. - migrate: Messages that contain - :attr:`telegram.Message.migrate_to_chat_id` or - :attr:`telegram.Message.migrate_from_chat_id`. - new_chat_members: Messages that contain - :attr:`telegram.Message.new_chat_members`. - new_chat_photo: Messages that contain - :attr:`telegram.Message.new_chat_photo`. - new_chat_title: Messages that contain - :attr:`telegram.Message.new_chat_title`. - message_auto_delete_timer_changed: Messages that contain - :attr:`message_auto_delete_timer_changed`. - - .. versionadded:: 13.4 - pinned_message: Messages that contain - :attr:`telegram.Message.pinned_message`. - proximity_alert_triggered: Messages that contain - :attr:`telegram.Message.proximity_alert_triggered`. - voice_chat_scheduled: Messages that contain - :attr:`telegram.Message.voice_chat_scheduled`. - - .. versionadded:: 13.5 - voice_chat_started: Messages that contain - :attr:`telegram.Message.voice_chat_started`. - - .. versionadded:: 13.4 - voice_chat_ended: Messages that contain - :attr:`telegram.Message.voice_chat_ended`. - - .. versionadded:: 13.4 - voice_chat_participants_invited: Messages that contain - :attr:`telegram.Message.voice_chat_participants_invited`. - - .. versionadded:: 13.4 - - """ - - class _Forwarded(MessageFilter): - __slots__ = () - name = 'Filters.forwarded' - - def filter(self, message: Message) -> bool: - return bool(message.forward_date) - - forwarded = _Forwarded() - """Messages that are forwarded.""" - - class _Game(MessageFilter): - __slots__ = () - name = 'Filters.game' - - def filter(self, message: Message) -> bool: - return bool(message.game) - - game = _Game() - """Messages that contain :class:`telegram.Game`.""" - - class entity(MessageFilter): - """ - Filters messages to only allow those which have a :class:`telegram.MessageEntity` - where their `type` matches `entity_type`. - - Examples: - Example ``MessageHandler(Filters.entity("hashtag"), callback_method)`` - - Args: - entity_type: Entity type to check for. All types can be found as constants - in :class:`telegram.MessageEntity`. - - """ - - __slots__ = ('entity_type',) - - def __init__(self, entity_type: str): - self.entity_type = entity_type - self.name = f'Filters.entity({self.entity_type})' - - def filter(self, message: Message) -> bool: - """""" # remove method from docs - return any(entity.type == self.entity_type for entity in message.entities) - - class caption_entity(MessageFilter): - """ - Filters media messages to only allow those which have a :class:`telegram.MessageEntity` - where their `type` matches `entity_type`. - - Examples: - Example ``MessageHandler(Filters.caption_entity("hashtag"), callback_method)`` - - Args: - entity_type: Caption Entity type to check for. All types can be found as constants - in :class:`telegram.MessageEntity`. - - """ - - __slots__ = ('entity_type',) - - def __init__(self, entity_type: str): - self.entity_type = entity_type - self.name = f'Filters.caption_entity({self.entity_type})' - - def filter(self, message: Message) -> bool: - """""" # remove method from docs - return any(entity.type == self.entity_type for entity in message.caption_entities) - - class _Private(MessageFilter): - __slots__ = () - name = 'Filters.private' - - def filter(self, message: Message) -> bool: - warnings.warn( - 'Filters.private is deprecated. Use Filters.chat_type.private instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - return message.chat.type == Chat.PRIVATE - - private = _Private() - """ - Messages sent in a private chat. - - Note: - DEPRECATED. Use - :attr:`telegram.ext.Filters.chat_type.private` instead. - """ - - class _Group(MessageFilter): - __slots__ = () - name = 'Filters.group' - - def filter(self, message: Message) -> bool: - warnings.warn( - 'Filters.group is deprecated. Use Filters.chat_type.groups instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP] - - group = _Group() - """ - Messages sent in a group or a supergroup chat. - - Note: - DEPRECATED. Use - :attr:`telegram.ext.Filters.chat_type.groups` instead. - """ - - class _ChatType(MessageFilter): - __slots__ = () - name = 'Filters.chat_type' - - class _Channel(MessageFilter): - __slots__ = () - name = 'Filters.chat_type.channel' - - def filter(self, message: Message) -> bool: - return message.chat.type == Chat.CHANNEL - - channel = _Channel() - - class _Group(MessageFilter): - __slots__ = () - name = 'Filters.chat_type.group' - - def filter(self, message: Message) -> bool: - return message.chat.type == Chat.GROUP - - group = _Group() - - class _SuperGroup(MessageFilter): - __slots__ = () - name = 'Filters.chat_type.supergroup' - - def filter(self, message: Message) -> bool: - return message.chat.type == Chat.SUPERGROUP - - supergroup = _SuperGroup() - - class _Groups(MessageFilter): - __slots__ = () - name = 'Filters.chat_type.groups' - - def filter(self, message: Message) -> bool: - return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP] - - groups = _Groups() - - class _Private(MessageFilter): - __slots__ = () - name = 'Filters.chat_type.private' - - def filter(self, message: Message) -> bool: - return message.chat.type == Chat.PRIVATE - - private = _Private() - - def filter(self, message: Message) -> bool: - return bool(message.chat.type) - - chat_type = _ChatType() - """Subset for filtering the type of chat. - - Examples: - Use these filters like: ``Filters.chat_type.channel`` or - ``Filters.chat_type.supergroup`` etc. Or use just ``Filters.chat_type`` for all - chat types. - - Attributes: - channel: Updates from channel - group: Updates from group - supergroup: Updates from supergroup - groups: Updates from group *or* supergroup - private: Updates sent in private chat - """ - - class _ChatUserBaseFilter(MessageFilter, ABC): - __slots__ = ( - 'chat_id_name', - 'username_name', - 'allow_empty', - '__lock', - '_chat_ids', - '_usernames', - ) - - def __init__( - self, - chat_id: SLT[int] = None, - username: SLT[str] = None, - allow_empty: bool = False, - ): - self.chat_id_name = 'chat_id' - self.username_name = 'username' - self.allow_empty = allow_empty - self.__lock = Lock() - - self._chat_ids: Set[int] = set() - self._usernames: Set[str] = set() - - self._set_chat_ids(chat_id) - self._set_usernames(username) - - @abstractmethod - def get_chat_or_user(self, message: Message) -> Union[Chat, User, None]: - ... - - @staticmethod - def _parse_chat_id(chat_id: SLT[int]) -> Set[int]: - if chat_id is None: - return set() - if isinstance(chat_id, int): - return {chat_id} - return set(chat_id) - - @staticmethod - def _parse_username(username: SLT[str]) -> Set[str]: - if username is None: - return set() - if isinstance(username, str): - return {username[1:] if username.startswith('@') else username} - return {chat[1:] if chat.startswith('@') else chat for chat in username} - - def _set_chat_ids(self, chat_id: SLT[int]) -> None: - with self.__lock: - if chat_id and self._usernames: - raise RuntimeError( - f"Can't set {self.chat_id_name} in conjunction with (already set) " - f"{self.username_name}s." - ) - self._chat_ids = self._parse_chat_id(chat_id) - - def _set_usernames(self, username: SLT[str]) -> None: - with self.__lock: - if username and self._chat_ids: - raise RuntimeError( - f"Can't set {self.username_name} in conjunction with (already set) " - f"{self.chat_id_name}s." - ) - self._usernames = self._parse_username(username) - - @property - def chat_ids(self) -> FrozenSet[int]: - with self.__lock: - return frozenset(self._chat_ids) - - @chat_ids.setter - def chat_ids(self, chat_id: SLT[int]) -> None: - self._set_chat_ids(chat_id) - - @property - def usernames(self) -> FrozenSet[str]: - with self.__lock: - return frozenset(self._usernames) - - @usernames.setter - def usernames(self, username: SLT[str]) -> None: - self._set_usernames(username) - - def add_usernames(self, username: SLT[str]) -> None: - with self.__lock: - if self._chat_ids: - raise RuntimeError( - f"Can't set {self.username_name} in conjunction with (already set) " - f"{self.chat_id_name}s." - ) - - parsed_username = self._parse_username(username) - self._usernames |= parsed_username - - def add_chat_ids(self, chat_id: SLT[int]) -> None: - with self.__lock: - if self._usernames: - raise RuntimeError( - f"Can't set {self.chat_id_name} in conjunction with (already set) " - f"{self.username_name}s." - ) - - parsed_chat_id = self._parse_chat_id(chat_id) - - self._chat_ids |= parsed_chat_id - - def remove_usernames(self, username: SLT[str]) -> None: - with self.__lock: - if self._chat_ids: - raise RuntimeError( - f"Can't set {self.username_name} in conjunction with (already set) " - f"{self.chat_id_name}s." - ) - - parsed_username = self._parse_username(username) - self._usernames -= parsed_username - - def remove_chat_ids(self, chat_id: SLT[int]) -> None: - with self.__lock: - if self._usernames: - raise RuntimeError( - f"Can't set {self.chat_id_name} in conjunction with (already set) " - f"{self.username_name}s." - ) - parsed_chat_id = self._parse_chat_id(chat_id) - self._chat_ids -= parsed_chat_id - - def filter(self, message: Message) -> bool: - """""" # remove method from docs - chat_or_user = self.get_chat_or_user(message) - if chat_or_user: - if self.chat_ids: - return chat_or_user.id in self.chat_ids - if self.usernames: - return bool(chat_or_user.username and chat_or_user.username in self.usernames) - return self.allow_empty - return False - - @property - def name(self) -> str: - return ( - f'Filters.{self.__class__.__name__}(' - f'{", ".join(str(s) for s in (self.usernames or self.chat_ids))})' - ) - - @name.setter - def name(self, name: str) -> NoReturn: - raise RuntimeError(f'Cannot set name for Filters.{self.__class__.__name__}') - - class user(_ChatUserBaseFilter): - # pylint: disable=W0235 - """Filters messages to allow only those which are from specified user ID(s) or - username(s). - - Examples: - ``MessageHandler(Filters.user(1234), callback_method)`` - - Warning: - :attr:`user_ids` will give a *copy* of the saved user ids as :class:`frozenset`. This - is to ensure thread safety. To add/remove a user, you should use :meth:`add_usernames`, - :meth:`add_user_ids`, :meth:`remove_usernames` and :meth:`remove_user_ids`. Only update - the entire set by ``filter.user_ids/usernames = new_set``, if you are entirely sure - that it is not causing race conditions, as this will complete replace the current set - of allowed users. - - Args: - user_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which user ID(s) to allow through. - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. Leading ``'@'`` s in usernames will be - discarded. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no user - is specified in :attr:`user_ids` and :attr:`usernames`. Defaults to :obj:`False` - - Raises: - RuntimeError: If user_id and username are both present. - - Attributes: - user_ids(set(:obj:`int`), optional): Which user ID(s) to allow through. - usernames(set(:obj:`str`), optional): Which username(s) (without leading ``'@'``) to - allow through. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no user - is specified in :attr:`user_ids` and :attr:`usernames`. - - """ - - __slots__ = () - - def __init__( - self, - user_id: SLT[int] = None, - username: SLT[str] = None, - allow_empty: bool = False, - ): - super().__init__(chat_id=user_id, username=username, allow_empty=allow_empty) - self.chat_id_name = 'user_id' - - def get_chat_or_user(self, message: Message) -> Optional[User]: - return message.from_user - - @property - def user_ids(self) -> FrozenSet[int]: - return self.chat_ids - - @user_ids.setter - def user_ids(self, user_id: SLT[int]) -> None: - self.chat_ids = user_id # type: ignore[assignment] - - def add_usernames(self, username: SLT[str]) -> None: - """ - Add one or more users to the allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().add_usernames(username) - - def add_user_ids(self, user_id: SLT[int]) -> None: - """ - Add one or more users to the allowed user ids. - - Args: - user_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which user ID(s) to allow through. - """ - return super().add_chat_ids(user_id) - - def remove_usernames(self, username: SLT[str]) -> None: - """ - Remove one or more users from allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to disallow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().remove_usernames(username) - - def remove_user_ids(self, user_id: SLT[int]) -> None: - """ - Remove one or more users from allowed user ids. - - Args: - user_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which user ID(s) to disallow through. - """ - return super().remove_chat_ids(user_id) - - class via_bot(_ChatUserBaseFilter): - # pylint: disable=W0235 - """Filters messages to allow only those which are from specified via_bot ID(s) or - username(s). - - Examples: - ``MessageHandler(Filters.via_bot(1234), callback_method)`` - - Warning: - :attr:`bot_ids` will give a *copy* of the saved bot ids as :class:`frozenset`. This - is to ensure thread safety. To add/remove a bot, you should use :meth:`add_usernames`, - :meth:`add_bot_ids`, :meth:`remove_usernames` and :meth:`remove_bot_ids`. Only update - the entire set by ``filter.bot_ids/usernames = new_set``, if you are entirely sure - that it is not causing race conditions, as this will complete replace the current set - of allowed bots. - - Args: - bot_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which bot ID(s) to allow through. - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. Leading ``'@'`` s in usernames will be - discarded. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no user - is specified in :attr:`bot_ids` and :attr:`usernames`. Defaults to :obj:`False` - - Raises: - RuntimeError: If bot_id and username are both present. - - Attributes: - bot_ids(set(:obj:`int`), optional): Which bot ID(s) to allow through. - usernames(set(:obj:`str`), optional): Which username(s) (without leading ``'@'``) to - allow through. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no bot - is specified in :attr:`bot_ids` and :attr:`usernames`. - - """ - - __slots__ = () - - def __init__( - self, - bot_id: SLT[int] = None, - username: SLT[str] = None, - allow_empty: bool = False, - ): - super().__init__(chat_id=bot_id, username=username, allow_empty=allow_empty) - self.chat_id_name = 'bot_id' - - def get_chat_or_user(self, message: Message) -> Optional[User]: - return message.via_bot - - @property - def bot_ids(self) -> FrozenSet[int]: - return self.chat_ids - - @bot_ids.setter - def bot_ids(self, bot_id: SLT[int]) -> None: - self.chat_ids = bot_id # type: ignore[assignment] - - def add_usernames(self, username: SLT[str]) -> None: - """ - Add one or more users to the allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().add_usernames(username) - - def add_bot_ids(self, bot_id: SLT[int]) -> None: - """ - - Add one or more users to the allowed user ids. - - Args: - bot_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which bot ID(s) to allow through. - """ - return super().add_chat_ids(bot_id) - - def remove_usernames(self, username: SLT[str]) -> None: - """ - Remove one or more users from allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to disallow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().remove_usernames(username) - - def remove_bot_ids(self, bot_id: SLT[int]) -> None: - """ - Remove one or more users from allowed user ids. - - Args: - bot_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which bot ID(s) to disallow through. - """ - return super().remove_chat_ids(bot_id) - - class chat(_ChatUserBaseFilter): - # pylint: disable=W0235 - """Filters messages to allow only those which are from a specified chat ID or username. - - Examples: - ``MessageHandler(Filters.chat(-1234), callback_method)`` - - Warning: - :attr:`chat_ids` will give a *copy* of the saved chat ids as :class:`frozenset`. This - is to ensure thread safety. To add/remove a chat, you should use :meth:`add_usernames`, - :meth:`add_chat_ids`, :meth:`remove_usernames` and :meth:`remove_chat_ids`. Only update - the entire set by ``filter.chat_ids/usernames = new_set``, if you are entirely sure - that it is not causing race conditions, as this will complete replace the current set - of allowed chats. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which chat ID(s) to allow through. - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. - Leading ``'@'`` s in usernames will be discarded. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat - is specified in :attr:`chat_ids` and :attr:`usernames`. Defaults to :obj:`False` - - Raises: - RuntimeError: If chat_id and username are both present. - - Attributes: - chat_ids(set(:obj:`int`), optional): Which chat ID(s) to allow through. - usernames(set(:obj:`str`), optional): Which username(s) (without leading ``'@'``) to - allow through. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat - is specified in :attr:`chat_ids` and :attr:`usernames`. - - """ - - __slots__ = () - - def get_chat_or_user(self, message: Message) -> Optional[Chat]: - return message.chat - - def add_usernames(self, username: SLT[str]) -> None: - """ - Add one or more chats to the allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().add_usernames(username) - - def add_chat_ids(self, chat_id: SLT[int]) -> None: - """ - Add one or more chats to the allowed chat ids. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which chat ID(s) to allow through. - """ - return super().add_chat_ids(chat_id) - - def remove_usernames(self, username: SLT[str]) -> None: - """ - Remove one or more chats from allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to disallow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().remove_usernames(username) - - def remove_chat_ids(self, chat_id: SLT[int]) -> None: - """ - Remove one or more chats from allowed chat ids. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which chat ID(s) to disallow through. - """ - return super().remove_chat_ids(chat_id) - - class forwarded_from(_ChatUserBaseFilter): - # pylint: disable=W0235 - """Filters messages to allow only those which are forwarded from the specified chat ID(s) - or username(s) based on :attr:`telegram.Message.forward_from` and - :attr:`telegram.Message.forward_from_chat`. - - .. versionadded:: 13.5 - - Examples: - ``MessageHandler(Filters.forwarded_from(chat_id=1234), callback_method)`` - - Note: - When a user has disallowed adding a link to their account while forwarding their - messages, this filter will *not* work since both - :attr:`telegram.Message.forwarded_from` and - :attr:`telegram.Message.forwarded_from_chat` are :obj:`None`. However, this behaviour - is undocumented and might be changed by Telegram. - - Warning: - :attr:`chat_ids` will give a *copy* of the saved chat ids as :class:`frozenset`. This - is to ensure thread safety. To add/remove a chat, you should use :meth:`add_usernames`, - :meth:`add_chat_ids`, :meth:`remove_usernames` and :meth:`remove_chat_ids`. Only update - the entire set by ``filter.chat_ids/usernames = new_set``, if you are entirely sure - that it is not causing race conditions, as this will complete replace the current set - of allowed chats. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which chat/user ID(s) to allow through. - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. Leading ``'@'`` s in usernames will be - discarded. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat - is specified in :attr:`chat_ids` and :attr:`usernames`. Defaults to :obj:`False`. - - Raises: - RuntimeError: If both chat_id and username are present. - - Attributes: - chat_ids(set(:obj:`int`), optional): Which chat/user ID(s) to allow through. - usernames(set(:obj:`str`), optional): Which username(s) (without leading ``'@'``) to - allow through. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat - is specified in :attr:`chat_ids` and :attr:`usernames`. - """ - - __slots__ = () - - def get_chat_or_user(self, message: Message) -> Union[User, Chat, None]: - return message.forward_from or message.forward_from_chat - - def add_usernames(self, username: SLT[str]) -> None: - """ - Add one or more chats to the allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to allow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().add_usernames(username) - - def add_chat_ids(self, chat_id: SLT[int]) -> None: - """ - Add one or more chats to the allowed chat ids. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which chat/user ID(s) to allow through. - """ - return super().add_chat_ids(chat_id) - - def remove_usernames(self, username: SLT[str]) -> None: - """ - Remove one or more chats from allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which username(s) to disallow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().remove_usernames(username) - - def remove_chat_ids(self, chat_id: SLT[int]) -> None: - """ - Remove one or more chats from allowed chat ids. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which chat/user ID(s) to disallow through. - """ - return super().remove_chat_ids(chat_id) - - class sender_chat(_ChatUserBaseFilter): - # pylint: disable=W0235 - """Filters messages to allow only those which are from a specified sender chat's chat ID or - username. - - Examples: - * To filter for messages sent to a group by a channel with ID - ``-1234``, use ``MessageHandler(Filters.sender_chat(-1234), callback_method)``. - * To filter for messages of anonymous admins in a super group with username - ``@anonymous``, use - ``MessageHandler(Filters.sender_chat(username='anonymous'), callback_method)``. - * To filter for messages sent to a group by *any* channel, use - ``MessageHandler(Filters.sender_chat.channel, callback_method)``. - * To filter for messages of anonymous admins in *any* super group, use - ``MessageHandler(Filters.sender_chat.super_group, callback_method)``. - - Note: - Remember, ``sender_chat`` is also set for messages in a channel as the channel itself, - so when your bot is an admin in a channel and the linked discussion group, you would - receive the message twice (once from inside the channel, once inside the discussion - group). Since v13.9, the field :attr:`telegram.Message.is_automatic_forward` will be - :obj:`True` for the discussion group message. - - .. seealso:: :attr:`Filters.is_automatic_forward` - - Warning: - :attr:`chat_ids` will return a *copy* of the saved chat ids as :class:`frozenset`. This - is to ensure thread safety. To add/remove a chat, you should use :meth:`add_usernames`, - :meth:`add_chat_ids`, :meth:`remove_usernames` and :meth:`remove_chat_ids`. Only update - the entire set by ``filter.chat_ids/usernames = new_set``, if you are entirely sure - that it is not causing race conditions, as this will complete replace the current set - of allowed chats. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which sender chat chat ID(s) to allow through. - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which sender chat username(s) to allow through. - Leading ``'@'`` s in usernames will be discarded. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no sender - chat is specified in :attr:`chat_ids` and :attr:`usernames`. Defaults to - :obj:`False` - - Raises: - RuntimeError: If both chat_id and username are present. - - Attributes: - chat_ids(set(:obj:`int`), optional): Which sender chat chat ID(s) to allow through. - usernames(set(:obj:`str`), optional): Which sender chat username(s) (without leading - ``'@'``) to allow through. - allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no sender - chat is specified in :attr:`chat_ids` and :attr:`usernames`. - super_group: Messages whose sender chat is a super group. - - Examples: - ``Filters.sender_chat.supergroup`` - channel: Messages whose sender chat is a channel. - - Examples: - ``Filters.sender_chat.channel`` - - """ - - __slots__ = () - - def get_chat_or_user(self, message: Message) -> Optional[Chat]: - return message.sender_chat - - def add_usernames(self, username: SLT[str]) -> None: - """ - Add one or more sender chats to the allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which sender chat username(s) to allow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().add_usernames(username) - - def add_chat_ids(self, chat_id: SLT[int]) -> None: - """ - Add one or more sender chats to the allowed chat ids. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which sender chat ID(s) to allow through. - """ - return super().add_chat_ids(chat_id) - - def remove_usernames(self, username: SLT[str]) -> None: - """ - Remove one or more sender chats from allowed usernames. - - Args: - username(:class:`telegram.utils.types.SLT[str]`, optional): - Which sender chat username(s) to disallow through. - Leading ``'@'`` s in usernames will be discarded. - """ - return super().remove_usernames(username) - - def remove_chat_ids(self, chat_id: SLT[int]) -> None: - """ - Remove one or more sender chats from allowed chat ids. - - Args: - chat_id(:class:`telegram.utils.types.SLT[int]`, optional): - Which sender chat ID(s) to disallow through. - """ - return super().remove_chat_ids(chat_id) - - class _SuperGroup(MessageFilter): - __slots__ = () - - def filter(self, message: Message) -> bool: - if message.sender_chat: - return message.sender_chat.type == Chat.SUPERGROUP - return False - - class _Channel(MessageFilter): - __slots__ = () - - def filter(self, message: Message) -> bool: - if message.sender_chat: - return message.sender_chat.type == Chat.CHANNEL - return False - - super_group = _SuperGroup() - channel = _Channel() - - class _IsAutomaticForward(MessageFilter): - __slots__ = () - name = 'Filters.is_automatic_forward' - - def filter(self, message: Message) -> bool: - return bool(message.is_automatic_forward) - - is_automatic_forward = _IsAutomaticForward() - """Messages that contain :attr:`telegram.Message.is_automatic_forward`. - - .. versionadded:: 13.9 - """ - - class _HasProtectedContent(MessageFilter): - __slots__ = () - name = 'Filters.has_protected_content' - - def filter(self, message: Message) -> bool: - return bool(message.has_protected_content) - - has_protected_content = _HasProtectedContent() - """Messages that contain :attr:`telegram.Message.has_protected_content`. - - .. versionadded:: 13.9 - """ - - class _Invoice(MessageFilter): - __slots__ = () - name = 'Filters.invoice' - - def filter(self, message: Message) -> bool: - return bool(message.invoice) - - invoice = _Invoice() - """Messages that contain :class:`telegram.Invoice`.""" - - class _SuccessfulPayment(MessageFilter): - __slots__ = () - name = 'Filters.successful_payment' - - def filter(self, message: Message) -> bool: - return bool(message.successful_payment) - - successful_payment = _SuccessfulPayment() - """Messages that confirm a :class:`telegram.SuccessfulPayment`.""" - - class _PassportData(MessageFilter): - __slots__ = () - name = 'Filters.passport_data' - - def filter(self, message: Message) -> bool: - return bool(message.passport_data) - - passport_data = _PassportData() - """Messages that contain a :class:`telegram.PassportData`""" - - class _Poll(MessageFilter): - __slots__ = () - name = 'Filters.poll' - - def filter(self, message: Message) -> bool: - return bool(message.poll) - - poll = _Poll() - """Messages that contain a :class:`telegram.Poll`.""" - - class _Dice(_DiceEmoji): - __slots__ = () - dice = _DiceEmoji('🎲', 'dice') - darts = _DiceEmoji('🎯', 'darts') - basketball = _DiceEmoji('🏀', 'basketball') - football = _DiceEmoji('⚽') - slot_machine = _DiceEmoji('🎰') - bowling = _DiceEmoji('🎳', 'bowling') - - dice = _Dice() - """Dice Messages. If an integer or a list of integers is passed, it filters messages to only - allow those whose dice value is appearing in the given list. - - Examples: - To allow any dice message, simply use - ``MessageHandler(Filters.dice, callback_method)``. - - To allow only dice messages with the emoji 🎲, but any value, use - ``MessageHandler(Filters.dice.dice, callback_method)``. - - To allow only dice messages with the emoji 🎯 and with value 6, use - ``MessageHandler(Filters.dice.darts(6), callback_method)``. - - To allow only dice messages with the emoji ⚽ and with value 5 `or` 6, use - ``MessageHandler(Filters.dice.football([5, 6]), callback_method)``. - - Note: - Dice messages don't have text. If you want to filter either text or dice messages, use - ``Filters.text | Filters.dice``. - - Args: - update (:class:`telegram.utils.types.SLT[int]`, optional): - Which values to allow. If not specified, will allow any dice message. - - Attributes: - dice: Dice messages with the emoji 🎲. Passing a list of integers is supported just as for - :attr:`Filters.dice`. - darts: Dice messages with the emoji 🎯. Passing a list of integers is supported just as for - :attr:`Filters.dice`. - basketball: Dice messages with the emoji 🏀. Passing a list of integers is supported just - as for :attr:`Filters.dice`. - football: Dice messages with the emoji ⚽. Passing a list of integers is supported just - as for :attr:`Filters.dice`. - slot_machine: Dice messages with the emoji 🎰. Passing a list of integers is supported just - as for :attr:`Filters.dice`. - bowling: Dice messages with the emoji 🎳. Passing a list of integers is supported just - as for :attr:`Filters.dice`. - - .. versionadded:: 13.4 - - """ - - class language(MessageFilter): - """Filters messages to only allow those which are from users with a certain language code. - - Note: - According to official Telegram API documentation, not every single user has the - `language_code` attribute. Do not count on this filter working on all users. - - Examples: - ``MessageHandler(Filters.language("en"), callback_method)`` - - Args: - lang (:class:`telegram.utils.types.SLT[str]`): - Which language code(s) to allow through. - This will be matched using ``.startswith`` meaning that - 'en' will match both 'en_US' and 'en_GB'. - - """ - - __slots__ = ('lang',) - - def __init__(self, lang: SLT[str]): - if isinstance(lang, str): - lang = cast(str, lang) - self.lang = [lang] - else: - lang = cast(List[str], lang) - self.lang = lang - self.name = f'Filters.language({self.lang})' - - def filter(self, message: Message) -> bool: - """""" # remove method from docs - return bool( - message.from_user.language_code - and any(message.from_user.language_code.startswith(x) for x in self.lang) - ) - - class _Attachment(MessageFilter): - __slots__ = () - - name = 'Filters.attachment' - - def filter(self, message: Message) -> bool: - return bool(message.effective_attachment) - - attachment = _Attachment() - """Messages that contain :meth:`telegram.Message.effective_attachment`. - - - .. versionadded:: 13.6""" - - class _UpdateType(UpdateFilter): - __slots__ = () - name = 'Filters.update' - - class _Message(UpdateFilter): - __slots__ = () - name = 'Filters.update.message' - - def filter(self, update: Update) -> bool: - return update.message is not None - - message = _Message() - - class _EditedMessage(UpdateFilter): - __slots__ = () - name = 'Filters.update.edited_message' - - def filter(self, update: Update) -> bool: - return update.edited_message is not None - - edited_message = _EditedMessage() - - class _Messages(UpdateFilter): - __slots__ = () - name = 'Filters.update.messages' - - def filter(self, update: Update) -> bool: - return update.message is not None or update.edited_message is not None - - messages = _Messages() - - class _ChannelPost(UpdateFilter): - __slots__ = () - name = 'Filters.update.channel_post' - - def filter(self, update: Update) -> bool: - return update.channel_post is not None - - channel_post = _ChannelPost() - - class _EditedChannelPost(UpdateFilter): - __slots__ = () - name = 'Filters.update.edited_channel_post' - - def filter(self, update: Update) -> bool: - return update.edited_channel_post is not None - - edited_channel_post = _EditedChannelPost() - - class _ChannelPosts(UpdateFilter): - __slots__ = () - name = 'Filters.update.channel_posts' - - def filter(self, update: Update) -> bool: - return update.channel_post is not None or update.edited_channel_post is not None - - channel_posts = _ChannelPosts() - - def filter(self, update: Update) -> bool: - return bool(self.messages(update) or self.channel_posts(update)) - - update = _UpdateType() - """Subset for filtering the type of update. - - Examples: - Use these filters like: ``Filters.update.message`` or - ``Filters.update.channel_posts`` etc. Or use just ``Filters.update`` for all - types. - - Attributes: - message: Updates with :attr:`telegram.Update.message` - edited_message: Updates with :attr:`telegram.Update.edited_message` - messages: Updates with either :attr:`telegram.Update.message` or - :attr:`telegram.Update.edited_message` - channel_post: Updates with :attr:`telegram.Update.channel_post` - edited_channel_post: Updates with - :attr:`telegram.Update.edited_channel_post` - channel_posts: Updates with either :attr:`telegram.Update.channel_post` or - :attr:`telegram.Update.edited_channel_post` - """ diff --git a/telegramer/include/telegram/ext/handler.py b/telegramer/include/telegram/ext/handler.py deleted file mode 100644 index b6e3a63..0000000 --- a/telegramer/include/telegram/ext/handler.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the base class for handlers as used by the Dispatcher.""" -from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, Union, Generic -from sys import version_info as py_ver - -from telegram.utils.deprecate import set_new_attribute_deprecated - -from telegram import Update -from telegram.ext.utils.promise import Promise -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE -from telegram.ext.utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') -UT = TypeVar('UT') - - -class Handler(Generic[UT, CCT], ABC): - """The base class for all update handlers. Create custom handlers by inheriting from it. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - # Apparently Py 3.7 and below have '__dict__' in ABC - if py_ver < (3, 7): - __slots__ = ( - 'callback', - 'pass_update_queue', - 'pass_job_queue', - 'pass_user_data', - 'pass_chat_data', - 'run_async', - ) - else: - __slots__ = ( - 'callback', # type: ignore[assignment] - 'pass_update_queue', - 'pass_job_queue', - 'pass_user_data', - 'pass_chat_data', - 'run_async', - '__dict__', - ) - - def __init__( - self, - callback: Callable[[UT, CCT], RT], - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - self.callback = callback - self.pass_update_queue = pass_update_queue - self.pass_job_queue = pass_job_queue - self.pass_user_data = pass_user_data - self.pass_chat_data = pass_chat_data - self.run_async = run_async - - def __setattr__(self, key: str, value: object) -> None: - # See comment on BaseFilter to know why this was done. - if key.startswith('__'): - key = f"_{self.__class__.__name__}{key}" - if issubclass(self.__class__, Handler) and not self.__class__.__module__.startswith( - 'telegram.ext.' - ): - object.__setattr__(self, key, value) - return - set_new_attribute_deprecated(self, key, value) - - @abstractmethod - def check_update(self, update: object) -> Optional[Union[bool, object]]: - """ - This method is called to determine if an update should be handled by - this handler instance. It should always be overridden. - - Note: - Custom updates types can be handled by the dispatcher. Therefore, an implementation of - this method should always check the type of :attr:`update`. - - Args: - update (:obj:`str` | :class:`telegram.Update`): The update to be tested. - - Returns: - Either :obj:`None` or :obj:`False` if the update should not be handled. Otherwise an - object that will be passed to :meth:`handle_update` and - :meth:`collect_additional_context` when the update gets handled. - - """ - - def handle_update( - self, - update: UT, - dispatcher: 'Dispatcher', - check_result: object, - context: CCT = None, - ) -> Union[RT, Promise]: - """ - This method is called if it was determined that an update should indeed - be handled by this instance. Calls :attr:`callback` along with its respectful - arguments. To work with the :class:`telegram.ext.ConversationHandler`, this method - returns the value returned from :attr:`callback`. - Note that it can be overridden if needed by the subclassing handler. - - Args: - update (:obj:`str` | :class:`telegram.Update`): The update to be handled. - dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher. - check_result (:obj:`obj`): The result from :attr:`check_update`. - context (:class:`telegram.ext.CallbackContext`, optional): The context as provided by - the dispatcher. - - """ - run_async = self.run_async - if ( - self.run_async is DEFAULT_FALSE - and dispatcher.bot.defaults - and dispatcher.bot.defaults.run_async - ): - run_async = True - - if context: - self.collect_additional_context(context, update, dispatcher, check_result) - if run_async: - return dispatcher.run_async(self.callback, update, context, update=update) - return self.callback(update, context) - - optional_args = self.collect_optional_args(dispatcher, update, check_result) - if run_async: - return dispatcher.run_async( - self.callback, dispatcher.bot, update, update=update, **optional_args - ) - return self.callback(dispatcher.bot, update, **optional_args) # type: ignore - - def collect_additional_context( - self, - context: CCT, - update: UT, - dispatcher: 'Dispatcher', - check_result: Any, - ) -> None: - """Prepares additional arguments for the context. Override if needed. - - Args: - context (:class:`telegram.ext.CallbackContext`): The context object. - update (:class:`telegram.Update`): The update to gather chat/user id from. - dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher. - check_result: The result (return value) from :attr:`check_update`. - - """ - - def collect_optional_args( - self, - dispatcher: 'Dispatcher', - update: UT = None, - check_result: Any = None, # pylint: disable=W0613 - ) -> Dict[str, object]: - """ - Prepares the optional arguments. If the handler has additional optional args, - it should subclass this method, but remember to call this super method. - - DEPRECATED: This method is being replaced by new context based callbacks. Please see - https://git.io/fxJuV for more info. - - Args: - dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher. - update (:class:`telegram.Update`): The update to gather chat/user id from. - check_result: The result from check_update - - """ - optional_args: Dict[str, object] = {} - - if self.pass_update_queue: - optional_args['update_queue'] = dispatcher.update_queue - if self.pass_job_queue: - optional_args['job_queue'] = dispatcher.job_queue - if self.pass_user_data and isinstance(update, Update): - user = update.effective_user - optional_args['user_data'] = dispatcher.user_data[ - user.id if user else None # type: ignore[index] - ] - if self.pass_chat_data and isinstance(update, Update): - chat = update.effective_chat - optional_args['chat_data'] = dispatcher.chat_data[ - chat.id if chat else None # type: ignore[index] - ] - - return optional_args diff --git a/telegramer/include/telegram/ext/inlinequeryhandler.py b/telegramer/include/telegram/ext/inlinequeryhandler.py deleted file mode 100644 index de43431..0000000 --- a/telegramer/include/telegram/ext/inlinequeryhandler.py +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the InlineQueryHandler class.""" -import re -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - Match, - Optional, - Pattern, - TypeVar, - Union, - cast, - List, -) - -from telegram import Update -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE - -from .handler import Handler -from .utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') - - -class InlineQueryHandler(Handler[Update, CCT]): - """ - Handler class to handle Telegram inline queries. Optionally based on a regex. Read the - documentation of the ``re`` module for more information. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - * When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - * :attr:`telegram.InlineQuery.chat_type` will not be set for inline queries from secret - chats and may not be set for inline queries coming from third-party clients. These - updates won't be handled, if :attr:`chat_types` is passed. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pattern (:obj:`str` | :obj:`Pattern`, optional): Regex pattern. If not :obj:`None`, - ``re.match`` is used on :attr:`telegram.InlineQuery.query` to determine if an update - should be handled by this handler. - chat_types (List[:obj:`str`], optional): List of allowed chat types. If passed, will only - handle inline queries with the appropriate :attr:`telegram.InlineQuery.chat_type`. - - .. versionadded:: 13.5 - pass_groups (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. - Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``. - Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pattern (:obj:`str` | :obj:`Pattern`): Optional. Regex pattern to test - :attr:`telegram.InlineQuery.query` against. - chat_types (List[:obj:`str`], optional): List of allowed chat types. - - .. versionadded:: 13.5 - pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the - callback function. - pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('pattern', 'chat_types', 'pass_groups', 'pass_groupdict') - - def __init__( - self, - callback: Callable[[Update, CCT], RT], - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pattern: Union[str, Pattern] = None, - pass_groups: bool = False, - pass_groupdict: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - chat_types: List[str] = None, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - run_async=run_async, - ) - - if isinstance(pattern, str): - pattern = re.compile(pattern) - - self.pattern = pattern - self.chat_types = chat_types - self.pass_groups = pass_groups - self.pass_groupdict = pass_groupdict - - def check_update(self, update: object) -> Optional[Union[bool, Match]]: - """ - Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - if isinstance(update, Update) and update.inline_query: - if (self.chat_types is not None) and ( - update.inline_query.chat_type not in self.chat_types - ): - return False - if self.pattern: - if update.inline_query.query: - match = re.match(self.pattern, update.inline_query.query) - if match: - return match - else: - return True - return None - - def collect_optional_args( - self, - dispatcher: 'Dispatcher', - update: Update = None, - check_result: Optional[Union[bool, Match]] = None, - ) -> Dict[str, object]: - """Pass the results of ``re.match(pattern, query).{groups(), groupdict()}`` to the - callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if - needed. - """ - optional_args = super().collect_optional_args(dispatcher, update, check_result) - if self.pattern: - check_result = cast(Match, check_result) - if self.pass_groups: - optional_args['groups'] = check_result.groups() - if self.pass_groupdict: - optional_args['groupdict'] = check_result.groupdict() - return optional_args - - def collect_additional_context( - self, - context: CCT, - update: Update, - dispatcher: 'Dispatcher', - check_result: Optional[Union[bool, Match]], - ) -> None: - """Add the result of ``re.match(pattern, update.inline_query.query)`` to - :attr:`CallbackContext.matches` as list with one element. - """ - if self.pattern: - check_result = cast(Match, check_result) - context.matches = [check_result] diff --git a/telegramer/include/telegram/ext/jobqueue.py b/telegramer/include/telegram/ext/jobqueue.py deleted file mode 100644 index f0c1fba..0000000 --- a/telegramer/include/telegram/ext/jobqueue.py +++ /dev/null @@ -1,659 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes JobQueue and Job.""" - -import datetime -import logging -from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union, cast, overload - -import pytz -from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED, JobEvent -from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.triggers.combining import OrTrigger -from apscheduler.triggers.cron import CronTrigger -from apscheduler.job import Job as APSJob - -from telegram.ext.callbackcontext import CallbackContext -from telegram.utils.types import JSONDict -from telegram.utils.deprecate import set_new_attribute_deprecated - -if TYPE_CHECKING: - from telegram import Bot - from telegram.ext import Dispatcher - import apscheduler.job # noqa: F401 - - -class JobQueue: - """This class allows you to periodically perform tasks with the bot. It is a convenience - wrapper for the APScheduler library. - - Attributes: - scheduler (:class:`apscheduler.schedulers.background.BackgroundScheduler`): The APScheduler - bot (:class:`telegram.Bot`): The bot instance that should be passed to the jobs. - DEPRECATED: Use :attr:`set_dispatcher` instead. - - """ - - __slots__ = ('_dispatcher', 'logger', 'scheduler', '__dict__') - - def __init__(self) -> None: - self._dispatcher: 'Dispatcher' = None # type: ignore[assignment] - self.logger = logging.getLogger(self.__class__.__name__) - self.scheduler = BackgroundScheduler(timezone=pytz.utc) - self.scheduler.add_listener( - self._update_persistence, mask=EVENT_JOB_EXECUTED | EVENT_JOB_ERROR - ) - - # Dispatch errors and don't log them in the APS logger - def aps_log_filter(record): # type: ignore - return 'raised an exception' not in record.msg - - logging.getLogger('apscheduler.executors.default').addFilter(aps_log_filter) - self.scheduler.add_listener(self._dispatch_error, EVENT_JOB_ERROR) - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - def _build_args(self, job: 'Job') -> List[Union[CallbackContext, 'Bot', 'Job']]: - if self._dispatcher.use_context: - return [self._dispatcher.context_types.context.from_job(job, self._dispatcher)] - return [self._dispatcher.bot, job] - - def _tz_now(self) -> datetime.datetime: - return datetime.datetime.now(self.scheduler.timezone) - - def _update_persistence(self, _: JobEvent) -> None: - self._dispatcher.update_persistence() - - def _dispatch_error(self, event: JobEvent) -> None: - try: - self._dispatcher.dispatch_error(None, event.exception) - # Errors should not stop the thread. - except Exception: - self.logger.exception( - 'An error was raised while processing the job and an ' - 'uncaught error was raised while handling the error ' - 'with an error_handler.' - ) - - @overload - def _parse_time_input(self, time: None, shift_day: bool = False) -> None: - ... - - @overload - def _parse_time_input( - self, - time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time], - shift_day: bool = False, - ) -> datetime.datetime: - ... - - def _parse_time_input( - self, - time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time, None], - shift_day: bool = False, - ) -> Optional[datetime.datetime]: - if time is None: - return None - if isinstance(time, (int, float)): - return self._tz_now() + datetime.timedelta(seconds=time) - if isinstance(time, datetime.timedelta): - return self._tz_now() + time - if isinstance(time, datetime.time): - date_time = datetime.datetime.combine( - datetime.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time - ) - if date_time.tzinfo is None: - date_time = self.scheduler.timezone.localize(date_time) - if shift_day and date_time <= datetime.datetime.now(pytz.utc): - date_time += datetime.timedelta(days=1) - return date_time - # isinstance(time, datetime.datetime): - return time - - def set_dispatcher(self, dispatcher: 'Dispatcher') -> None: - """Set the dispatcher to be used by this JobQueue. Use this instead of passing a - :class:`telegram.Bot` to the JobQueue, which is deprecated. - - Args: - dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher. - - """ - self._dispatcher = dispatcher - if dispatcher.bot.defaults: - self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc) - - def run_once( - self, - callback: Callable[['CallbackContext'], None], - when: Union[float, datetime.timedelta, datetime.datetime, datetime.time], - context: object = None, - name: str = None, - job_kwargs: JSONDict = None, - ) -> 'Job': - """Creates a new ``Job`` that runs once and adds it to the queue. - - Args: - callback (:obj:`callable`): The callback function that should be executed by the new - job. Callback signature for context based API: - - ``def callback(CallbackContext)`` - - ``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access - its ``job.context`` or change it to a repeating job. - when (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \ - :obj:`datetime.datetime` | :obj:`datetime.time`): - Time in or at which the job should run. This parameter will be interpreted - depending on its type. - - * :obj:`int` or :obj:`float` will be interpreted as "seconds from now" in which the - job should run. - * :obj:`datetime.timedelta` will be interpreted as "time from now" in which the - job should run. - * :obj:`datetime.datetime` will be interpreted as a specific date and time at - which the job should run. If the timezone (``datetime.tzinfo``) is :obj:`None`, - the default timezone of the bot will be used. - * :obj:`datetime.time` will be interpreted as a specific time of day at which the - job should run. This could be either today or, if the time has already passed, - tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, the - default timezone of the bot will be used. - - context (:obj:`object`, optional): Additional data needed for the callback function. - Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`. - name (:obj:`str`, optional): The name of the new job. Defaults to - ``callback.__name__``. - job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the - ``scheduler.add_job()``. - - Returns: - :class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job - queue. - - """ - if not job_kwargs: - job_kwargs = {} - - name = name or callback.__name__ - job = Job(callback, context, name, self) - date_time = self._parse_time_input(when, shift_day=True) - - j = self.scheduler.add_job( - callback, - name=name, - trigger='date', - run_date=date_time, - args=self._build_args(job), - timezone=date_time.tzinfo or self.scheduler.timezone, - **job_kwargs, - ) - - job.job = j - return job - - def run_repeating( - self, - callback: Callable[['CallbackContext'], None], - interval: Union[float, datetime.timedelta], - first: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None, - last: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None, - context: object = None, - name: str = None, - job_kwargs: JSONDict = None, - ) -> 'Job': - """Creates a new ``Job`` that runs at specified intervals and adds it to the queue. - - Note: - For a note about DST, please see the documentation of `APScheduler`_. - - .. _`APScheduler`: https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html - #daylight-saving-time-behavior - - Args: - callback (:obj:`callable`): The callback function that should be executed by the new - job. Callback signature for context based API: - - ``def callback(CallbackContext)`` - - ``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access - its ``job.context`` or change it to a repeating job. - interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`): The interval in which - the job will run. If it is an :obj:`int` or a :obj:`float`, it will be interpreted - as seconds. - first (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \ - :obj:`datetime.datetime` | :obj:`datetime.time`, optional): - Time in or at which the job should run. This parameter will be interpreted - depending on its type. - - * :obj:`int` or :obj:`float` will be interpreted as "seconds from now" in which the - job should run. - * :obj:`datetime.timedelta` will be interpreted as "time from now" in which the - job should run. - * :obj:`datetime.datetime` will be interpreted as a specific date and time at - which the job should run. If the timezone (``datetime.tzinfo``) is :obj:`None`, - the default timezone of the bot will be used. - * :obj:`datetime.time` will be interpreted as a specific time of day at which the - job should run. This could be either today or, if the time has already passed, - tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, the - default timezone of the bot will be used. - - Defaults to ``interval`` - last (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \ - :obj:`datetime.datetime` | :obj:`datetime.time`, optional): - Latest possible time for the job to run. This parameter will be interpreted - depending on its type. See ``first`` for details. - - If ``last`` is :obj:`datetime.datetime` or :obj:`datetime.time` type - and ``last.tzinfo`` is :obj:`None`, the default timezone of the bot will be - assumed. - - Defaults to :obj:`None`. - context (:obj:`object`, optional): Additional data needed for the callback function. - Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`. - name (:obj:`str`, optional): The name of the new job. Defaults to - ``callback.__name__``. - job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the - ``scheduler.add_job()``. - - Returns: - :class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job - queue. - - """ - if not job_kwargs: - job_kwargs = {} - - name = name or callback.__name__ - job = Job(callback, context, name, self) - - dt_first = self._parse_time_input(first) - dt_last = self._parse_time_input(last) - - if dt_last and dt_first and dt_last < dt_first: - raise ValueError("'last' must not be before 'first'!") - - if isinstance(interval, datetime.timedelta): - interval = interval.total_seconds() - - j = self.scheduler.add_job( - callback, - trigger='interval', - args=self._build_args(job), - start_date=dt_first, - end_date=dt_last, - seconds=interval, - name=name, - **job_kwargs, - ) - - job.job = j - return job - - def run_monthly( - self, - callback: Callable[['CallbackContext'], None], - when: datetime.time, - day: int, - context: object = None, - name: str = None, - day_is_strict: bool = True, - job_kwargs: JSONDict = None, - ) -> 'Job': - """Creates a new ``Job`` that runs on a monthly basis and adds it to the queue. - - Args: - callback (:obj:`callable`): The callback function that should be executed by the new - job. Callback signature for context based API: - - ``def callback(CallbackContext)`` - - ``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access - its ``job.context`` or change it to a repeating job. - when (:obj:`datetime.time`): Time of day at which the job should run. If the timezone - (``when.tzinfo``) is :obj:`None`, the default timezone of the bot will be used. - day (:obj:`int`): Defines the day of the month whereby the job would run. It should - be within the range of 1 and 31, inclusive. - context (:obj:`object`, optional): Additional data needed for the callback function. - Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`. - name (:obj:`str`, optional): The name of the new job. Defaults to - ``callback.__name__``. - day_is_strict (:obj:`bool`, optional): If :obj:`False` and day > month.days, will pick - the last day in the month. Defaults to :obj:`True`. - job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the - ``scheduler.add_job()``. - - Returns: - :class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job - queue. - - """ - if not job_kwargs: - job_kwargs = {} - - name = name or callback.__name__ - job = Job(callback, context, name, self) - - if day_is_strict: - j = self.scheduler.add_job( - callback, - trigger='cron', - args=self._build_args(job), - name=name, - day=day, - hour=when.hour, - minute=when.minute, - second=when.second, - timezone=when.tzinfo or self.scheduler.timezone, - **job_kwargs, - ) - else: - trigger = OrTrigger( - [ - CronTrigger( - day=day, - hour=when.hour, - minute=when.minute, - second=when.second, - timezone=when.tzinfo, - **job_kwargs, - ), - CronTrigger( - day='last', - hour=when.hour, - minute=when.minute, - second=when.second, - timezone=when.tzinfo or self.scheduler.timezone, - **job_kwargs, - ), - ] - ) - j = self.scheduler.add_job( - callback, trigger=trigger, args=self._build_args(job), name=name, **job_kwargs - ) - - job.job = j - return job - - def run_daily( - self, - callback: Callable[['CallbackContext'], None], - time: datetime.time, - days: Tuple[int, ...] = tuple(range(7)), - context: object = None, - name: str = None, - job_kwargs: JSONDict = None, - ) -> 'Job': - """Creates a new ``Job`` that runs on a daily basis and adds it to the queue. - - Note: - For a note about DST, please see the documentation of `APScheduler`_. - - .. _`APScheduler`: https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html - #daylight-saving-time-behavior - - Args: - callback (:obj:`callable`): The callback function that should be executed by the new - job. Callback signature for context based API: - - ``def callback(CallbackContext)`` - - ``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access - its ``job.context`` or change it to a repeating job. - time (:obj:`datetime.time`): Time of day at which the job should run. If the timezone - (``time.tzinfo``) is :obj:`None`, the default timezone of the bot will be used. - days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should - run (where ``0-6`` correspond to monday - sunday). Defaults to ``EVERY_DAY`` - context (:obj:`object`, optional): Additional data needed for the callback function. - Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`. - name (:obj:`str`, optional): The name of the new job. Defaults to - ``callback.__name__``. - job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the - ``scheduler.add_job()``. - - Returns: - :class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job - queue. - - """ - if not job_kwargs: - job_kwargs = {} - - name = name or callback.__name__ - job = Job(callback, context, name, self) - - j = self.scheduler.add_job( - callback, - name=name, - args=self._build_args(job), - trigger='cron', - day_of_week=','.join([str(d) for d in days]), - hour=time.hour, - minute=time.minute, - second=time.second, - timezone=time.tzinfo or self.scheduler.timezone, - **job_kwargs, - ) - - job.job = j - return job - - def run_custom( - self, - callback: Callable[['CallbackContext'], None], - job_kwargs: JSONDict, - context: object = None, - name: str = None, - ) -> 'Job': - """Creates a new customly defined ``Job``. - - Args: - callback (:obj:`callable`): The callback function that should be executed by the new - job. Callback signature for context based API: - - ``def callback(CallbackContext)`` - - ``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access - its ``job.context`` or change it to a repeating job. - job_kwargs (:obj:`dict`): Arbitrary keyword arguments. Used as arguments for - ``scheduler.add_job``. - context (:obj:`object`, optional): Additional data needed for the callback function. - Can be accessed through ``job.context`` in the callback. Defaults to ``None``. - name (:obj:`str`, optional): The name of the new job. Defaults to - ``callback.__name__``. - - Returns: - :class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job - queue. - - """ - name = name or callback.__name__ - job = Job(callback, context, name, self) - - j = self.scheduler.add_job(callback, args=self._build_args(job), name=name, **job_kwargs) - - job.job = j - return job - - def start(self) -> None: - """Starts the job_queue thread.""" - if not self.scheduler.running: - self.scheduler.start() - - def stop(self) -> None: - """Stops the thread.""" - if self.scheduler.running: - self.scheduler.shutdown() - - def jobs(self) -> Tuple['Job', ...]: - """Returns a tuple of all *scheduled* jobs that are currently in the ``JobQueue``.""" - return tuple( - Job._from_aps_job(job, self) # pylint: disable=W0212 - for job in self.scheduler.get_jobs() - ) - - def get_jobs_by_name(self, name: str) -> Tuple['Job', ...]: - """Returns a tuple of all *pending/scheduled* jobs with the given name that are currently - in the ``JobQueue``. - """ - return tuple(job for job in self.jobs() if job.name == name) - - -class Job: - """This class is a convenience wrapper for the jobs held in a :class:`telegram.ext.JobQueue`. - With the current backend APScheduler, :attr:`job` holds a :class:`apscheduler.job.Job` - instance. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Note: - * All attributes and instance methods of :attr:`job` are also directly available as - attributes/methods of the corresponding :class:`telegram.ext.Job` object. - * Two instances of :class:`telegram.ext.Job` are considered equal, if their corresponding - ``job`` attributes have the same ``id``. - * If :attr:`job` isn't passed on initialization, it must be set manually afterwards for - this :class:`telegram.ext.Job` to be useful. - - Args: - callback (:obj:`callable`): The callback function that should be executed by the new job. - Callback signature for context based API: - - ``def callback(CallbackContext)`` - - a ``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access - its ``job.context`` or change it to a repeating job. - context (:obj:`object`, optional): Additional data needed for the callback function. Can be - accessed through ``job.context`` in the callback. Defaults to :obj:`None`. - name (:obj:`str`, optional): The name of the new job. Defaults to ``callback.__name__``. - job_queue (:class:`telegram.ext.JobQueue`, optional): The ``JobQueue`` this job belongs to. - Only optional for backward compatibility with ``JobQueue.put()``. - job (:class:`apscheduler.job.Job`, optional): The APS Job this job is a wrapper for. - - Attributes: - callback (:obj:`callable`): The callback function that should be executed by the new job. - context (:obj:`object`): Optional. Additional data needed for the callback function. - name (:obj:`str`): Optional. The name of the new job. - job_queue (:class:`telegram.ext.JobQueue`): Optional. The ``JobQueue`` this job belongs to. - job (:class:`apscheduler.job.Job`): Optional. The APS Job this job is a wrapper for. - """ - - __slots__ = ( - 'callback', - 'context', - 'name', - 'job_queue', - '_removed', - '_enabled', - 'job', - '__dict__', - ) - - def __init__( - self, - callback: Callable[['CallbackContext'], None], - context: object = None, - name: str = None, - job_queue: JobQueue = None, - job: APSJob = None, - ): - - self.callback = callback - self.context = context - self.name = name or callback.__name__ - self.job_queue = job_queue - - self._removed = False - self._enabled = False - - self.job = cast(APSJob, job) # skipcq: PTC-W0052 - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - def run(self, dispatcher: 'Dispatcher') -> None: - """Executes the callback function independently of the jobs schedule.""" - try: - if dispatcher.use_context: - self.callback(dispatcher.context_types.context.from_job(self, dispatcher)) - else: - self.callback(dispatcher.bot, self) # type: ignore[arg-type,call-arg] - except Exception as exc: - try: - dispatcher.dispatch_error(None, exc) - # Errors should not stop the thread. - except Exception: - dispatcher.logger.exception( - 'An error was raised while processing the job and an ' - 'uncaught error was raised while handling the error ' - 'with an error_handler.' - ) - - def schedule_removal(self) -> None: - """ - Schedules this job for removal from the ``JobQueue``. It will be removed without executing - its callback function again. - """ - self.job.remove() - self._removed = True - - @property - def removed(self) -> bool: - """:obj:`bool`: Whether this job is due to be removed.""" - return self._removed - - @property - def enabled(self) -> bool: - """:obj:`bool`: Whether this job is enabled.""" - return self._enabled - - @enabled.setter - def enabled(self, status: bool) -> None: - if status: - self.job.resume() - else: - self.job.pause() - self._enabled = status - - @property - def next_t(self) -> Optional[datetime.datetime]: - """ - :obj:`datetime.datetime`: Datetime for the next job execution. - Datetime is localized according to :attr:`tzinfo`. - If job is removed or already ran it equals to :obj:`None`. - """ - return self.job.next_run_time - - @classmethod - def _from_aps_job(cls, job: APSJob, job_queue: JobQueue) -> 'Job': - # context based callbacks - if len(job.args) == 1: - context = job.args[0].job.context - else: - context = job.args[1].context - return cls(job.func, context=context, name=job.name, job_queue=job_queue, job=job) - - def __getattr__(self, item: str) -> object: - return getattr(self.job, item) - - def __lt__(self, other: object) -> bool: - return False - - def __eq__(self, other: object) -> bool: - if isinstance(other, self.__class__): - return self.id == other.id - return False diff --git a/telegramer/include/telegram/ext/messagehandler.py b/telegramer/include/telegram/ext/messagehandler.py deleted file mode 100644 index 57faa3f..0000000 --- a/telegramer/include/telegram/ext/messagehandler.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# TODO: Remove allow_edited -"""This module contains the MessageHandler class.""" -import warnings -from typing import TYPE_CHECKING, Callable, Dict, Optional, TypeVar, Union - -from telegram import Update -from telegram.ext import BaseFilter, Filters -from telegram.utils.deprecate import TelegramDeprecationWarning -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE - -from .handler import Handler -from .utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') - - -class MessageHandler(Handler[Update, CCT]): - """Handler class to handle telegram messages. They might contain text, media or status updates. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from - :class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in - :class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise - operators (& for and, | for or, ~ for not). Default is - :attr:`telegram.ext.filters.Filters.update`. This defaults to all message_type updates - being: ``message``, ``edited_message``, ``channel_post`` and ``edited_channel_post``. - If you don't want or need any of those pass ``~Filters.update.*`` in the filter - argument. - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - message_updates (:obj:`bool`, optional): Should "normal" message updates be handled? - Default is :obj:`None`. - DEPRECATED: Please switch to filters for update filtering. - channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled? - Default is :obj:`None`. - DEPRECATED: Please switch to filters for update filtering. - edited_updates (:obj:`bool`, optional): Should "edited" message updates be handled? Default - is :obj:`None`. - DEPRECATED: Please switch to filters for update filtering. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Raises: - ValueError - - Attributes: - filters (:obj:`Filter`): Only allow updates with these Filters. See - :mod:`telegram.ext.filters` for a full list of all available filters. - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - message_updates (:obj:`bool`): Should "normal" message updates be handled? - Default is :obj:`None`. - channel_post_updates (:obj:`bool`): Should channel posts updates be handled? - Default is :obj:`None`. - edited_updates (:obj:`bool`): Should "edited" message updates be handled? - Default is :obj:`None`. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('filters',) - - def __init__( - self, - filters: BaseFilter, - callback: Callable[[Update, CCT], RT], - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - message_updates: bool = None, - channel_post_updates: bool = None, - edited_updates: bool = None, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - run_async=run_async, - ) - if message_updates is False and channel_post_updates is False and edited_updates is False: - raise ValueError( - 'message_updates, channel_post_updates and edited_updates are all False' - ) - if filters is not None: - self.filters = Filters.update & filters - else: - self.filters = Filters.update - if message_updates is not None: - warnings.warn( - 'message_updates is deprecated. See https://git.io/fxJuV for more info', - TelegramDeprecationWarning, - stacklevel=2, - ) - if message_updates is False: - self.filters &= ~Filters.update.message - - if channel_post_updates is not None: - warnings.warn( - 'channel_post_updates is deprecated. See https://git.io/fxJuV ' 'for more info', - TelegramDeprecationWarning, - stacklevel=2, - ) - if channel_post_updates is False: - self.filters &= ~Filters.update.channel_post - - if edited_updates is not None: - warnings.warn( - 'edited_updates is deprecated. See https://git.io/fxJuV for more info', - TelegramDeprecationWarning, - stacklevel=2, - ) - if edited_updates is False: - self.filters &= ~( - Filters.update.edited_message | Filters.update.edited_channel_post - ) - - def check_update(self, update: object) -> Optional[Union[bool, Dict[str, list]]]: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - if isinstance(update, Update) and update.effective_message: - return self.filters(update) - return None - - def collect_additional_context( - self, - context: CCT, - update: Update, - dispatcher: 'Dispatcher', - check_result: Optional[Union[bool, Dict[str, object]]], - ) -> None: - """Adds possible output of data filters to the :class:`CallbackContext`.""" - if isinstance(check_result, dict): - context.update(check_result) diff --git a/telegramer/include/telegram/ext/messagequeue.py b/telegramer/include/telegram/ext/messagequeue.py deleted file mode 100644 index da2a734..0000000 --- a/telegramer/include/telegram/ext/messagequeue.py +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env python -# -# Module author: -# Tymofii A. Khodniev (thodnev) -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/] -"""A throughput-limiting message processor for Telegram bots.""" -import functools -import queue as q -import threading -import time -import warnings -from typing import TYPE_CHECKING, Callable, List, NoReturn - -from telegram.ext.utils.promise import Promise -from telegram.utils.deprecate import TelegramDeprecationWarning - -if TYPE_CHECKING: - from telegram import Bot - -# We need to count < 1s intervals, so the most accurate timer is needed -curtime = time.perf_counter - - -class DelayQueueError(RuntimeError): - """Indicates processing errors.""" - - __slots__ = () - - -class DelayQueue(threading.Thread): - """ - Processes callbacks from queue with specified throughput limits. Creates a separate thread to - process callbacks with delays. - - .. deprecated:: 13.3 - :class:`telegram.ext.DelayQueue` in its current form is deprecated and will be reinvented - in a future release. See `this thread `_ for a list of known bugs. - - Args: - queue (:obj:`Queue`, optional): Used to pass callbacks to thread. Creates ``Queue`` - implicitly if not provided. - burst_limit (:obj:`int`, optional): Number of maximum callbacks to process per time-window - defined by :attr:`time_limit_ms`. Defaults to 30. - time_limit_ms (:obj:`int`, optional): Defines width of time-window used when each - processing limit is calculated. Defaults to 1000. - exc_route (:obj:`callable`, optional): A callable, accepting 1 positional argument; used to - route exceptions from processor thread to main thread; is called on `Exception` - subclass exceptions. If not provided, exceptions are routed through dummy handler, - which re-raises them. - autostart (:obj:`bool`, optional): If :obj:`True`, processor is started immediately after - object's creation; if :obj:`False`, should be started manually by `start` method. - Defaults to :obj:`True`. - name (:obj:`str`, optional): Thread's name. Defaults to ``'DelayQueue-N'``, where N is - sequential number of object created. - - Attributes: - burst_limit (:obj:`int`): Number of maximum callbacks to process per time-window. - time_limit (:obj:`int`): Defines width of time-window used when each processing limit is - calculated. - exc_route (:obj:`callable`): A callable, accepting 1 positional argument; used to route - exceptions from processor thread to main thread; - name (:obj:`str`): Thread's name. - - """ - - _instcnt = 0 # instance counter - - def __init__( - self, - queue: q.Queue = None, - burst_limit: int = 30, - time_limit_ms: int = 1000, - exc_route: Callable[[Exception], None] = None, - autostart: bool = True, - name: str = None, - ): - warnings.warn( - 'DelayQueue in its current form is deprecated and will be reinvented in a future ' - 'release. See https://git.io/JtDbF for a list of known bugs.', - category=TelegramDeprecationWarning, - ) - - self._queue = queue if queue is not None else q.Queue() - self.burst_limit = burst_limit - self.time_limit = time_limit_ms / 1000 - self.exc_route = exc_route if exc_route is not None else self._default_exception_handler - self.__exit_req = False # flag to gently exit thread - self.__class__._instcnt += 1 - if name is None: - name = f'{self.__class__.__name__}-{self.__class__._instcnt}' - super().__init__(name=name) - self.daemon = False - if autostart: # immediately start processing - super().start() - - def run(self) -> None: - """ - Do not use the method except for unthreaded testing purposes, the method normally is - automatically called by autostart argument. - - """ - times: List[float] = [] # used to store each callable processing time - while True: - item = self._queue.get() - if self.__exit_req: - return # shutdown thread - # delay routine - now = time.perf_counter() - t_delta = now - self.time_limit # calculate early to improve perf. - if times and t_delta > times[-1]: - # if last call was before the limit time-window - # used to impr. perf. in long-interval calls case - times = [now] - else: - # collect last in current limit time-window - times = [t for t in times if t >= t_delta] - times.append(now) - if len(times) >= self.burst_limit: # if throughput limit was hit - time.sleep(times[1] - t_delta) - # finally process one - try: - func, args, kwargs = item - func(*args, **kwargs) - except Exception as exc: # re-route any exceptions - self.exc_route(exc) # to prevent thread exit - - def stop(self, timeout: float = None) -> None: - """Used to gently stop processor and shutdown its thread. - - Args: - timeout (:obj:`float`): Indicates maximum time to wait for processor to stop and its - thread to exit. If timeout exceeds and processor has not stopped, method silently - returns. :attr:`is_alive` could be used afterwards to check the actual status. - ``timeout`` set to :obj:`None`, blocks until processor is shut down. - Defaults to :obj:`None`. - - """ - self.__exit_req = True # gently request - self._queue.put(None) # put something to unfreeze if frozen - super().join(timeout=timeout) - - @staticmethod - def _default_exception_handler(exc: Exception) -> NoReturn: - """ - Dummy exception handler which re-raises exception in thread. Could be possibly overwritten - by subclasses. - - """ - raise exc - - def __call__(self, func: Callable, *args: object, **kwargs: object) -> None: - """Used to process callbacks in throughput-limiting thread through queue. - - Args: - func (:obj:`callable`): The actual function (or any callable) that is processed through - queue. - *args (:obj:`list`): Variable-length `func` arguments. - **kwargs (:obj:`dict`): Arbitrary keyword-arguments to `func`. - - """ - if not self.is_alive() or self.__exit_req: - raise DelayQueueError('Could not process callback in stopped thread') - self._queue.put((func, args, kwargs)) - - -# The most straightforward way to implement this is to use 2 sequential delay -# queues, like on classic delay chain schematics in electronics. -# So, message path is: -# msg --> group delay if group msg, else no delay --> normal msg delay --> out -# This way OS threading scheduler cares of timings accuracy. -# (see time.time, time.clock, time.perf_counter, time.sleep @ docs.python.org) -class MessageQueue: - """ - Implements callback processing with proper delays to avoid hitting Telegram's message limits. - Contains two ``DelayQueue``, for group and for all messages, interconnected in delay chain. - Callables are processed through *group* ``DelayQueue``, then through *all* ``DelayQueue`` for - group-type messages. For non-group messages, only the *all* ``DelayQueue`` is used. - - .. deprecated:: 13.3 - :class:`telegram.ext.MessageQueue` in its current form is deprecated and will be reinvented - in a future release. See `this thread `_ for a list of known bugs. - - Args: - all_burst_limit (:obj:`int`, optional): Number of maximum *all-type* callbacks to process - per time-window defined by :attr:`all_time_limit_ms`. Defaults to 30. - all_time_limit_ms (:obj:`int`, optional): Defines width of *all-type* time-window used when - each processing limit is calculated. Defaults to 1000 ms. - group_burst_limit (:obj:`int`, optional): Number of maximum *group-type* callbacks to - process per time-window defined by :attr:`group_time_limit_ms`. Defaults to 20. - group_time_limit_ms (:obj:`int`, optional): Defines width of *group-type* time-window used - when each processing limit is calculated. Defaults to 60000 ms. - exc_route (:obj:`callable`, optional): A callable, accepting one positional argument; used - to route exceptions from processor threads to main thread; is called on ``Exception`` - subclass exceptions. If not provided, exceptions are routed through dummy handler, - which re-raises them. - autostart (:obj:`bool`, optional): If :obj:`True`, processors are started immediately after - object's creation; if :obj:`False`, should be started manually by :attr:`start` method. - Defaults to :obj:`True`. - - """ - - def __init__( - self, - all_burst_limit: int = 30, - all_time_limit_ms: int = 1000, - group_burst_limit: int = 20, - group_time_limit_ms: int = 60000, - exc_route: Callable[[Exception], None] = None, - autostart: bool = True, - ): - warnings.warn( - 'MessageQueue in its current form is deprecated and will be reinvented in a future ' - 'release. See https://git.io/JtDbF for a list of known bugs.', - category=TelegramDeprecationWarning, - ) - - # create according delay queues, use composition - self._all_delayq = DelayQueue( - burst_limit=all_burst_limit, - time_limit_ms=all_time_limit_ms, - exc_route=exc_route, - autostart=autostart, - ) - self._group_delayq = DelayQueue( - burst_limit=group_burst_limit, - time_limit_ms=group_time_limit_ms, - exc_route=exc_route, - autostart=autostart, - ) - - def start(self) -> None: - """Method is used to manually start the ``MessageQueue`` processing.""" - self._all_delayq.start() - self._group_delayq.start() - - def stop(self, timeout: float = None) -> None: - """Stops the ``MessageQueue``.""" - self._group_delayq.stop(timeout=timeout) - self._all_delayq.stop(timeout=timeout) - - stop.__doc__ = DelayQueue.stop.__doc__ or '' # reuse docstring if any - - def __call__(self, promise: Callable, is_group_msg: bool = False) -> Callable: - """ - Processes callables in throughput-limiting queues to avoid hitting limits (specified with - :attr:`burst_limit` and :attr:`time_limit`. - - Args: - promise (:obj:`callable`): Mainly the ``telegram.utils.promise.Promise`` (see Notes for - other callables), that is processed in delay queues. - is_group_msg (:obj:`bool`, optional): Defines whether ``promise`` would be processed in - group*+*all* ``DelayQueue``s (if set to :obj:`True`), or only through *all* - ``DelayQueue`` (if set to :obj:`False`), resulting in needed delays to avoid - hitting specified limits. Defaults to :obj:`False`. - - Note: - Method is designed to accept ``telegram.utils.promise.Promise`` as ``promise`` - argument, but other callables could be used too. For example, lambdas or simple - functions could be used to wrap original func to be called with needed args. In that - case, be sure that either wrapper func does not raise outside exceptions or the proper - :attr:`exc_route` handler is provided. - - Returns: - :obj:`callable`: Used as ``promise`` argument. - - """ - if not is_group_msg: # ignore middle group delay - self._all_delayq(promise) - else: # use middle group delay - self._group_delayq(self._all_delayq, promise) - return promise - - -def queuedmessage(method: Callable) -> Callable: - """A decorator to be used with :attr:`telegram.Bot` send* methods. - - Note: - As it probably wouldn't be a good idea to make this decorator a property, it has been coded - as decorator function, so it implies that first positional argument to wrapped MUST be - self. - - The next object attributes are used by decorator: - - Attributes: - self._is_messages_queued_default (:obj:`bool`): Value to provide class-defaults to - ``queued`` kwarg if not provided during wrapped method call. - self._msg_queue (:class:`telegram.ext.messagequeue.MessageQueue`): The actual - ``MessageQueue`` used to delay outbound messages according to specified time-limits. - - Wrapped method starts accepting the next kwargs: - - Args: - queued (:obj:`bool`, optional): If set to :obj:`True`, the ``MessageQueue`` is used to - process output messages. Defaults to `self._is_queued_out`. - isgroup (:obj:`bool`, optional): If set to :obj:`True`, the message is meant to be - group-type(as there's no obvious way to determine its type in other way at the moment). - Group-type messages could have additional processing delay according to limits set - in `self._out_queue`. Defaults to :obj:`False`. - - Returns: - ``telegram.utils.promise.Promise``: In case call is queued or original method's return - value if it's not. - - """ - - @functools.wraps(method) - def wrapped(self: 'Bot', *args: object, **kwargs: object) -> object: - # pylint: disable=W0212 - queued = kwargs.pop( - 'queued', self._is_messages_queued_default # type: ignore[attr-defined] - ) - isgroup = kwargs.pop('isgroup', False) - if queued: - prom = Promise(method, (self,) + args, kwargs) - return self._msg_queue(prom, isgroup) # type: ignore[attr-defined] - return method(self, *args, **kwargs) - - return wrapped diff --git a/telegramer/include/telegram/ext/picklepersistence.py b/telegramer/include/telegram/ext/picklepersistence.py deleted file mode 100644 index c3e4ba8..0000000 --- a/telegramer/include/telegram/ext/picklepersistence.py +++ /dev/null @@ -1,463 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the PicklePersistence class.""" -import pickle -from collections import defaultdict -from typing import ( - Any, - Dict, - Optional, - Tuple, - overload, - cast, - DefaultDict, -) - -from telegram.ext import BasePersistence -from .utils.types import UD, CD, BD, ConversationDict, CDCData -from .contexttypes import ContextTypes - - -class PicklePersistence(BasePersistence[UD, CD, BD]): - """Using python's builtin pickle for making your bot persistent. - - Warning: - :class:`PicklePersistence` will try to replace :class:`telegram.Bot` instances by - :attr:`REPLACED_BOT` and insert the bot set with - :meth:`telegram.ext.BasePersistence.set_bot` upon loading of the data. This is to ensure - that changes to the bot apply to the saved objects, too. If you change the bots token, this - may lead to e.g. ``Chat not found`` errors. For the limitations on replacing bots see - :meth:`telegram.ext.BasePersistence.replace_bot` and - :meth:`telegram.ext.BasePersistence.insert_bot`. - - Args: - filename (:obj:`str`): The filename for storing the pickle files. When :attr:`single_file` - is :obj:`False` this will be used as a prefix. - store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this - persistence class. Default is :obj:`True`. - store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this - persistence class. Default is :obj:`True`. - store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this - persistence class. Default is :obj:`True`. - store_callback_data (:obj:`bool`, optional): Whether callback_data should be saved by this - persistence class. Default is :obj:`False`. - - .. versionadded:: 13.6 - single_file (:obj:`bool`, optional): When :obj:`False` will store 5 separate files of - `filename_user_data`, `filename_bot_data`, `filename_chat_data`, - `filename_callback_data` and `filename_conversations`. Default is :obj:`True`. - on_flush (:obj:`bool`, optional): When :obj:`True` will only save to file when - :meth:`flush` is called and keep data in memory until that happens. When - :obj:`False` will store data on any transaction *and* on call to :meth:`flush`. - Default is :obj:`False`. - context_types (:class:`telegram.ext.ContextTypes`, optional): Pass an instance - of :class:`telegram.ext.ContextTypes` to customize the types used in the - ``context`` interface. If not passed, the defaults documented in - :class:`telegram.ext.ContextTypes` will be used. - - .. versionadded:: 13.6 - - Attributes: - filename (:obj:`str`): The filename for storing the pickle files. When :attr:`single_file` - is :obj:`False` this will be used as a prefix. - store_user_data (:obj:`bool`): Optional. Whether user_data should be saved by this - persistence class. - store_chat_data (:obj:`bool`): Optional. Whether chat_data should be saved by this - persistence class. - store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this - persistence class. - store_callback_data (:obj:`bool`): Optional. Whether callback_data be saved by this - persistence class. - - .. versionadded:: 13.6 - single_file (:obj:`bool`): Optional. When :obj:`False` will store 5 separate files of - `filename_user_data`, `filename_bot_data`, `filename_chat_data`, - `filename_callback_data` and `filename_conversations`. Default is :obj:`True`. - on_flush (:obj:`bool`, optional): When :obj:`True` will only save to file when - :meth:`flush` is called and keep data in memory until that happens. When - :obj:`False` will store data on any transaction *and* on call to :meth:`flush`. - Default is :obj:`False`. - context_types (:class:`telegram.ext.ContextTypes`): Container for the types used - in the ``context`` interface. - - .. versionadded:: 13.6 - """ - - __slots__ = ( - 'filename', - 'single_file', - 'on_flush', - 'user_data', - 'chat_data', - 'bot_data', - 'callback_data', - 'conversations', - 'context_types', - ) - - @overload - def __init__( - self: 'PicklePersistence[Dict, Dict, Dict]', - filename: str, - store_user_data: bool = True, - store_chat_data: bool = True, - store_bot_data: bool = True, - single_file: bool = True, - on_flush: bool = False, - store_callback_data: bool = False, - ): - ... - - @overload - def __init__( - self: 'PicklePersistence[UD, CD, BD]', - filename: str, - store_user_data: bool = True, - store_chat_data: bool = True, - store_bot_data: bool = True, - single_file: bool = True, - on_flush: bool = False, - store_callback_data: bool = False, - context_types: ContextTypes[Any, UD, CD, BD] = None, - ): - ... - - def __init__( - self, - filename: str, - store_user_data: bool = True, - store_chat_data: bool = True, - store_bot_data: bool = True, - single_file: bool = True, - on_flush: bool = False, - store_callback_data: bool = False, - context_types: ContextTypes[Any, UD, CD, BD] = None, - ): - super().__init__( - store_user_data=store_user_data, - store_chat_data=store_chat_data, - store_bot_data=store_bot_data, - store_callback_data=store_callback_data, - ) - self.filename = filename - self.single_file = single_file - self.on_flush = on_flush - self.user_data: Optional[DefaultDict[int, UD]] = None - self.chat_data: Optional[DefaultDict[int, CD]] = None - self.bot_data: Optional[BD] = None - self.callback_data: Optional[CDCData] = None - self.conversations: Optional[Dict[str, Dict[Tuple, object]]] = None - self.context_types = cast(ContextTypes[Any, UD, CD, BD], context_types or ContextTypes()) - - def _load_singlefile(self) -> None: - try: - filename = self.filename - with open(self.filename, "rb") as file: - data = pickle.load(file) - self.user_data = defaultdict(self.context_types.user_data, data['user_data']) - self.chat_data = defaultdict(self.context_types.chat_data, data['chat_data']) - # For backwards compatibility with files not containing bot data - self.bot_data = data.get('bot_data', self.context_types.bot_data()) - self.callback_data = data.get('callback_data', {}) - self.conversations = data['conversations'] - except OSError: - self.conversations = {} - self.user_data = defaultdict(self.context_types.user_data) - self.chat_data = defaultdict(self.context_types.chat_data) - self.bot_data = self.context_types.bot_data() - self.callback_data = None - except pickle.UnpicklingError as exc: - raise TypeError(f"File {filename} does not contain valid pickle data") from exc - except Exception as exc: - raise TypeError(f"Something went wrong unpickling {filename}") from exc - - @staticmethod - def _load_file(filename: str) -> Any: - try: - with open(filename, "rb") as file: - return pickle.load(file) - except OSError: - return None - except pickle.UnpicklingError as exc: - raise TypeError(f"File {filename} does not contain valid pickle data") from exc - except Exception as exc: - raise TypeError(f"Something went wrong unpickling {filename}") from exc - - def _dump_singlefile(self) -> None: - with open(self.filename, "wb") as file: - data = { - 'conversations': self.conversations, - 'user_data': self.user_data, - 'chat_data': self.chat_data, - 'bot_data': self.bot_data, - 'callback_data': self.callback_data, - } - pickle.dump(data, file) - - @staticmethod - def _dump_file(filename: str, data: object) -> None: - with open(filename, "wb") as file: - pickle.dump(data, file) - - def get_user_data(self) -> DefaultDict[int, UD]: - """Returns the user_data from the pickle file if it exists or an empty :obj:`defaultdict`. - - Returns: - DefaultDict[:obj:`int`, :class:`telegram.ext.utils.types.UD`]: The restored user data. - """ - if self.user_data: - pass - elif not self.single_file: - filename = f"{self.filename}_user_data" - data = self._load_file(filename) - if not data: - data = defaultdict(self.context_types.user_data) - else: - data = defaultdict(self.context_types.user_data, data) - self.user_data = data - else: - self._load_singlefile() - return self.user_data # type: ignore[return-value] - - def get_chat_data(self) -> DefaultDict[int, CD]: - """Returns the chat_data from the pickle file if it exists or an empty :obj:`defaultdict`. - - Returns: - DefaultDict[:obj:`int`, :class:`telegram.ext.utils.types.CD`]: The restored chat data. - """ - if self.chat_data: - pass - elif not self.single_file: - filename = f"{self.filename}_chat_data" - data = self._load_file(filename) - if not data: - data = defaultdict(self.context_types.chat_data) - else: - data = defaultdict(self.context_types.chat_data, data) - self.chat_data = data - else: - self._load_singlefile() - return self.chat_data # type: ignore[return-value] - - def get_bot_data(self) -> BD: - """Returns the bot_data from the pickle file if it exists or an empty object of type - :class:`telegram.ext.utils.types.BD`. - - Returns: - :class:`telegram.ext.utils.types.BD`: The restored bot data. - """ - if self.bot_data: - pass - elif not self.single_file: - filename = f"{self.filename}_bot_data" - data = self._load_file(filename) - if not data: - data = self.context_types.bot_data() - self.bot_data = data - else: - self._load_singlefile() - return self.bot_data # type: ignore[return-value] - - def get_callback_data(self) -> Optional[CDCData]: - """Returns the callback data from the pickle file if it exists or :obj:`None`. - - .. versionadded:: 13.6 - - Returns: - Optional[:class:`telegram.ext.utils.types.CDCData`]: The restored meta data or - :obj:`None`, if no data was stored. - """ - if self.callback_data: - pass - elif not self.single_file: - filename = f"{self.filename}_callback_data" - data = self._load_file(filename) - if not data: - data = None - self.callback_data = data - else: - self._load_singlefile() - if self.callback_data is None: - return None - return self.callback_data[0], self.callback_data[1].copy() - - def get_conversations(self, name: str) -> ConversationDict: - """Returns the conversations from the pickle file if it exists or an empty dict. - - Args: - name (:obj:`str`): The handlers name. - - Returns: - :obj:`dict`: The restored conversations for the handler. - """ - if self.conversations: - pass - elif not self.single_file: - filename = f"{self.filename}_conversations" - data = self._load_file(filename) - if not data: - data = {name: {}} - self.conversations = data - else: - self._load_singlefile() - return self.conversations.get(name, {}).copy() # type: ignore[union-attr] - - def update_conversation( - self, name: str, key: Tuple[int, ...], new_state: Optional[object] - ) -> None: - """Will update the conversations for the given handler and depending on :attr:`on_flush` - save the pickle file. - - Args: - name (:obj:`str`): The handler's name. - key (:obj:`tuple`): The key the state is changed for. - new_state (:obj:`tuple` | :obj:`any`): The new state for the given key. - """ - if not self.conversations: - self.conversations = {} - if self.conversations.setdefault(name, {}).get(key) == new_state: - return - self.conversations[name][key] = new_state - if not self.on_flush: - if not self.single_file: - filename = f"{self.filename}_conversations" - self._dump_file(filename, self.conversations) - else: - self._dump_singlefile() - - def update_user_data(self, user_id: int, data: UD) -> None: - """Will update the user_data and depending on :attr:`on_flush` save the pickle file. - - Args: - user_id (:obj:`int`): The user the data might have been changed for. - data (:class:`telegram.ext.utils.types.UD`): The - :attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``. - """ - if self.user_data is None: - self.user_data = defaultdict(self.context_types.user_data) - if self.user_data.get(user_id) == data: - return - self.user_data[user_id] = data - if not self.on_flush: - if not self.single_file: - filename = f"{self.filename}_user_data" - self._dump_file(filename, self.user_data) - else: - self._dump_singlefile() - - def update_chat_data(self, chat_id: int, data: CD) -> None: - """Will update the chat_data and depending on :attr:`on_flush` save the pickle file. - - Args: - chat_id (:obj:`int`): The chat the data might have been changed for. - data (:class:`telegram.ext.utils.types.CD`): The - :attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``. - """ - if self.chat_data is None: - self.chat_data = defaultdict(self.context_types.chat_data) - if self.chat_data.get(chat_id) == data: - return - self.chat_data[chat_id] = data - if not self.on_flush: - if not self.single_file: - filename = f"{self.filename}_chat_data" - self._dump_file(filename, self.chat_data) - else: - self._dump_singlefile() - - def update_bot_data(self, data: BD) -> None: - """Will update the bot_data and depending on :attr:`on_flush` save the pickle file. - - Args: - data (:class:`telegram.ext.utils.types.BD`): The - :attr:`telegram.ext.Dispatcher.bot_data`. - """ - if self.bot_data == data: - return - self.bot_data = data - if not self.on_flush: - if not self.single_file: - filename = f"{self.filename}_bot_data" - self._dump_file(filename, self.bot_data) - else: - self._dump_singlefile() - - def update_callback_data(self, data: CDCData) -> None: - """Will update the callback_data (if changed) and depending on :attr:`on_flush` save the - pickle file. - - .. versionadded:: 13.6 - - Args: - data (:class:`telegram.ext.utils.types.CDCData`): The relevant data to restore - :class:`telegram.ext.CallbackDataCache`. - """ - if self.callback_data == data: - return - self.callback_data = (data[0], data[1].copy()) - if not self.on_flush: - if not self.single_file: - filename = f"{self.filename}_callback_data" - self._dump_file(filename, self.callback_data) - else: - self._dump_singlefile() - - def refresh_user_data(self, user_id: int, user_data: UD) -> None: - """Does nothing. - - .. versionadded:: 13.6 - .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_user_data` - """ - - def refresh_chat_data(self, chat_id: int, chat_data: CD) -> None: - """Does nothing. - - .. versionadded:: 13.6 - .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_chat_data` - """ - - def refresh_bot_data(self, bot_data: BD) -> None: - """Does nothing. - - .. versionadded:: 13.6 - .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_bot_data` - """ - - def flush(self) -> None: - """Will save all data in memory to pickle file(s).""" - if self.single_file: - if ( - self.user_data - or self.chat_data - or self.bot_data - or self.callback_data - or self.conversations - ): - self._dump_singlefile() - else: - if self.user_data: - self._dump_file(f"{self.filename}_user_data", self.user_data) - if self.chat_data: - self._dump_file(f"{self.filename}_chat_data", self.chat_data) - if self.bot_data: - self._dump_file(f"{self.filename}_bot_data", self.bot_data) - if self.callback_data: - self._dump_file(f"{self.filename}_callback_data", self.callback_data) - if self.conversations: - self._dump_file(f"{self.filename}_conversations", self.conversations) diff --git a/telegramer/include/telegram/ext/pollanswerhandler.py b/telegramer/include/telegram/ext/pollanswerhandler.py deleted file mode 100644 index 53172b2..0000000 --- a/telegramer/include/telegram/ext/pollanswerhandler.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the PollAnswerHandler class.""" - - -from telegram import Update - -from .handler import Handler -from .utils.types import CCT - - -class PollAnswerHandler(Handler[Update, CCT]): - """Handler class to handle Telegram updates that contain a poll answer. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = () - - def check_update(self, update: object) -> bool: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - return isinstance(update, Update) and bool(update.poll_answer) diff --git a/telegramer/include/telegram/ext/pollhandler.py b/telegramer/include/telegram/ext/pollhandler.py deleted file mode 100644 index 0e2dee6..0000000 --- a/telegramer/include/telegram/ext/pollhandler.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the PollHandler classes.""" - - -from telegram import Update - -from .handler import Handler -from .utils.types import CCT - - -class PollHandler(Handler[Update, CCT]): - """Handler class to handle Telegram updates that contain a poll. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = () - - def check_update(self, update: object) -> bool: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - return isinstance(update, Update) and bool(update.poll) diff --git a/telegramer/include/telegram/ext/precheckoutqueryhandler.py b/telegramer/include/telegram/ext/precheckoutqueryhandler.py deleted file mode 100644 index bbef18e..0000000 --- a/telegramer/include/telegram/ext/precheckoutqueryhandler.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the PreCheckoutQueryHandler class.""" - - -from telegram import Update - -from .handler import Handler -from .utils.types import CCT - - -class PreCheckoutQueryHandler(Handler[Update, CCT]): - """Handler class to handle Telegram PreCheckout callback queries. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - DEPRECATED: Please switch to context based callbacks. - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = () - - def check_update(self, update: object) -> bool: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - return isinstance(update, Update) and bool(update.pre_checkout_query) diff --git a/telegramer/include/telegram/ext/regexhandler.py b/telegramer/include/telegram/ext/regexhandler.py deleted file mode 100644 index 8211d83..0000000 --- a/telegramer/include/telegram/ext/regexhandler.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# TODO: Remove allow_edited -"""This module contains the RegexHandler class.""" - -import warnings -from typing import TYPE_CHECKING, Callable, Dict, Optional, Pattern, TypeVar, Union, Any - -from telegram import Update -from telegram.ext import Filters, MessageHandler -from telegram.utils.deprecate import TelegramDeprecationWarning -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE -from telegram.ext.utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') - - -class RegexHandler(MessageHandler): - """Handler class to handle Telegram updates based on a regex. - - It uses a regular expression to check text messages. Read the documentation of the ``re`` - module for more information. The ``re.match`` function is used to determine if an update should - be handled by this handler. - - Note: - This handler is being deprecated. For the same use case use: - ``MessageHandler(Filters.regex(r'pattern'), callback)`` - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - - Args: - pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_groups (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. - Default is :obj:`False` - pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``. - Default is :obj:`False` - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - message_updates (:obj:`bool`, optional): Should "normal" message updates be handled? - Default is :obj:`True`. - channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled? - Default is :obj:`True`. - edited_updates (:obj:`bool`, optional): Should "edited" message updates be handled? Default - is :obj:`False`. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Raises: - ValueError - - Attributes: - pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - callback (:obj:`callable`): The callback function for this handler. - pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the - callback function. - pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to - the callback function. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('pass_groups', 'pass_groupdict') - - def __init__( - self, - pattern: Union[str, Pattern], - callback: Callable[[Update, CCT], RT], - pass_groups: bool = False, - pass_groupdict: bool = False, - pass_update_queue: bool = False, - pass_job_queue: bool = False, - pass_user_data: bool = False, - pass_chat_data: bool = False, - allow_edited: bool = False, # pylint: disable=W0613 - message_updates: bool = True, - channel_post_updates: bool = False, - edited_updates: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - warnings.warn( - 'RegexHandler is deprecated. See https://git.io/fxJuV for more info', - TelegramDeprecationWarning, - stacklevel=2, - ) - super().__init__( - Filters.regex(pattern), - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - pass_user_data=pass_user_data, - pass_chat_data=pass_chat_data, - message_updates=message_updates, - channel_post_updates=channel_post_updates, - edited_updates=edited_updates, - run_async=run_async, - ) - self.pass_groups = pass_groups - self.pass_groupdict = pass_groupdict - - def collect_optional_args( - self, - dispatcher: 'Dispatcher', - update: Update = None, - check_result: Optional[Union[bool, Dict[str, Any]]] = None, - ) -> Dict[str, object]: - """Pass the results of ``re.match(pattern, text).{groups(), groupdict()}`` to the - callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if - needed. - """ - optional_args = super().collect_optional_args(dispatcher, update, check_result) - if isinstance(check_result, dict): - if self.pass_groups: - optional_args['groups'] = check_result['matches'][0].groups() - if self.pass_groupdict: - optional_args['groupdict'] = check_result['matches'][0].groupdict() - return optional_args diff --git a/telegramer/include/telegram/ext/shippingqueryhandler.py b/telegramer/include/telegram/ext/shippingqueryhandler.py deleted file mode 100644 index d8b0218..0000000 --- a/telegramer/include/telegram/ext/shippingqueryhandler.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the ShippingQueryHandler class.""" - - -from telegram import Update -from .handler import Handler -from .utils.types import CCT - - -class ShippingQueryHandler(Handler[Update, CCT]): - """Handler class to handle Telegram shipping callback queries. - - Note: - :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you - can use to keep any data in will be sent to the :attr:`callback` function. Related to - either the user or the chat that the update was sent in. For each update from the same user - or in the same chat, it will be the same ``dict``. - - Note that this is DEPRECATED, and you should use context based callbacks. See - https://git.io/fxJuV for more info. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``user_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``chat_data`` will be passed to the callback function. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - callback (:obj:`callable`): The callback function for this handler. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to - the callback function. - pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = () - - def check_update(self, update: object) -> bool: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:class:`telegram.Update` | :obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - return isinstance(update, Update) and bool(update.shipping_query) diff --git a/telegramer/include/telegram/ext/stringcommandhandler.py b/telegramer/include/telegram/ext/stringcommandhandler.py deleted file mode 100644 index e3945ae..0000000 --- a/telegramer/include/telegram/ext/stringcommandhandler.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the StringCommandHandler class.""" - -from typing import TYPE_CHECKING, Callable, Dict, List, Optional, TypeVar, Union - -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE - -from .handler import Handler -from .utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') - - -class StringCommandHandler(Handler[str, CCT]): - """Handler class to handle string commands. Commands are string updates that start with ``/``. - The handler will add a ``list`` to the - :class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings, - which is the text following the command split on single whitespace characters. - - Note: - This handler is not used to handle Telegram :attr:`telegram.Update`, but strings manually - put in the queue. For example to send messages with the bot using command line or API. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - command (:obj:`str`): The command this handler should listen for. - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the - arguments passed to the command as a keyword argument called ``args``. It will contain - a list of strings, which is the text following the command split on single or - consecutive whitespace characters. Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - command (:obj:`str`): The command this handler should listen for. - callback (:obj:`callable`): The callback function for this handler. - pass_args (:obj:`bool`): Determines whether the handler should be passed - ``args``. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('command', 'pass_args') - - def __init__( - self, - command: str, - callback: Callable[[str, CCT], RT], - pass_args: bool = False, - pass_update_queue: bool = False, - pass_job_queue: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - run_async=run_async, - ) - self.command = command - self.pass_args = pass_args - - def check_update(self, update: object) -> Optional[List[str]]: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:obj:`object`): The incoming update. - - Returns: - :obj:`bool` - - """ - if isinstance(update, str) and update.startswith('/'): - args = update[1:].split(' ') - if args[0] == self.command: - return args[1:] - return None - - def collect_optional_args( - self, - dispatcher: 'Dispatcher', - update: str = None, - check_result: Optional[List[str]] = None, - ) -> Dict[str, object]: - """Provide text after the command to the callback the ``args`` argument as list, split on - single whitespaces. - """ - optional_args = super().collect_optional_args(dispatcher, update, check_result) - if self.pass_args: - optional_args['args'] = check_result - return optional_args - - def collect_additional_context( - self, - context: CCT, - update: str, - dispatcher: 'Dispatcher', - check_result: Optional[List[str]], - ) -> None: - """Add text after the command to :attr:`CallbackContext.args` as list, split on single - whitespaces. - """ - context.args = check_result diff --git a/telegramer/include/telegram/ext/stringregexhandler.py b/telegramer/include/telegram/ext/stringregexhandler.py deleted file mode 100644 index 9be6b36..0000000 --- a/telegramer/include/telegram/ext/stringregexhandler.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the StringRegexHandler class.""" - -import re -from typing import TYPE_CHECKING, Callable, Dict, Match, Optional, Pattern, TypeVar, Union - -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE - -from .handler import Handler -from .utils.types import CCT - -if TYPE_CHECKING: - from telegram.ext import Dispatcher - -RT = TypeVar('RT') - - -class StringRegexHandler(Handler[str, CCT]): - """Handler class to handle string updates based on a regex which checks the update content. - - Read the documentation of the ``re`` module for more information. The ``re.match`` function is - used to determine if an update should be handled by this handler. - - Note: - This handler is not used to handle Telegram :attr:`telegram.Update`, but strings manually - put in the queue. For example to send messages with the bot using command line or API. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - pass_groups (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groups()`` as a keyword argument called ``groups``. - Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of - ``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``. - Default is :obj:`False` - DEPRECATED: Please switch to context based callbacks. - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - pattern (:obj:`str` | :obj:`Pattern`): The regex pattern. - callback (:obj:`callable`): The callback function for this handler. - pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the - callback function. - pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to - the callback function. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('pass_groups', 'pass_groupdict', 'pattern') - - def __init__( - self, - pattern: Union[str, Pattern], - callback: Callable[[str, CCT], RT], - pass_groups: bool = False, - pass_groupdict: bool = False, - pass_update_queue: bool = False, - pass_job_queue: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - run_async=run_async, - ) - - if isinstance(pattern, str): - pattern = re.compile(pattern) - - self.pattern = pattern - self.pass_groups = pass_groups - self.pass_groupdict = pass_groupdict - - def check_update(self, update: object) -> Optional[Match]: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:obj:`object`): The incoming update. - - Returns: - :obj:`bool` - - """ - if isinstance(update, str): - match = re.match(self.pattern, update) - if match: - return match - return None - - def collect_optional_args( - self, - dispatcher: 'Dispatcher', - update: str = None, - check_result: Optional[Match] = None, - ) -> Dict[str, object]: - """Pass the results of ``re.match(pattern, update).{groups(), groupdict()}`` to the - callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if - needed. - """ - optional_args = super().collect_optional_args(dispatcher, update, check_result) - if self.pattern: - if self.pass_groups and check_result: - optional_args['groups'] = check_result.groups() - if self.pass_groupdict and check_result: - optional_args['groupdict'] = check_result.groupdict() - return optional_args - - def collect_additional_context( - self, - context: CCT, - update: str, - dispatcher: 'Dispatcher', - check_result: Optional[Match], - ) -> None: - """Add the result of ``re.match(pattern, update)`` to :attr:`CallbackContext.matches` as - list with one element. - """ - if self.pattern and check_result: - context.matches = [check_result] diff --git a/telegramer/include/telegram/ext/typehandler.py b/telegramer/include/telegram/ext/typehandler.py deleted file mode 100644 index 14029ab..0000000 --- a/telegramer/include/telegram/ext/typehandler.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the TypeHandler class.""" - -from typing import Callable, Type, TypeVar, Union -from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE - -from .handler import Handler -from .utils.types import CCT - -RT = TypeVar('RT') -UT = TypeVar('UT') - - -class TypeHandler(Handler[UT, CCT]): - """Handler class to handle updates of custom types. - - Warning: - When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom - attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info. - - Args: - type (:obj:`type`): The ``type`` of updates this handler should process, as - determined by ``isinstance`` - callback (:obj:`callable`): The callback function for this handler. Will be called when - :attr:`check_update` has determined that an update should be processed by this handler. - Callback signature for context based API: - - ``def callback(update: Update, context: CallbackContext)`` - - The return value of the callback is usually ignored except for the special case of - :class:`telegram.ext.ConversationHandler`. - strict (:obj:`bool`, optional): Use ``type`` instead of ``isinstance``. - Default is :obj:`False` - pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``update_queue`` will be passed to the callback function. It will be the ``Queue`` - instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` - that contains new updates which can be used to insert updates. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called - ``job_queue`` will be passed to the callback function. It will be a - :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` - which can be used to schedule new jobs. Default is :obj:`False`. - DEPRECATED: Please switch to context based callbacks. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - Defaults to :obj:`False`. - - Attributes: - type (:obj:`type`): The ``type`` of updates this handler should process. - callback (:obj:`callable`): The callback function for this handler. - strict (:obj:`bool`): Use ``type`` instead of ``isinstance``. Default is :obj:`False`. - pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be - passed to the callback function. - pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to - the callback function. - run_async (:obj:`bool`): Determines whether the callback will run asynchronously. - - """ - - __slots__ = ('type', 'strict') - - def __init__( - self, - type: Type[UT], # pylint: disable=W0622 - callback: Callable[[UT, CCT], RT], - strict: bool = False, - pass_update_queue: bool = False, - pass_job_queue: bool = False, - run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, - ): - super().__init__( - callback, - pass_update_queue=pass_update_queue, - pass_job_queue=pass_job_queue, - run_async=run_async, - ) - self.type = type # pylint: disable=E0237 - self.strict = strict # pylint: disable=E0237 - - def check_update(self, update: object) -> bool: - """Determines whether an update should be passed to this handlers :attr:`callback`. - - Args: - update (:obj:`object`): Incoming update. - - Returns: - :obj:`bool` - - """ - if not self.strict: - return isinstance(update, self.type) - return type(update) is self.type # pylint: disable=C0123 diff --git a/telegramer/include/telegram/ext/updater.py b/telegramer/include/telegram/ext/updater.py deleted file mode 100644 index b2c0512..0000000 --- a/telegramer/include/telegram/ext/updater.py +++ /dev/null @@ -1,890 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the class Updater, which tries to make creating Telegram bots intuitive.""" - -import logging -import ssl -import warnings -from queue import Queue -from signal import SIGABRT, SIGINT, SIGTERM, signal -from threading import Event, Lock, Thread, current_thread -from time import sleep -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - List, - Optional, - Tuple, - Union, - no_type_check, - Generic, - overload, -) - -from telegram import Bot, TelegramError -from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized -from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot -from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated -from telegram.utils.helpers import get_signal_name, DEFAULT_FALSE, DefaultValue -from telegram.utils.request import Request -from telegram.ext.utils.types import CCT, UD, CD, BD -from telegram.ext.utils.webhookhandler import WebhookAppClass, WebhookServer - -if TYPE_CHECKING: - from telegram.ext import BasePersistence, Defaults, CallbackContext - - -class Updater(Generic[CCT, UD, CD, BD]): - """ - This class, which employs the :class:`telegram.ext.Dispatcher`, provides a frontend to - :class:`telegram.Bot` to the programmer, so they can focus on coding the bot. Its purpose is to - receive the updates from Telegram and to deliver them to said dispatcher. It also runs in a - separate thread, so the user can interact with the bot, for example on the command line. The - dispatcher supports handlers for different kinds of data: Updates from Telegram, basic text - commands and even arbitrary types. The updater can be started as a polling service or, for - production, use a webhook to receive updates. This is achieved using the WebhookServer and - WebhookHandler classes. - - Note: - * You must supply either a :attr:`bot` or a :attr:`token` argument. - * If you supply a :attr:`bot`, you will need to pass :attr:`arbitrary_callback_data`, - and :attr:`defaults` to the bot instead of the :class:`telegram.ext.Updater`. In this - case, you'll have to use the class :class:`telegram.ext.ExtBot`. - - .. versionchanged:: 13.6 - - Args: - token (:obj:`str`, optional): The bot's token given by the @BotFather. - base_url (:obj:`str`, optional): Base_url for the bot. - base_file_url (:obj:`str`, optional): Base_file_url for the bot. - workers (:obj:`int`, optional): Amount of threads in the thread pool for functions - decorated with ``@run_async`` (ignored if `dispatcher` argument is used). - bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance (ignored if - `dispatcher` argument is used). If a pre-initialized bot is used, it is the user's - responsibility to create it using a `Request` instance with a large enough connection - pool. - dispatcher (:class:`telegram.ext.Dispatcher`, optional): A pre-initialized dispatcher - instance. If a pre-initialized dispatcher is used, it is the user's responsibility to - create it with proper arguments. - private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data. - private_key_password (:obj:`bytes`, optional): Password for above private key. - user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional - arguments. This will be called when a signal is received, defaults are (SIGINT, - SIGTERM, SIGABRT) settable with :attr:`idle`. - request_kwargs (:obj:`dict`, optional): Keyword args to control the creation of a - `telegram.utils.request.Request` object (ignored if `bot` or `dispatcher` argument is - used). The request_kwargs are very useful for the advanced users who would like to - control the default timeouts and/or control the proxy used for http communication. - use_context (:obj:`bool`, optional): If set to :obj:`True` uses the context based callback - API (ignored if `dispatcher` argument is used). Defaults to :obj:`True`. - **New users**: set this to :obj:`True`. - persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to - store data that should be persistent over restarts (ignored if `dispatcher` argument is - used). - defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to - be used if not set explicitly in the bot methods. - arbitrary_callback_data (:obj:`bool` | :obj:`int` | :obj:`None`, optional): Whether to - allow arbitrary objects as callback data for :class:`telegram.InlineKeyboardButton`. - Pass an integer to specify the maximum number of cached objects. For more details, - please see our wiki. Defaults to :obj:`False`. - - .. versionadded:: 13.6 - context_types (:class:`telegram.ext.ContextTypes`, optional): Pass an instance - of :class:`telegram.ext.ContextTypes` to customize the types used in the - ``context`` interface. If not passed, the defaults documented in - :class:`telegram.ext.ContextTypes` will be used. - - .. versionadded:: 13.6 - - Raises: - ValueError: If both :attr:`token` and :attr:`bot` are passed or none of them. - - - Attributes: - bot (:class:`telegram.Bot`): The bot used with this Updater. - user_sig_handler (:obj:`function`): Optional. Function to be called when a signal is - received. - update_queue (:obj:`Queue`): Queue for the updates. - job_queue (:class:`telegram.ext.JobQueue`): Jobqueue for the updater. - dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that handles the updates and - dispatches them to the handlers. - running (:obj:`bool`): Indicates if the updater is running. - persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to - store data that should be persistent over restarts. - use_context (:obj:`bool`): Optional. :obj:`True` if using context based callbacks. - - """ - - __slots__ = ( - 'persistence', - 'dispatcher', - 'user_sig_handler', - 'bot', - 'logger', - 'update_queue', - 'job_queue', - '__exception_event', - 'last_update_id', - 'running', - '_request', - 'is_idle', - 'httpd', - '__lock', - '__threads', - '__dict__', - ) - - @overload - def __init__( - self: 'Updater[CallbackContext, dict, dict, dict]', - token: str = None, - base_url: str = None, - workers: int = 4, - bot: Bot = None, - private_key: bytes = None, - private_key_password: bytes = None, - user_sig_handler: Callable = None, - request_kwargs: Dict[str, Any] = None, - persistence: 'BasePersistence' = None, # pylint: disable=E0601 - defaults: 'Defaults' = None, - use_context: bool = True, - base_file_url: str = None, - arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE, - ): - ... - - @overload - def __init__( - self: 'Updater[CCT, UD, CD, BD]', - token: str = None, - base_url: str = None, - workers: int = 4, - bot: Bot = None, - private_key: bytes = None, - private_key_password: bytes = None, - user_sig_handler: Callable = None, - request_kwargs: Dict[str, Any] = None, - persistence: 'BasePersistence' = None, - defaults: 'Defaults' = None, - use_context: bool = True, - base_file_url: str = None, - arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE, - context_types: ContextTypes[CCT, UD, CD, BD] = None, - ): - ... - - @overload - def __init__( - self: 'Updater[CCT, UD, CD, BD]', - user_sig_handler: Callable = None, - dispatcher: Dispatcher[CCT, UD, CD, BD] = None, - ): - ... - - def __init__( # type: ignore[no-untyped-def,misc] - self, - token: str = None, - base_url: str = None, - workers: int = 4, - bot: Bot = None, - private_key: bytes = None, - private_key_password: bytes = None, - user_sig_handler: Callable = None, - request_kwargs: Dict[str, Any] = None, - persistence: 'BasePersistence' = None, - defaults: 'Defaults' = None, - use_context: bool = True, - dispatcher=None, - base_file_url: str = None, - arbitrary_callback_data: Union[DefaultValue, bool, int, None] = DEFAULT_FALSE, - context_types: ContextTypes[CCT, UD, CD, BD] = None, - ): - - if defaults and bot: - warnings.warn( - 'Passing defaults to an Updater has no effect when a Bot is passed ' - 'as well. Pass them to the Bot instead.', - TelegramDeprecationWarning, - stacklevel=2, - ) - if arbitrary_callback_data is not DEFAULT_FALSE and bot: - warnings.warn( - 'Passing arbitrary_callback_data to an Updater has no ' - 'effect when a Bot is passed as well. Pass them to the Bot instead.', - stacklevel=2, - ) - - if dispatcher is None: - if (token is None) and (bot is None): - raise ValueError('`token` or `bot` must be passed') - if (token is not None) and (bot is not None): - raise ValueError('`token` and `bot` are mutually exclusive') - if (private_key is not None) and (bot is not None): - raise ValueError('`bot` and `private_key` are mutually exclusive') - else: - if bot is not None: - raise ValueError('`dispatcher` and `bot` are mutually exclusive') - if persistence is not None: - raise ValueError('`dispatcher` and `persistence` are mutually exclusive') - if use_context != dispatcher.use_context: - raise ValueError('`dispatcher` and `use_context` are mutually exclusive') - if context_types is not None: - raise ValueError('`dispatcher` and `context_types` are mutually exclusive') - if workers is not None: - raise ValueError('`dispatcher` and `workers` are mutually exclusive') - - self.logger = logging.getLogger(__name__) - self._request = None - - if dispatcher is None: - con_pool_size = workers + 4 - - if bot is not None: - self.bot = bot - if bot.request.con_pool_size < con_pool_size: - self.logger.warning( - 'Connection pool of Request object is smaller than optimal value (%s)', - con_pool_size, - ) - else: - # we need a connection pool the size of: - # * for each of the workers - # * 1 for Dispatcher - # * 1 for polling Updater (even if webhook is used, we can spare a connection) - # * 1 for JobQueue - # * 1 for main thread - if request_kwargs is None: - request_kwargs = {} - if 'con_pool_size' not in request_kwargs: - request_kwargs['con_pool_size'] = con_pool_size - self._request = Request(**request_kwargs) - self.bot = ExtBot( - token, # type: ignore[arg-type] - base_url, - base_file_url=base_file_url, - request=self._request, - private_key=private_key, - private_key_password=private_key_password, - defaults=defaults, - arbitrary_callback_data=( - False # type: ignore[arg-type] - if arbitrary_callback_data is DEFAULT_FALSE - else arbitrary_callback_data - ), - ) - self.update_queue: Queue = Queue() - self.job_queue = JobQueue() - self.__exception_event = Event() - self.persistence = persistence - self.dispatcher = Dispatcher( - self.bot, - self.update_queue, - job_queue=self.job_queue, - workers=workers, - exception_event=self.__exception_event, - persistence=persistence, - use_context=use_context, - context_types=context_types, - ) - self.job_queue.set_dispatcher(self.dispatcher) - else: - con_pool_size = dispatcher.workers + 4 - - self.bot = dispatcher.bot - if self.bot.request.con_pool_size < con_pool_size: - self.logger.warning( - 'Connection pool of Request object is smaller than optimal value (%s)', - con_pool_size, - ) - self.update_queue = dispatcher.update_queue - self.__exception_event = dispatcher.exception_event - self.persistence = dispatcher.persistence - self.job_queue = dispatcher.job_queue - self.dispatcher = dispatcher - - self.user_sig_handler = user_sig_handler - self.last_update_id = 0 - self.running = False - self.is_idle = False - self.httpd = None - self.__lock = Lock() - self.__threads: List[Thread] = [] - - def __setattr__(self, key: str, value: object) -> None: - if key.startswith('__'): - key = f"_{self.__class__.__name__}{key}" - if issubclass(self.__class__, Updater) and self.__class__ is not Updater: - object.__setattr__(self, key, value) - return - set_new_attribute_deprecated(self, key, value) - - def _init_thread(self, target: Callable, name: str, *args: object, **kwargs: object) -> None: - thr = Thread( - target=self._thread_wrapper, - name=f"Bot:{self.bot.id}:{name}", - args=(target,) + args, - kwargs=kwargs, - ) - thr.start() - self.__threads.append(thr) - - def _thread_wrapper(self, target: Callable, *args: object, **kwargs: object) -> None: - thr_name = current_thread().name - self.logger.debug('%s - started', thr_name) - try: - target(*args, **kwargs) - except Exception: - self.__exception_event.set() - self.logger.exception('unhandled exception in %s', thr_name) - raise - self.logger.debug('%s - ended', thr_name) - - def start_polling( - self, - poll_interval: float = 0.0, - timeout: float = 10, - clean: bool = None, - bootstrap_retries: int = -1, - read_latency: float = 2.0, - allowed_updates: List[str] = None, - drop_pending_updates: bool = None, - ) -> Optional[Queue]: - """Starts polling updates from Telegram. - - Args: - poll_interval (:obj:`float`, optional): Time to wait between polling updates from - Telegram in seconds. Default is ``0.0``. - timeout (:obj:`float`, optional): Passed to :meth:`telegram.Bot.get_updates`. - drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on - Telegram servers before actually starting to poll. Default is :obj:`False`. - - .. versionadded :: 13.4 - clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``. - - .. deprecated:: 13.4 - Use ``drop_pending_updates`` instead. - bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the - :class:`telegram.ext.Updater` will retry on failures on the Telegram server. - - * < 0 - retry indefinitely (default) - * 0 - no retries - * > 0 - retry up to X times - - allowed_updates (List[:obj:`str`], optional): Passed to - :meth:`telegram.Bot.get_updates`. - read_latency (:obj:`float` | :obj:`int`, optional): Grace time in seconds for receiving - the reply from server. Will be added to the ``timeout`` value and used as the read - timeout from server (Default: ``2``). - - Returns: - :obj:`Queue`: The update queue that can be filled from the main thread. - - """ - if (clean is not None) and (drop_pending_updates is not None): - raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.') - - if clean is not None: - warnings.warn( - 'The argument `clean` of `start_polling` is deprecated. Please use ' - '`drop_pending_updates` instead.', - category=TelegramDeprecationWarning, - stacklevel=2, - ) - - drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean - - with self.__lock: - if not self.running: - self.running = True - - # Create & start threads - self.job_queue.start() - dispatcher_ready = Event() - polling_ready = Event() - self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready) - self._init_thread( - self._start_polling, - "updater", - poll_interval, - timeout, - read_latency, - bootstrap_retries, - drop_pending_updates, - allowed_updates, - ready=polling_ready, - ) - - self.logger.debug('Waiting for Dispatcher and polling to start') - dispatcher_ready.wait() - polling_ready.wait() - - # Return the update queue so the main thread can insert updates - return self.update_queue - return None - - def start_webhook( - self, - listen: str = '127.0.0.1', - port: int = 80, - url_path: str = '', - cert: str = None, - key: str = None, - clean: bool = None, - bootstrap_retries: int = 0, - webhook_url: str = None, - allowed_updates: List[str] = None, - force_event_loop: bool = None, - drop_pending_updates: bool = None, - ip_address: str = None, - max_connections: int = 40, - ) -> Optional[Queue]: - """ - Starts a small http server to listen for updates via webhook. If :attr:`cert` - and :attr:`key` are not provided, the webhook will be started directly on - http://listen:port/url_path, so SSL can be handled by another - application. Else, the webhook will be started on - https://listen:port/url_path. Also calls :meth:`telegram.Bot.set_webhook` as required. - - .. versionchanged:: 13.4 - :meth:`start_webhook` now *always* calls :meth:`telegram.Bot.set_webhook`, so pass - ``webhook_url`` instead of calling ``updater.bot.set_webhook(webhook_url)`` manually. - - Args: - listen (:obj:`str`, optional): IP-Address to listen on. Default ``127.0.0.1``. - port (:obj:`int`, optional): Port the bot should be listening on. Default ``80``. - url_path (:obj:`str`, optional): Path inside url. - cert (:obj:`str`, optional): Path to the SSL certificate file. - key (:obj:`str`, optional): Path to the SSL key file. - drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on - Telegram servers before actually starting to poll. Default is :obj:`False`. - - .. versionadded :: 13.4 - clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``. - - .. deprecated:: 13.4 - Use ``drop_pending_updates`` instead. - bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the - :class:`telegram.ext.Updater` will retry on failures on the Telegram server. - - * < 0 - retry indefinitely (default) - * 0 - no retries - * > 0 - retry up to X times - - webhook_url (:obj:`str`, optional): Explicitly specify the webhook url. Useful behind - NAT, reverse proxy, etc. Default is derived from ``listen``, ``port`` & - ``url_path``. - ip_address (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`. - - .. versionadded :: 13.4 - allowed_updates (List[:obj:`str`], optional): Passed to - :meth:`telegram.Bot.set_webhook`. - force_event_loop (:obj:`bool`, optional): Legacy parameter formerly used for a - workaround on Windows + Python 3.8+. No longer has any effect. - - .. deprecated:: 13.6 - Since version 13.6, ``tornade>=6.1`` is required, which resolves the former - issue. - - max_connections (:obj:`int`, optional): Passed to - :meth:`telegram.Bot.set_webhook`. - - .. versionadded:: 13.6 - - Returns: - :obj:`Queue`: The update queue that can be filled from the main thread. - - """ - if (clean is not None) and (drop_pending_updates is not None): - raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.') - - if clean is not None: - warnings.warn( - 'The argument `clean` of `start_webhook` is deprecated. Please use ' - '`drop_pending_updates` instead.', - category=TelegramDeprecationWarning, - stacklevel=2, - ) - - if force_event_loop is not None: - warnings.warn( - 'The argument `force_event_loop` of `start_webhook` is deprecated and no longer ' - 'has any effect.', - category=TelegramDeprecationWarning, - stacklevel=2, - ) - - drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean - - with self.__lock: - if not self.running: - self.running = True - - # Create & start threads - webhook_ready = Event() - dispatcher_ready = Event() - self.job_queue.start() - self._init_thread(self.dispatcher.start, "dispatcher", dispatcher_ready) - self._init_thread( - self._start_webhook, - "updater", - listen, - port, - url_path, - cert, - key, - bootstrap_retries, - drop_pending_updates, - webhook_url, - allowed_updates, - ready=webhook_ready, - ip_address=ip_address, - max_connections=max_connections, - ) - - self.logger.debug('Waiting for Dispatcher and Webhook to start') - webhook_ready.wait() - dispatcher_ready.wait() - - # Return the update queue so the main thread can insert updates - return self.update_queue - return None - - @no_type_check - def _start_polling( - self, - poll_interval, - timeout, - read_latency, - bootstrap_retries, - drop_pending_updates, - allowed_updates, - ready=None, - ): # pragma: no cover - # Thread target of thread 'updater'. Runs in background, pulls - # updates from Telegram and inserts them in the update queue of the - # Dispatcher. - - self.logger.debug('Updater thread started (polling)') - - self._bootstrap( - bootstrap_retries, - drop_pending_updates=drop_pending_updates, - webhook_url='', - allowed_updates=None, - ) - - self.logger.debug('Bootstrap done') - - def polling_action_cb(): - updates = self.bot.get_updates( - self.last_update_id, - timeout=timeout, - read_latency=read_latency, - allowed_updates=allowed_updates, - ) - - if updates: - if not self.running: - self.logger.debug('Updates ignored and will be pulled again on restart') - else: - for update in updates: - self.update_queue.put(update) - self.last_update_id = updates[-1].update_id + 1 - - return True - - def polling_onerr_cb(exc): - # Put the error into the update queue and let the Dispatcher - # broadcast it - self.update_queue.put(exc) - - if ready is not None: - ready.set() - - self._network_loop_retry( - polling_action_cb, polling_onerr_cb, 'getting Updates', poll_interval - ) - - @no_type_check - def _network_loop_retry(self, action_cb, onerr_cb, description, interval): - """Perform a loop calling `action_cb`, retrying after network errors. - - Stop condition for loop: `self.running` evaluates :obj:`False` or return value of - `action_cb` evaluates :obj:`False`. - - Args: - action_cb (:obj:`callable`): Network oriented callback function to call. - onerr_cb (:obj:`callable`): Callback to call when TelegramError is caught. Receives the - exception object as a parameter. - description (:obj:`str`): Description text to use for logs and exception raised. - interval (:obj:`float` | :obj:`int`): Interval to sleep between each call to - `action_cb`. - - """ - self.logger.debug('Start network loop retry %s', description) - cur_interval = interval - while self.running: - try: - if not action_cb(): - break - except RetryAfter as exc: - self.logger.info('%s', exc) - cur_interval = 0.5 + exc.retry_after - except TimedOut as toe: - self.logger.debug('Timed out %s: %s', description, toe) - # If failure is due to timeout, we should retry asap. - cur_interval = 0 - except InvalidToken as pex: - self.logger.error('Invalid token; aborting') - raise pex - except TelegramError as telegram_exc: - self.logger.error('Error while %s: %s', description, telegram_exc) - onerr_cb(telegram_exc) - cur_interval = self._increase_poll_interval(cur_interval) - else: - cur_interval = interval - - if cur_interval: - sleep(cur_interval) - - @staticmethod - def _increase_poll_interval(current_interval: float) -> float: - # increase waiting times on subsequent errors up to 30secs - if current_interval == 0: - current_interval = 1 - elif current_interval < 30: - current_interval *= 1.5 - else: - current_interval = min(30.0, current_interval) - return current_interval - - @no_type_check - def _start_webhook( - self, - listen, - port, - url_path, - cert, - key, - bootstrap_retries, - drop_pending_updates, - webhook_url, - allowed_updates, - ready=None, - ip_address=None, - max_connections: int = 40, - ): - self.logger.debug('Updater thread started (webhook)') - - # Note that we only use the SSL certificate for the WebhookServer, if the key is also - # present. This is because the WebhookServer may not actually be in charge of performing - # the SSL handshake, e.g. in case a reverse proxy is used - use_ssl = cert is not None and key is not None - - if not url_path.startswith('/'): - url_path = f'/{url_path}' - - # Create Tornado app instance - app = WebhookAppClass(url_path, self.bot, self.update_queue) - - # Form SSL Context - # An SSLError is raised if the private key does not match with the certificate - if use_ssl: - try: - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_ctx.load_cert_chain(cert, key) - except ssl.SSLError as exc: - raise TelegramError('Invalid SSL Certificate') from exc - else: - ssl_ctx = None - - # Create and start server - self.httpd = WebhookServer(listen, port, app, ssl_ctx) - - if not webhook_url: - webhook_url = self._gen_webhook_url(listen, port, url_path) - - # We pass along the cert to the webhook if present. - cert_file = open(cert, 'rb') if cert is not None else None - self._bootstrap( - max_retries=bootstrap_retries, - drop_pending_updates=drop_pending_updates, - webhook_url=webhook_url, - allowed_updates=allowed_updates, - cert=cert_file, - ip_address=ip_address, - max_connections=max_connections, - ) - if cert_file is not None: - cert_file.close() - - self.httpd.serve_forever(ready=ready) - - @staticmethod - def _gen_webhook_url(listen: str, port: int, url_path: str) -> str: - return f'https://{listen}:{port}{url_path}' - - @no_type_check - def _bootstrap( - self, - max_retries, - drop_pending_updates, - webhook_url, - allowed_updates, - cert=None, - bootstrap_interval=5, - ip_address=None, - max_connections: int = 40, - ): - retries = [0] - - def bootstrap_del_webhook(): - self.logger.debug('Deleting webhook') - if drop_pending_updates: - self.logger.debug('Dropping pending updates from Telegram server') - self.bot.delete_webhook(drop_pending_updates=drop_pending_updates) - return False - - def bootstrap_set_webhook(): - self.logger.debug('Setting webhook') - if drop_pending_updates: - self.logger.debug('Dropping pending updates from Telegram server') - self.bot.set_webhook( - url=webhook_url, - certificate=cert, - allowed_updates=allowed_updates, - ip_address=ip_address, - drop_pending_updates=drop_pending_updates, - max_connections=max_connections, - ) - return False - - def bootstrap_onerr_cb(exc): - if not isinstance(exc, Unauthorized) and (max_retries < 0 or retries[0] < max_retries): - retries[0] += 1 - self.logger.warning( - 'Failed bootstrap phase; try=%s max_retries=%s', retries[0], max_retries - ) - else: - self.logger.error('Failed bootstrap phase after %s retries (%s)', retries[0], exc) - raise exc - - # Dropping pending updates from TG can be efficiently done with the drop_pending_updates - # parameter of delete/start_webhook, even in the case of polling. Also we want to make - # sure that no webhook is configured in case of polling, so we just always call - # delete_webhook for polling - if drop_pending_updates or not webhook_url: - self._network_loop_retry( - bootstrap_del_webhook, - bootstrap_onerr_cb, - 'bootstrap del webhook', - bootstrap_interval, - ) - retries[0] = 0 - - # Restore/set webhook settings, if needed. Again, we don't know ahead if a webhook is set, - # so we set it anyhow. - if webhook_url: - self._network_loop_retry( - bootstrap_set_webhook, - bootstrap_onerr_cb, - 'bootstrap set webhook', - bootstrap_interval, - ) - - def stop(self) -> None: - """Stops the polling/webhook thread, the dispatcher and the job queue.""" - self.job_queue.stop() - with self.__lock: - if self.running or self.dispatcher.has_running_threads: - self.logger.debug('Stopping Updater and Dispatcher...') - - self.running = False - - self._stop_httpd() - self._stop_dispatcher() - self._join_threads() - - # Stop the Request instance only if it was created by the Updater - if self._request: - self._request.stop() - - @no_type_check - def _stop_httpd(self) -> None: - if self.httpd: - self.logger.debug( - 'Waiting for current webhook connection to be ' - 'closed... Send a Telegram message to the bot to exit ' - 'immediately.' - ) - self.httpd.shutdown() - self.httpd = None - - @no_type_check - def _stop_dispatcher(self) -> None: - self.logger.debug('Requesting Dispatcher to stop...') - self.dispatcher.stop() - - @no_type_check - def _join_threads(self) -> None: - for thr in self.__threads: - self.logger.debug('Waiting for %s thread to end', thr.name) - thr.join() - self.logger.debug('%s thread has ended', thr.name) - self.__threads = [] - - @no_type_check - def _signal_handler(self, signum, frame) -> None: - self.is_idle = False - if self.running: - self.logger.info( - 'Received signal %s (%s), stopping...', signum, get_signal_name(signum) - ) - if self.persistence: - # Update user_data, chat_data and bot_data before flushing - self.dispatcher.update_persistence() - self.persistence.flush() - self.stop() - if self.user_sig_handler: - self.user_sig_handler(signum, frame) - else: - self.logger.warning('Exiting immediately!') - # pylint: disable=C0415,W0212 - import os - - os._exit(1) - - def idle(self, stop_signals: Union[List, Tuple] = (SIGINT, SIGTERM, SIGABRT)) -> None: - """Blocks until one of the signals are received and stops the updater. - - Args: - stop_signals (:obj:`list` | :obj:`tuple`): List containing signals from the signal - module that should be subscribed to. :meth:`Updater.stop()` will be called on - receiving one of those signals. Defaults to (``SIGINT``, ``SIGTERM``, ``SIGABRT``). - - """ - for sig in stop_signals: - signal(sig, self._signal_handler) - - self.is_idle = True - - while self.is_idle: - sleep(1) diff --git a/telegramer/include/telegram/ext/utils/__init__.py b/telegramer/include/telegram/ext/utils/__init__.py deleted file mode 100644 index b624e1e..0000000 --- a/telegramer/include/telegram/ext/utils/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. diff --git a/telegramer/include/telegram/ext/utils/promise.py b/telegramer/include/telegram/ext/utils/promise.py deleted file mode 100644 index 86b3981..0000000 --- a/telegramer/include/telegram/ext/utils/promise.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the Promise class.""" - -import logging -from threading import Event -from typing import Callable, List, Optional, Tuple, TypeVar, Union - -from telegram.utils.deprecate import set_new_attribute_deprecated -from telegram.utils.types import JSONDict - -RT = TypeVar('RT') - - -logger = logging.getLogger(__name__) - - -class Promise: - """A simple Promise implementation for use with the run_async decorator, DelayQueue etc. - - Args: - pooled_function (:obj:`callable`): The callable that will be called concurrently. - args (:obj:`list` | :obj:`tuple`): Positional arguments for :attr:`pooled_function`. - kwargs (:obj:`dict`): Keyword arguments for :attr:`pooled_function`. - update (:class:`telegram.Update` | :obj:`object`, optional): The update this promise is - associated with. - error_handling (:obj:`bool`, optional): Whether exceptions raised by :attr:`func` - may be handled by error handlers. Defaults to :obj:`True`. - - Attributes: - pooled_function (:obj:`callable`): The callable that will be called concurrently. - args (:obj:`list` | :obj:`tuple`): Positional arguments for :attr:`pooled_function`. - kwargs (:obj:`dict`): Keyword arguments for :attr:`pooled_function`. - done (:obj:`threading.Event`): Is set when the result is available. - update (:class:`telegram.Update` | :obj:`object`): Optional. The update this promise is - associated with. - error_handling (:obj:`bool`): Optional. Whether exceptions raised by :attr:`func` - may be handled by error handlers. Defaults to :obj:`True`. - - """ - - __slots__ = ( - 'pooled_function', - 'args', - 'kwargs', - 'update', - 'error_handling', - 'done', - '_done_callback', - '_result', - '_exception', - '__dict__', - ) - - # TODO: Remove error_handling parameter once we drop the @run_async decorator - def __init__( - self, - pooled_function: Callable[..., RT], - args: Union[List, Tuple], - kwargs: JSONDict, - update: object = None, - error_handling: bool = True, - ): - self.pooled_function = pooled_function - self.args = args - self.kwargs = kwargs - self.update = update - self.error_handling = error_handling - self.done = Event() - self._done_callback: Optional[Callable] = None - self._result: Optional[RT] = None - self._exception: Optional[Exception] = None - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - def run(self) -> None: - """Calls the :attr:`pooled_function` callable.""" - try: - self._result = self.pooled_function(*self.args, **self.kwargs) - - except Exception as exc: - self._exception = exc - - finally: - self.done.set() - if self._exception is None and self._done_callback: - try: - self._done_callback(self.result()) - except Exception as exc: - logger.warning( - "`done_callback` of a Promise raised the following exception." - " The exception won't be handled by error handlers." - ) - logger.warning("Full traceback:", exc_info=exc) - - def __call__(self) -> None: - self.run() - - def result(self, timeout: float = None) -> Optional[RT]: - """Return the result of the ``Promise``. - - Args: - timeout (:obj:`float`, optional): Maximum time in seconds to wait for the result to be - calculated. ``None`` means indefinite. Default is ``None``. - - Returns: - Returns the return value of :attr:`pooled_function` or ``None`` if the ``timeout`` - expires. - - Raises: - object exception raised by :attr:`pooled_function`. - """ - self.done.wait(timeout=timeout) - if self._exception is not None: - raise self._exception # pylint: disable=raising-bad-type - return self._result - - def add_done_callback(self, callback: Callable) -> None: - """ - Callback to be run when :class:`telegram.ext.utils.promise.Promise` becomes done. - - Note: - Callback won't be called if :attr:`pooled_function` - raises an exception. - - Args: - callback (:obj:`callable`): The callable that will be called when promise is done. - callback will be called by passing ``Promise.result()`` as only positional argument. - - """ - if self.done.wait(0): - callback(self.result()) - else: - self._done_callback = callback - - @property - def exception(self) -> Optional[Exception]: - """The exception raised by :attr:`pooled_function` or ``None`` if no exception has been - raised (yet). - """ - return self._exception diff --git a/telegramer/include/telegram/ext/utils/types.py b/telegramer/include/telegram/ext/utils/types.py deleted file mode 100644 index a63e528..0000000 --- a/telegramer/include/telegram/ext/utils/types.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains custom typing aliases. - -.. versionadded:: 13.6 -""" -from typing import TypeVar, TYPE_CHECKING, Tuple, List, Dict, Any, Optional - -if TYPE_CHECKING: - from telegram.ext import CallbackContext # noqa: F401 - - -ConversationDict = Dict[Tuple[int, ...], Optional[object]] -"""Dicts as maintained by the :class:`telegram.ext.ConversationHandler`. - - .. versionadded:: 13.6 -""" - -CDCData = Tuple[List[Tuple[str, float, Dict[str, Any]]], Dict[str, str]] -"""Tuple[List[Tuple[:obj:`str`, :obj:`float`, Dict[:obj:`str`, :obj:`any`]]], \ - Dict[:obj:`str`, :obj:`str`]]: Data returned by - :attr:`telegram.ext.CallbackDataCache.persistence_data`. - - .. versionadded:: 13.6 -""" - -CCT = TypeVar('CCT', bound='CallbackContext') -"""An instance of :class:`telegram.ext.CallbackContext` or a custom subclass. - -.. versionadded:: 13.6 -""" -UD = TypeVar('UD') -"""Type of the user data for a single user. - -.. versionadded:: 13.6 -""" -CD = TypeVar('CD') -"""Type of the chat data for a single user. - -.. versionadded:: 13.6 -""" -BD = TypeVar('BD') -"""Type of the bot data. - -.. versionadded:: 13.6 -""" diff --git a/telegramer/include/telegram/ext/utils/webhookhandler.py b/telegramer/include/telegram/ext/utils/webhookhandler.py deleted file mode 100644 index 64e639b..0000000 --- a/telegramer/include/telegram/ext/utils/webhookhandler.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0114 - -import logging -from queue import Queue -from ssl import SSLContext -from threading import Event, Lock -from typing import TYPE_CHECKING, Any, Optional - -import tornado.web -from tornado import httputil -from tornado.httpserver import HTTPServer -from tornado.ioloop import IOLoop - -from telegram import Update -from telegram.ext import ExtBot -from telegram.utils.deprecate import set_new_attribute_deprecated -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - -try: - import ujson as json -except ImportError: - import json # type: ignore[no-redef] - - -class WebhookServer: - __slots__ = ( - 'http_server', - 'listen', - 'port', - 'loop', - 'logger', - 'is_running', - 'server_lock', - 'shutdown_lock', - '__dict__', - ) - - def __init__( - self, listen: str, port: int, webhook_app: 'WebhookAppClass', ssl_ctx: SSLContext - ): - self.http_server = HTTPServer(webhook_app, ssl_options=ssl_ctx) - self.listen = listen - self.port = port - self.loop: Optional[IOLoop] = None - self.logger = logging.getLogger(__name__) - self.is_running = False - self.server_lock = Lock() - self.shutdown_lock = Lock() - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - def serve_forever(self, ready: Event = None) -> None: - with self.server_lock: - IOLoop().make_current() - self.is_running = True - self.logger.debug('Webhook Server started.') - self.loop = IOLoop.current() - self.http_server.listen(self.port, address=self.listen) - - if ready is not None: - ready.set() - - self.loop.start() - self.logger.debug('Webhook Server stopped.') - self.is_running = False - - def shutdown(self) -> None: - with self.shutdown_lock: - if not self.is_running: - self.logger.warning('Webhook Server already stopped.') - return - self.loop.add_callback(self.loop.stop) # type: ignore - - def handle_error(self, request: object, client_address: str) -> None: # pylint: disable=W0613 - """Handle an error gracefully.""" - self.logger.debug( - 'Exception happened during processing of request from %s', - client_address, - exc_info=True, - ) - - -class WebhookAppClass(tornado.web.Application): - def __init__(self, webhook_path: str, bot: 'Bot', update_queue: Queue): - self.shared_objects = {"bot": bot, "update_queue": update_queue} - handlers = [(rf"{webhook_path}/?", WebhookHandler, self.shared_objects)] # noqa - tornado.web.Application.__init__(self, handlers) # type: ignore - - def log_request(self, handler: tornado.web.RequestHandler) -> None: # skipcq: PTC-W0049 - pass - - -# WebhookHandler, process webhook calls -# pylint: disable=W0223 -class WebhookHandler(tornado.web.RequestHandler): - SUPPORTED_METHODS = ["POST"] # type: ignore - - def __init__( - self, - application: tornado.web.Application, - request: httputil.HTTPServerRequest, - **kwargs: JSONDict, - ): - super().__init__(application, request, **kwargs) - self.logger = logging.getLogger(__name__) - - def initialize(self, bot: 'Bot', update_queue: Queue) -> None: - # pylint: disable=W0201 - self.bot = bot - self.update_queue = update_queue - - def set_default_headers(self) -> None: - self.set_header("Content-Type", 'application/json; charset="utf-8"') - - def post(self) -> None: - self.logger.debug('Webhook triggered') - self._validate_post() - json_string = self.request.body.decode() - data = json.loads(json_string) - self.set_status(200) - self.logger.debug('Webhook received data: %s', json_string) - update = Update.de_json(data, self.bot) - if update: - self.logger.debug('Received Update with ID %d on Webhook', update.update_id) - # handle arbitrary callback data, if necessary - if isinstance(self.bot, ExtBot): - self.bot.insert_callback_data(update) - self.update_queue.put(update) - - def _validate_post(self) -> None: - ct_header = self.request.headers.get("Content-Type", None) - if ct_header != 'application/json': - raise tornado.web.HTTPError(403) - - def write_error(self, status_code: int, **kwargs: Any) -> None: - """Log an arbitrary message. - - This is used by all other logging functions. - - It overrides ``BaseHTTPRequestHandler.log_message``, which logs to ``sys.stderr``. - - The first argument, FORMAT, is a format string for the message to be logged. If the format - string contains any % escapes requiring parameters, they should be specified as subsequent - arguments (it's just like printf!). - - The client ip is prefixed to every message. - - """ - super().write_error(status_code, **kwargs) - self.logger.debug( - "%s - - %s", - self.request.remote_ip, - "Exception in WebhookHandler", - exc_info=kwargs['exc_info'], - ) diff --git a/telegramer/include/telegram/files/__init__.py b/telegramer/include/telegram/files/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/files/animation.py b/telegramer/include/telegram/files/animation.py deleted file mode 100644 index a9f2ab5..0000000 --- a/telegramer/include/telegram/files/animation.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Animation.""" -from typing import TYPE_CHECKING, Any, Optional - -from telegram import PhotoSize, TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class Animation(TelegramObject): - """This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound). - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Video width as defined by sender. - height (:obj:`int`): Video height as defined by sender. - duration (:obj:`int`): Duration of the video in seconds as defined by sender. - thumb (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by sender. - file_name (:obj:`str`, optional): Original animation filename as defined by sender. - mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. - file_size (:obj:`int`, optional): File size. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): File identifier. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Video width as defined by sender. - height (:obj:`int`): Video height as defined by sender. - duration (:obj:`int`): Duration of the video in seconds as defined by sender. - thumb (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined by sender. - file_name (:obj:`str`): Optional. Original animation filename as defined by sender. - mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender. - file_size (:obj:`int`): Optional. File size. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'width', - 'file_id', - 'file_size', - 'file_name', - 'thumb', - 'duration', - 'mime_type', - 'height', - 'file_unique_id', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - width: int, - height: int, - duration: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.width = int(width) - self.height = int(height) - self.duration = duration - # Optionals - self.thumb = thumb - self.file_name = file_name - self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Animation']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/files/audio.py b/telegramer/include/telegram/files/audio.py deleted file mode 100644 index af6683c..0000000 --- a/telegramer/include/telegram/files/audio.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Audio.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import PhotoSize, TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class Audio(TelegramObject): - """This object represents an audio file to be treated as music by the Telegram clients. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be - the same over time and for different bots. Can't be used to download or reuse the file. - duration (:obj:`int`): Duration of the audio in seconds as defined by sender. - performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio - tags. - title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags. - file_name (:obj:`str`, optional): Original filename as defined by sender. - mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. - file_size (:obj:`int`, optional): File size. - thumb (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to - which the music file belongs. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - duration (:obj:`int`): Duration of the audio in seconds. - performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio - tags. - title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags. - file_name (:obj:`str`): Optional. Original filename as defined by sender. - mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender. - file_size (:obj:`int`): Optional. File size. - thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to - which the music file belongs. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'file_id', - 'bot', - 'file_size', - 'file_name', - 'thumb', - 'title', - 'duration', - 'performer', - 'mime_type', - 'file_unique_id', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - duration: int, - performer: str = None, - title: str = None, - mime_type: str = None, - file_size: int = None, - thumb: PhotoSize = None, - bot: 'Bot' = None, - file_name: str = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.duration = int(duration) - # Optionals - self.performer = performer - self.title = title - self.file_name = file_name - self.mime_type = mime_type - self.file_size = file_size - self.thumb = thumb - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Audio']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/files/chatphoto.py b/telegramer/include/telegram/files/chatphoto.py deleted file mode 100644 index 2f55bf9..0000000 --- a/telegramer/include/telegram/files/chatphoto.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ChatPhoto.""" -from typing import TYPE_CHECKING, Any - -from telegram import TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class ChatPhoto(TelegramObject): - """This object represents a chat photo. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`small_file_unique_id` and :attr:`big_file_unique_id` are - equal. - - Args: - small_file_id (:obj:`str`): Unique file identifier of small (160x160) chat photo. This - file_id can be used only for photo download and only for as long - as the photo is not changed. - small_file_unique_id (:obj:`str`): Unique file identifier of small (160x160) chat photo, - which is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - big_file_id (:obj:`str`): Unique file identifier of big (640x640) chat photo. This file_id - can be used only for photo download and only for as long as the photo is not changed. - big_file_unique_id (:obj:`str`): Unique file identifier of big (640x640) chat photo, - which is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - small_file_id (:obj:`str`): File identifier of small (160x160) chat photo. - This file_id can be used only for photo download and only for as long - as the photo is not changed. - small_file_unique_id (:obj:`str`): Unique file identifier of small (160x160) chat photo, - which is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - big_file_id (:obj:`str`): File identifier of big (640x640) chat photo. - This file_id can be used only for photo download and only for as long as - the photo is not changed. - big_file_unique_id (:obj:`str`): Unique file identifier of big (640x640) chat photo, - which is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - - """ - - __slots__ = ( - 'big_file_unique_id', - 'bot', - 'small_file_id', - 'small_file_unique_id', - 'big_file_id', - '_id_attrs', - ) - - def __init__( - self, - small_file_id: str, - small_file_unique_id: str, - big_file_id: str, - big_file_unique_id: str, - bot: 'Bot' = None, - **_kwargs: Any, - ): - self.small_file_id = small_file_id - self.small_file_unique_id = small_file_unique_id - self.big_file_id = big_file_id - self.big_file_unique_id = big_file_unique_id - - self.bot = bot - - self._id_attrs = ( - self.small_file_unique_id, - self.big_file_unique_id, - ) - - def get_small_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` for getting the - small (160x160) chat photo - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file( - file_id=self.small_file_id, timeout=timeout, api_kwargs=api_kwargs - ) - - def get_big_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` for getting the - big (640x640) chat photo - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.big_file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/files/contact.py b/telegramer/include/telegram/files/contact.py deleted file mode 100644 index cee769f..0000000 --- a/telegramer/include/telegram/files/contact.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Contact.""" - -from typing import Any - -from telegram import TelegramObject - - -class Contact(TelegramObject): - """This object represents a phone contact. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`phone_number` is equal. - - Args: - phone_number (:obj:`str`): Contact's phone number. - first_name (:obj:`str`): Contact's first name. - last_name (:obj:`str`, optional): Contact's last name. - user_id (:obj:`int`, optional): Contact's user identifier in Telegram. - vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - phone_number (:obj:`str`): Contact's phone number. - first_name (:obj:`str`): Contact's first name. - last_name (:obj:`str`): Optional. Contact's last name. - user_id (:obj:`int`): Optional. Contact's user identifier in Telegram. - vcard (:obj:`str`): Optional. Additional data about the contact in the form of a vCard. - - """ - - __slots__ = ('vcard', 'user_id', 'first_name', 'last_name', 'phone_number', '_id_attrs') - - def __init__( - self, - phone_number: str, - first_name: str, - last_name: str = None, - user_id: int = None, - vcard: str = None, - **_kwargs: Any, - ): - # Required - self.phone_number = str(phone_number) - self.first_name = first_name - # Optionals - self.last_name = last_name - self.user_id = user_id - self.vcard = vcard - - self._id_attrs = (self.phone_number,) diff --git a/telegramer/include/telegram/files/document.py b/telegramer/include/telegram/files/document.py deleted file mode 100644 index a0ffcef..0000000 --- a/telegramer/include/telegram/files/document.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Document.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import PhotoSize, TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class Document(TelegramObject): - """This object represents a general file - (as opposed to photos, voice messages and audio files). - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be - the same over time and for different bots. Can't be used to download or reuse the file. - thumb (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by sender. - file_name (:obj:`str`, optional): Original filename as defined by sender. - mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. - file_size (:obj:`int`, optional): File size. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): File identifier. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - thumb (:class:`telegram.PhotoSize`): Optional. Document thumbnail. - file_name (:obj:`str`): Original filename. - mime_type (:obj:`str`): Optional. MIME type of the file. - file_size (:obj:`int`): Optional. File size. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'file_id', - 'file_size', - 'file_name', - 'thumb', - 'mime_type', - 'file_unique_id', - '_id_attrs', - ) - - _id_keys = ('file_id',) - - def __init__( - self, - file_id: str, - file_unique_id: str, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - # Optionals - self.thumb = thumb - self.file_name = file_name - self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Document']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/files/file.py b/telegramer/include/telegram/files/file.py deleted file mode 100644 index ca77f9d..0000000 --- a/telegramer/include/telegram/files/file.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram File.""" -import os -import shutil -import urllib.parse as urllib_parse -from base64 import b64decode -from os.path import basename -from typing import IO, TYPE_CHECKING, Any, Optional, Union - -from telegram import TelegramObject -from telegram.passport.credentials import decrypt -from telegram.utils.helpers import is_local_file - -if TYPE_CHECKING: - from telegram import Bot, FileCredentials - - -class File(TelegramObject): - """ - This object represents a file ready to be downloaded. The file can be downloaded with - :attr:`download`. It is guaranteed that the link will be valid for at least 1 hour. When the - link expires, a new one can be requested by calling :meth:`telegram.Bot.get_file`. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Note: - * Maximum file size to download is 20 MB. - * If you obtain an instance of this class from :attr:`telegram.PassportFile.get_file`, - then it will automatically be decrypted as it downloads when you call :attr:`download()`. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - file_size (:obj:`int`, optional): Optional. File size, if known. - file_path (:obj:`str`, optional): File path. Use :attr:`download` to get the file. - bot (:obj:`telegram.Bot`, optional): Bot to use with shortcut method. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - file_size (:obj:`str`): Optional. File size. - file_path (:obj:`str`): Optional. File path. Use :attr:`download` to get the file. - - """ - - __slots__ = ( - 'bot', - 'file_id', - 'file_size', - 'file_unique_id', - 'file_path', - '_credentials', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - bot: 'Bot' = None, - file_size: int = None, - file_path: str = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - # Optionals - self.file_size = file_size - self.file_path = file_path - self.bot = bot - self._credentials: Optional['FileCredentials'] = None - - self._id_attrs = (self.file_unique_id,) - - def download( - self, custom_path: str = None, out: IO = None, timeout: int = None - ) -> Union[str, IO]: - """ - Download this file. By default, the file is saved in the current working directory with its - original filename as reported by Telegram. If the file has no filename, it the file ID will - be used as filename. If a :attr:`custom_path` is supplied, it will be saved to that path - instead. If :attr:`out` is defined, the file contents will be saved to that object using - the ``out.write`` method. - - Note: - * :attr:`custom_path` and :attr:`out` are mutually exclusive. - * If neither :attr:`custom_path` nor :attr:`out` is provided and :attr:`file_path` is - the path of a local file (which is the case when a Bot API Server is running in - local mode), this method will just return the path. - - Args: - custom_path (:obj:`str`, optional): Custom path. - out (:obj:`io.BufferedWriter`, optional): A file-like object. Must be opened for - writing in binary mode, if applicable. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - - Returns: - :obj:`str` | :obj:`io.BufferedWriter`: The same object as :attr:`out` if specified. - Otherwise, returns the filename downloaded to or the file path of the local file. - - Raises: - ValueError: If both :attr:`custom_path` and :attr:`out` are passed. - - """ - if custom_path is not None and out is not None: - raise ValueError('custom_path and out are mutually exclusive') - - local_file = is_local_file(self.file_path) - - if local_file: - url = self.file_path - else: - # Convert any UTF-8 char into a url encoded ASCII string. - url = self._get_encoded_url() - - if out: - if local_file: - with open(url, 'rb') as file: - buf = file.read() - else: - buf = self.bot.request.retrieve(url) - if self._credentials: - buf = decrypt( - b64decode(self._credentials.secret), b64decode(self._credentials.hash), buf - ) - out.write(buf) - return out - - if custom_path and local_file: - shutil.copyfile(self.file_path, custom_path) - return custom_path - - if custom_path: - filename = custom_path - elif local_file: - return self.file_path - elif self.file_path: - filename = basename(self.file_path) - else: - filename = os.path.join(os.getcwd(), self.file_id) - - buf = self.bot.request.retrieve(url, timeout=timeout) - if self._credentials: - buf = decrypt( - b64decode(self._credentials.secret), b64decode(self._credentials.hash), buf - ) - with open(filename, 'wb') as fobj: - fobj.write(buf) - return filename - - def _get_encoded_url(self) -> str: - """Convert any UTF-8 char in :obj:`File.file_path` into a url encoded ASCII string.""" - sres = urllib_parse.urlsplit(self.file_path) - return urllib_parse.urlunsplit( - urllib_parse.SplitResult( - sres.scheme, sres.netloc, urllib_parse.quote(sres.path), sres.query, sres.fragment - ) - ) - - def download_as_bytearray(self, buf: bytearray = None) -> bytes: - """Download this file and return it as a bytearray. - - Args: - buf (:obj:`bytearray`, optional): Extend the given bytearray with the downloaded data. - - Returns: - :obj:`bytearray`: The same object as :attr:`buf` if it was specified. Otherwise a newly - allocated :obj:`bytearray`. - - """ - if buf is None: - buf = bytearray() - if is_local_file(self.file_path): - with open(self.file_path, "rb") as file: - buf.extend(file.read()) - else: - buf.extend(self.bot.request.retrieve(self._get_encoded_url())) - return buf - - def set_credentials(self, credentials: 'FileCredentials') -> None: - """Sets the passport credentials for the file. - - Args: - credentials (:class:`telegram.FileCredentials`): The credentials. - """ - self._credentials = credentials diff --git a/telegramer/include/telegram/files/inputfile.py b/telegramer/include/telegram/files/inputfile.py deleted file mode 100644 index 2c3196f..0000000 --- a/telegramer/include/telegram/files/inputfile.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=W0622,E0611 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram InputFile.""" - -import imghdr -import logging -import mimetypes -import os -from typing import IO, Optional, Tuple, Union -from uuid import uuid4 - -from telegram.utils.deprecate import set_new_attribute_deprecated - -DEFAULT_MIME_TYPE = 'application/octet-stream' -logger = logging.getLogger(__name__) - - -class InputFile: - """This object represents a Telegram InputFile. - - Args: - obj (:obj:`File handler` | :obj:`bytes`): An open file descriptor or the files content as - bytes. - filename (:obj:`str`, optional): Filename for this InputFile. - attach (:obj:`bool`, optional): Whether this should be send as one file or is part of a - collection of files. - - Raises: - TelegramError - - Attributes: - input_file_content (:obj:`bytes`): The binary content of the file to send. - filename (:obj:`str`): Optional. Filename for the file to be sent. - attach (:obj:`str`): Optional. Attach id for sending multiple files. - - """ - - __slots__ = ('filename', 'attach', 'input_file_content', 'mimetype', '__dict__') - - def __init__(self, obj: Union[IO, bytes], filename: str = None, attach: bool = None): - self.filename = None - if isinstance(obj, bytes): - self.input_file_content = obj - else: - self.input_file_content = obj.read() - self.attach = 'attached' + uuid4().hex if attach else None - - if filename: - self.filename = filename - elif hasattr(obj, 'name') and not isinstance(obj.name, int): # type: ignore[union-attr] - self.filename = os.path.basename(obj.name) # type: ignore[union-attr] - - image_mime_type = self.is_image(self.input_file_content) - if image_mime_type: - self.mimetype = image_mime_type - elif self.filename: - self.mimetype = mimetypes.guess_type(self.filename)[0] or DEFAULT_MIME_TYPE - else: - self.mimetype = DEFAULT_MIME_TYPE - - if not self.filename: - self.filename = self.mimetype.replace('/', '.') - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - @property - def field_tuple(self) -> Tuple[str, bytes, str]: # skipcq: PY-D0003 - return self.filename, self.input_file_content, self.mimetype - - @staticmethod - def is_image(stream: bytes) -> Optional[str]: - """Check if the content file is an image by analyzing its headers. - - Args: - stream (:obj:`bytes`): A byte stream representing the content of a file. - - Returns: - :obj:`str` | :obj:`None`: The mime-type of an image, if the input is an image, or - :obj:`None` else. - - """ - try: - image = imghdr.what(None, stream) - if image: - return f'image/{image}' - return None - except Exception: - logger.debug( - "Could not parse file content. Assuming that file is not an image.", exc_info=True - ) - return None - - @staticmethod - def is_file(obj: object) -> bool: # skipcq: PY-D0003 - return hasattr(obj, 'read') - - def to_dict(self) -> Optional[str]: - """See :meth:`telegram.TelegramObject.to_dict`.""" - if self.attach: - return 'attach://' + self.attach - return None diff --git a/telegramer/include/telegram/files/inputmedia.py b/telegramer/include/telegram/files/inputmedia.py deleted file mode 100644 index c4899fb..0000000 --- a/telegramer/include/telegram/files/inputmedia.py +++ /dev/null @@ -1,525 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""Base class for Telegram InputMedia Objects.""" - -from typing import Union, List, Tuple - -from telegram import ( - Animation, - Audio, - Document, - InputFile, - PhotoSize, - TelegramObject, - Video, - MessageEntity, -) -from telegram.utils.helpers import DEFAULT_NONE, parse_file_input -from telegram.utils.types import FileInput, JSONDict, ODVInput - - -class InputMedia(TelegramObject): - """Base class for Telegram InputMedia Objects. - - See :class:`telegram.InputMediaAnimation`, :class:`telegram.InputMediaAudio`, - :class:`telegram.InputMediaDocument`, :class:`telegram.InputMediaPhoto` and - :class:`telegram.InputMediaVideo` for detailed use. - - """ - - __slots__ = () - caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...], None] = None - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - if self.caption_entities: - data['caption_entities'] = [ - ce.to_dict() for ce in self.caption_entities # pylint: disable=E1133 - ] - - return data - - -class InputMediaAnimation(InputMedia): - """Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. - - Note: - When using a :class:`telegram.Animation` for the :attr:`media` attribute. It will take the - width, height and duration from that video, unless otherwise specified with the optional - arguments. - - Args: - media (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Animation`): File to send. Pass a - file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP - URL for Telegram to get a file from the Internet. Lastly you can pass an existing - :class:`telegram.Animation` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the animation, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of - the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - caption (:obj:`str`, optional): Caption of the animation to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of parse_mode. - width (:obj:`int`, optional): Animation width. - height (:obj:`int`, optional): Animation height. - duration (:obj:`int`, optional): Animation duration. - - Attributes: - type (:obj:`str`): ``animation``. - media (:obj:`str` | :class:`telegram.InputFile`): Animation to send. - caption (:obj:`str`): Optional. Caption of the document to be sent. - parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption. - thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send. - width (:obj:`int`): Optional. Animation width. - height (:obj:`int`): Optional. Animation height. - duration (:obj:`int`): Optional. Animation duration. - - """ - - __slots__ = ( - 'caption_entities', - 'width', - 'media', - 'thumb', - 'caption', - 'duration', - 'parse_mode', - 'height', - 'type', - ) - - def __init__( - self, - media: Union[FileInput, Animation], - thumb: FileInput = None, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - width: int = None, - height: int = None, - duration: int = None, - caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, - filename: str = None, - ): - self.type = 'animation' - - if isinstance(media, Animation): - self.media: Union[str, InputFile] = media.file_id - self.width = media.width - self.height = media.height - self.duration = media.duration - else: - self.media = parse_file_input(media, attach=True, filename=filename) - - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - if width: - self.width = width - if height: - self.height = height - if duration: - self.duration = duration - - -class InputMediaPhoto(InputMedia): - """Represents a photo to be sent. - - Args: - media (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.PhotoSize`): File to send. Pass a - file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP - URL for Telegram to get a file from the Internet. Lastly you can pass an existing - :class:`telegram.PhotoSize` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the photo, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of parse_mode. - - Attributes: - type (:obj:`str`): ``photo``. - media (:obj:`str` | :class:`telegram.InputFile`): Photo to send. - caption (:obj:`str`): Optional. Caption of the document to be sent. - parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption. - - """ - - __slots__ = ('caption_entities', 'media', 'caption', 'parse_mode', 'type') - - def __init__( - self, - media: Union[FileInput, PhotoSize], - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, - filename: str = None, - ): - self.type = 'photo' - self.media = parse_file_input(media, PhotoSize, attach=True, filename=filename) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - - -class InputMediaVideo(InputMedia): - """Represents a video to be sent. - - Note: - * When using a :class:`telegram.Video` for the :attr:`media` attribute. It will take the - width, height and duration from that video, unless otherwise specified with the optional - arguments. - * ``thumb`` will be ignored for small video files, for which Telegram can easily - generate thumb nails. However, this behaviour is undocumented and might be changed - by Telegram. - - Args: - media (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Video`): File to send. Pass a - file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP - URL for Telegram to get a file from the Internet. Lastly you can pass an existing - :class:`telegram.Video` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the video, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - caption (:obj:`str`, optional): Caption of the video to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of parse_mode. - width (:obj:`int`, optional): Video width. - height (:obj:`int`, optional): Video height. - duration (:obj:`int`, optional): Video duration. - supports_streaming (:obj:`bool`, optional): Pass :obj:`True`, if the uploaded video is - suitable for streaming. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of - the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - - Attributes: - type (:obj:`str`): ``video``. - media (:obj:`str` | :class:`telegram.InputFile`): Video file to send. - caption (:obj:`str`): Optional. Caption of the document to be sent. - parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption. - width (:obj:`int`): Optional. Video width. - height (:obj:`int`): Optional. Video height. - duration (:obj:`int`): Optional. Video duration. - supports_streaming (:obj:`bool`): Optional. Pass :obj:`True`, if the uploaded video is - suitable for streaming. - thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send. - - """ - - __slots__ = ( - 'caption_entities', - 'width', - 'media', - 'thumb', - 'supports_streaming', - 'caption', - 'duration', - 'parse_mode', - 'height', - 'type', - ) - - def __init__( - self, - media: Union[FileInput, Video], - caption: str = None, - width: int = None, - height: int = None, - duration: int = None, - supports_streaming: bool = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, - filename: str = None, - ): - self.type = 'video' - - if isinstance(media, Video): - self.media: Union[str, InputFile] = media.file_id - self.width = media.width - self.height = media.height - self.duration = media.duration - else: - self.media = parse_file_input(media, attach=True, filename=filename) - - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - if width: - self.width = width - if height: - self.height = height - if duration: - self.duration = duration - if supports_streaming: - self.supports_streaming = supports_streaming - - -class InputMediaAudio(InputMedia): - """Represents an audio file to be treated as music to be sent. - - Note: - When using a :class:`telegram.Audio` for the :attr:`media` attribute. It will take the - duration, performer and title from that video, unless otherwise specified with the - optional arguments. - - Args: - media (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Audio`): - File to send. Pass a - file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP - URL for Telegram to get a file from the Internet. Lastly you can pass an existing - :class:`telegram.Audio` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the audio, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - caption (:obj:`str`, optional): Caption of the audio to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of parse_mode. - duration (:obj:`int`): Duration of the audio in seconds as defined by sender. - performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio - tags. - title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of - the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - - Attributes: - type (:obj:`str`): ``audio``. - media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send. - caption (:obj:`str`): Optional. Caption of the document to be sent. - parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption. - duration (:obj:`int`): Duration of the audio in seconds. - performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio - tags. - title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags. - thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send. - - """ - - __slots__ = ( - 'caption_entities', - 'media', - 'thumb', - 'caption', - 'title', - 'duration', - 'type', - 'parse_mode', - 'performer', - ) - - def __init__( - self, - media: Union[FileInput, Audio], - thumb: FileInput = None, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - duration: int = None, - performer: str = None, - title: str = None, - caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, - filename: str = None, - ): - self.type = 'audio' - - if isinstance(media, Audio): - self.media: Union[str, InputFile] = media.file_id - self.duration = media.duration - self.performer = media.performer - self.title = media.title - else: - self.media = parse_file_input(media, attach=True, filename=filename) - - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - if duration: - self.duration = duration - if performer: - self.performer = performer - if title: - self.title = title - - -class InputMediaDocument(InputMedia): - """Represents a general file to be sent. - - Args: - media (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ - :class:`telegram.Document`): File to send. Pass a - file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP - URL for Telegram to get a file from the Internet. Lastly you can pass an existing - :class:`telegram.Document` object to send. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the document, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. - - .. versionadded:: 13.1 - caption (:obj:`str`, optional): Caption of the document to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of parse_mode. - thumb (`filelike object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of - the file sent; can be ignored if - thumbnail generation for the file is supported server-side. The thumbnail should be - in JPEG format and less than 200 kB in size. A thumbnail's width and height should - not exceed 320. Ignored if the file is not uploaded using multipart/form-data. - Thumbnails can't be reused and can be only uploaded as a new file. - - .. versionchanged:: 13.2 - Accept :obj:`bytes` as input. - disable_content_type_detection (:obj:`bool`, optional): Disables automatic server-side - content type detection for files uploaded using multipart/form-data. Always true, if - the document is sent as part of an album. - - Attributes: - type (:obj:`str`): ``document``. - media (:obj:`str` | :class:`telegram.InputFile`): File to send. - caption (:obj:`str`): Optional. Caption of the document to be sent. - parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption. - thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send. - disable_content_type_detection (:obj:`bool`): Optional. Disables automatic server-side - content type detection for files uploaded using multipart/form-data. Always true, if - the document is sent as part of an album. - - """ - - __slots__ = ( - 'caption_entities', - 'media', - 'thumb', - 'caption', - 'parse_mode', - 'type', - 'disable_content_type_detection', - ) - - def __init__( - self, - media: Union[FileInput, Document], - thumb: FileInput = None, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_content_type_detection: bool = None, - caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, - filename: str = None, - ): - self.type = 'document' - self.media = parse_file_input(media, Document, attach=True, filename=filename) - - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.disable_content_type_detection = disable_content_type_detection diff --git a/telegramer/include/telegram/files/location.py b/telegramer/include/telegram/files/location.py deleted file mode 100644 index a5f5065..0000000 --- a/telegramer/include/telegram/files/location.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Location.""" - -from typing import Any - -from telegram import TelegramObject - - -class Location(TelegramObject): - """This object represents a point on the map. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`longitute` and :attr:`latitude` are equal. - - Args: - longitude (:obj:`float`): Longitude as defined by sender. - latitude (:obj:`float`): Latitude as defined by sender. - horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the location, - measured in meters; 0-1500. - live_period (:obj:`int`, optional): Time relative to the message sending date, during which - the location can be updated, in seconds. For active live locations only. - heading (:obj:`int`, optional): The direction in which user is moving, in degrees; 1-360. - For active live locations only. - proximity_alert_radius (:obj:`int`, optional): Maximum distance for proximity alerts about - approaching another chat member, in meters. For sent live locations only. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - longitude (:obj:`float`): Longitude as defined by sender. - latitude (:obj:`float`): Latitude as defined by sender. - horizontal_accuracy (:obj:`float`): Optional. The radius of uncertainty for the location, - measured in meters. - live_period (:obj:`int`): Optional. Time relative to the message sending date, during which - the location can be updated, in seconds. For active live locations only. - heading (:obj:`int`): Optional. The direction in which user is moving, in degrees. - For active live locations only. - proximity_alert_radius (:obj:`int`): Optional. Maximum distance for proximity alerts about - approaching another chat member, in meters. For sent live locations only. - - """ - - __slots__ = ( - 'longitude', - 'horizontal_accuracy', - 'proximity_alert_radius', - 'live_period', - 'latitude', - 'heading', - '_id_attrs', - ) - - def __init__( - self, - longitude: float, - latitude: float, - horizontal_accuracy: float = None, - live_period: int = None, - heading: int = None, - proximity_alert_radius: int = None, - **_kwargs: Any, - ): - # Required - self.longitude = float(longitude) - self.latitude = float(latitude) - - # Optionals - self.horizontal_accuracy = float(horizontal_accuracy) if horizontal_accuracy else None - self.live_period = int(live_period) if live_period else None - self.heading = int(heading) if heading else None - self.proximity_alert_radius = ( - int(proximity_alert_radius) if proximity_alert_radius else None - ) - - self._id_attrs = (self.longitude, self.latitude) diff --git a/telegramer/include/telegram/files/photosize.py b/telegramer/include/telegram/files/photosize.py deleted file mode 100644 index 9a7988b..0000000 --- a/telegramer/include/telegram/files/photosize.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram PhotoSize.""" - -from typing import TYPE_CHECKING, Any - -from telegram import TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class PhotoSize(TelegramObject): - """This object represents one size of a photo or a file/sticker thumbnail. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Photo width. - height (:obj:`int`): Photo height. - file_size (:obj:`int`, optional): File size. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Photo width. - height (:obj:`int`): Photo height. - file_size (:obj:`int`): Optional. File size. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ('bot', 'width', 'file_id', 'file_size', 'height', 'file_unique_id', '_id_attrs') - - def __init__( - self, - file_id: str, - file_unique_id: str, - width: int, - height: int, - file_size: int = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.width = int(width) - self.height = int(height) - # Optionals - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/files/sticker.py b/telegramer/include/telegram/files/sticker.py deleted file mode 100644 index e3f22a9..0000000 --- a/telegramer/include/telegram/files/sticker.py +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains objects that represents stickers.""" - -from typing import TYPE_CHECKING, Any, List, Optional, ClassVar - -from telegram import PhotoSize, TelegramObject, constants -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class Sticker(TelegramObject): - """This object represents a sticker. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Note: - As of v13.11 ``is_video`` is a required argument and therefore the order of the - arguments had to be changed. Use keyword arguments to make sure that the arguments are - passed correctly. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Sticker width. - height (:obj:`int`): Sticker height. - is_animated (:obj:`bool`): :obj:`True`, if the sticker is animated. - is_video (:obj:`bool`): :obj:`True`, if the sticker is a video sticker. - - .. versionadded:: 13.11 - thumb (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the .WEBP or .JPG - format. - emoji (:obj:`str`, optional): Emoji associated with the sticker - set_name (:obj:`str`, optional): Name of the sticker set to which the sticker - belongs. - mask_position (:class:`telegram.MaskPosition`, optional): For mask stickers, the - position where the mask should be placed. - file_size (:obj:`int`, optional): File size. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Sticker width. - height (:obj:`int`): Sticker height. - is_animated (:obj:`bool`): :obj:`True`, if the sticker is animated. - is_video (:obj:`bool`): :obj:`True`, if the sticker is a video sticker. - - .. versionadded:: 13.11 - thumb (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the .webp or .jpg - format. - emoji (:obj:`str`): Optional. Emoji associated with the sticker. - set_name (:obj:`str`): Optional. Name of the sticker set to which the sticker belongs. - mask_position (:class:`telegram.MaskPosition`): Optional. For mask stickers, the position - where the mask should be placed. - file_size (:obj:`int`): Optional. File size. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'width', - 'file_id', - 'is_animated', - 'is_video', - 'file_size', - 'thumb', - 'set_name', - 'mask_position', - 'height', - 'file_unique_id', - 'emoji', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - width: int, - height: int, - is_animated: bool, - is_video: bool, - thumb: PhotoSize = None, - emoji: str = None, - file_size: int = None, - set_name: str = None, - mask_position: 'MaskPosition' = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.width = int(width) - self.height = int(height) - self.is_animated = is_animated - self.is_video = is_video - # Optionals - self.thumb = thumb - self.emoji = emoji - self.file_size = file_size - self.set_name = set_name - self.mask_position = mask_position - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Sticker']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - data['mask_position'] = MaskPosition.de_json(data.get('mask_position'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) - - -class StickerSet(TelegramObject): - """This object represents a sticker set. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`name` is equal. - - Note: - As of v13.11 ``is_video`` is a required argument and therefore the order of the - arguments had to be changed. Use keyword arguments to make sure that the arguments are - passed correctly. - - Args: - name (:obj:`str`): Sticker set name. - title (:obj:`str`): Sticker set title. - is_animated (:obj:`bool`): :obj:`True`, if the sticker set contains animated stickers. - is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers. - - .. versionadded:: 13.11 - contains_masks (:obj:`bool`): :obj:`True`, if the sticker set contains masks. - stickers (List[:class:`telegram.Sticker`]): List of all set stickers. - thumb (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the ``.WEBP``, - ``.TGS``, or ``.WEBM`` format. - - Attributes: - name (:obj:`str`): Sticker set name. - title (:obj:`str`): Sticker set title. - is_animated (:obj:`bool`): :obj:`True`, if the sticker set contains animated stickers. - is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers. - - .. versionadded:: 13.11 - contains_masks (:obj:`bool`): :obj:`True`, if the sticker set contains masks. - stickers (List[:class:`telegram.Sticker`]): List of all set stickers. - thumb (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the ``.WEBP``, - ``.TGS`` or ``.WEBM`` format. - - """ - - __slots__ = ( - 'is_animated', - 'is_video', - 'contains_masks', - 'thumb', - 'title', - 'stickers', - 'name', - '_id_attrs', - ) - - def __init__( - self, - name: str, - title: str, - is_animated: bool, - contains_masks: bool, - stickers: List[Sticker], - is_video: bool, - thumb: PhotoSize = None, - **_kwargs: Any, - ): - self.name = name - self.title = title - self.is_animated = is_animated - self.is_video = is_video - self.contains_masks = contains_masks - self.stickers = stickers - # Optionals - self.thumb = thumb - - self._id_attrs = (self.name,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['StickerSet']: - """See :meth:`telegram.TelegramObject.de_json`.""" - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - data['stickers'] = Sticker.de_list(data.get('stickers'), bot) - - return cls(bot=bot, **data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['stickers'] = [s.to_dict() for s in data.get('stickers')] - - return data - - -class MaskPosition(TelegramObject): - """This object describes the position on faces where a mask should be placed by default. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`point`, :attr:`x_shift`, :attr:`y_shift` and, :attr:`scale` - are equal. - - Attributes: - point (:obj:`str`): The part of the face relative to which the mask should be placed. - One of ``'forehead'``, ``'eyes'``, ``'mouth'``, or ``'chin'``. - x_shift (:obj:`float`): Shift by X-axis measured in widths of the mask scaled to the face - size, from left to right. - y_shift (:obj:`float`): Shift by Y-axis measured in heights of the mask scaled to the face - size, from top to bottom. - scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size. - - Note: - :attr:`type` should be one of the following: `forehead`, `eyes`, `mouth` or `chin`. You can - use the class constants for those. - - Args: - point (:obj:`str`): The part of the face relative to which the mask should be placed. - One of ``'forehead'``, ``'eyes'``, ``'mouth'``, or ``'chin'``. - x_shift (:obj:`float`): Shift by X-axis measured in widths of the mask scaled to the face - size, from left to right. For example, choosing -1.0 will place mask just to the left - of the default mask position. - y_shift (:obj:`float`): Shift by Y-axis measured in heights of the mask scaled to the face - size, from top to bottom. For example, 1.0 will place the mask just below the default - mask position. - scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size. - - """ - - __slots__ = ('point', 'scale', 'x_shift', 'y_shift', '_id_attrs') - - FOREHEAD: ClassVar[str] = constants.STICKER_FOREHEAD - """:const:`telegram.constants.STICKER_FOREHEAD`""" - EYES: ClassVar[str] = constants.STICKER_EYES - """:const:`telegram.constants.STICKER_EYES`""" - MOUTH: ClassVar[str] = constants.STICKER_MOUTH - """:const:`telegram.constants.STICKER_MOUTH`""" - CHIN: ClassVar[str] = constants.STICKER_CHIN - """:const:`telegram.constants.STICKER_CHIN`""" - - def __init__(self, point: str, x_shift: float, y_shift: float, scale: float, **_kwargs: Any): - self.point = point - self.x_shift = x_shift - self.y_shift = y_shift - self.scale = scale - - self._id_attrs = (self.point, self.x_shift, self.y_shift, self.scale) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MaskPosition']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if data is None: - return None - - return cls(**data) diff --git a/telegramer/include/telegram/files/venue.py b/telegramer/include/telegram/files/venue.py deleted file mode 100644 index aad46db..0000000 --- a/telegramer/include/telegram/files/venue.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Venue.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import Location, TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class Venue(TelegramObject): - """This object represents a venue. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`location` and :attr:`title` are equal. - - Note: - Foursquare details and Google Pace details are mutually exclusive. However, this - behaviour is undocumented and might be changed by Telegram. - - Args: - location (:class:`telegram.Location`): Venue location. - title (:obj:`str`): Name of the venue. - address (:obj:`str`): Address of the venue. - foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue. - foursquare_type (:obj:`str`, optional): Foursquare type of the venue. (For example, - "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".) - google_place_id (:obj:`str`, optional): Google Places identifier of the venue. - google_place_type (:obj:`str`, optional): Google Places type of the venue. (See - `supported types `_.) - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - location (:class:`telegram.Location`): Venue location. - title (:obj:`str`): Name of the venue. - address (:obj:`str`): Address of the venue. - foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue. - foursquare_type (:obj:`str`): Optional. Foursquare type of the venue. - google_place_id (:obj:`str`): Optional. Google Places identifier of the venue. - google_place_type (:obj:`str`): Optional. Google Places type of the venue. - - """ - - __slots__ = ( - 'google_place_type', - 'location', - 'title', - 'address', - 'foursquare_type', - 'foursquare_id', - 'google_place_id', - '_id_attrs', - ) - - def __init__( - self, - location: Location, - title: str, - address: str, - foursquare_id: str = None, - foursquare_type: str = None, - google_place_id: str = None, - google_place_type: str = None, - **_kwargs: Any, - ): - # Required - self.location = location - self.title = title - self.address = address - # Optionals - self.foursquare_id = foursquare_id - self.foursquare_type = foursquare_type - self.google_place_id = google_place_id - self.google_place_type = google_place_type - - self._id_attrs = (self.location, self.title) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Venue']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['location'] = Location.de_json(data.get('location'), bot) - - return cls(**data) diff --git a/telegramer/include/telegram/files/video.py b/telegramer/include/telegram/files/video.py deleted file mode 100644 index 92e7c5e..0000000 --- a/telegramer/include/telegram/files/video.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Video.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import PhotoSize, TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class Video(TelegramObject): - """This object represents a video file. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Video width as defined by sender. - height (:obj:`int`): Video height as defined by sender. - duration (:obj:`int`): Duration of the video in seconds as defined by sender. - thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail. - file_name (:obj:`str`, optional): Original filename as defined by sender. - mime_type (:obj:`str`, optional): Mime type of a file as defined by sender. - file_size (:obj:`int`, optional): File size. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - width (:obj:`int`): Video width as defined by sender. - height (:obj:`int`): Video height as defined by sender. - duration (:obj:`int`): Duration of the video in seconds as defined by sender. - thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail. - file_name (:obj:`str`): Optional. Original filename as defined by sender. - mime_type (:obj:`str`): Optional. Mime type of a file as defined by sender. - file_size (:obj:`int`): Optional. File size. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'width', - 'file_id', - 'file_size', - 'file_name', - 'thumb', - 'duration', - 'mime_type', - 'height', - 'file_unique_id', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - width: int, - height: int, - duration: int, - thumb: PhotoSize = None, - mime_type: str = None, - file_size: int = None, - bot: 'Bot' = None, - file_name: str = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.width = int(width) - self.height = int(height) - self.duration = int(duration) - # Optionals - self.thumb = thumb - self.file_name = file_name - self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Video']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/files/videonote.py b/telegramer/include/telegram/files/videonote.py deleted file mode 100644 index 17d6207..0000000 --- a/telegramer/include/telegram/files/videonote.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram VideoNote.""" - -from typing import TYPE_CHECKING, Optional, Any - -from telegram import PhotoSize, TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class VideoNote(TelegramObject): - """This object represents a video message (available in Telegram apps as of v.4.0). - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - length (:obj:`int`): Video width and height (diameter of the video message) as defined - by sender. - duration (:obj:`int`): Duration of the video in seconds as defined by sender. - thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail. - file_size (:obj:`int`, optional): File size. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - length (:obj:`int`): Video width and height as defined by sender. - duration (:obj:`int`): Duration of the video in seconds as defined by sender. - thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail. - file_size (:obj:`int`): Optional. File size. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'length', - 'file_id', - 'file_size', - 'thumb', - 'duration', - 'file_unique_id', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - length: int, - duration: int, - thumb: PhotoSize = None, - file_size: int = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.length = int(length) - self.duration = int(duration) - # Optionals - self.thumb = thumb - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VideoNote']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/files/voice.py b/telegramer/include/telegram/files/voice.py deleted file mode 100644 index c878282..0000000 --- a/telegramer/include/telegram/files/voice.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Voice.""" - -from typing import TYPE_CHECKING, Any - -from telegram import TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File - - -class Voice(TelegramObject): - """This object represents a voice note. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - duration (:obj:`int`, optional): Duration of the audio in seconds as defined by sender. - mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. - file_size (:obj:`int`, optional): File size. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - duration (:obj:`int`): Duration of the audio in seconds as defined by sender. - mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender. - file_size (:obj:`int`): Optional. File size. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'file_id', - 'file_size', - 'duration', - 'mime_type', - 'file_unique_id', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - duration: int, - mime_type: str = None, - file_size: int = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.duration = int(duration) - # Optionals - self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegramer/include/telegram/forcereply.py b/telegramer/include/telegram/forcereply.py deleted file mode 100644 index 792b211..0000000 --- a/telegramer/include/telegram/forcereply.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ForceReply.""" - -from typing import Any - -from telegram import ReplyMarkup - - -class ForceReply(ReplyMarkup): - """ - Upon receiving a message with this object, Telegram clients will display a reply interface to - the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be - extremely useful if you want to create user-friendly step-by-step interfaces without having - to sacrifice privacy mode. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`selective` is equal. - - Args: - selective (:obj:`bool`, optional): Use this parameter if you want to force reply from - specific users only. Targets: - - 1) Users that are @mentioned in the :attr:`~telegram.Message.text` of the - :class:`telegram.Message` object. - 2) If the bot's message is a reply (has ``reply_to_message_id``), sender of the - original message. - - input_field_placeholder (:obj:`str`, optional): The placeholder to be shown in the input - field when the reply is active; 1-64 characters. - - .. versionadded:: 13.7 - - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - force_reply (:obj:`True`): Shows reply interface to the user, as if they manually selected - the bots message and tapped 'Reply'. - selective (:obj:`bool`): Optional. Force reply from specific users only. - input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input - field when the reply is active. - - .. versionadded:: 13.7 - - """ - - __slots__ = ('selective', 'force_reply', 'input_field_placeholder', '_id_attrs') - - def __init__( - self, - force_reply: bool = True, - selective: bool = False, - input_field_placeholder: str = None, - **_kwargs: Any, - ): - # Required - self.force_reply = bool(force_reply) - # Optionals - self.selective = bool(selective) - self.input_field_placeholder = input_field_placeholder - - self._id_attrs = (self.selective,) diff --git a/telegramer/include/telegram/games/__init__.py b/telegramer/include/telegram/games/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/games/callbackgame.py b/telegramer/include/telegram/games/callbackgame.py deleted file mode 100644 index 6803a44..0000000 --- a/telegramer/include/telegram/games/callbackgame.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram CallbackGame.""" - -from telegram import TelegramObject - - -class CallbackGame(TelegramObject): - """A placeholder, currently holds no information. Use BotFather to set up your game.""" - - __slots__ = () diff --git a/telegramer/include/telegram/games/game.py b/telegramer/include/telegram/games/game.py deleted file mode 100644 index 86eb2ab..0000000 --- a/telegramer/include/telegram/games/game.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Game.""" - -import sys -from typing import TYPE_CHECKING, Any, Dict, List, Optional - -from telegram import Animation, MessageEntity, PhotoSize, TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class Game(TelegramObject): - """ - This object represents a game. Use `BotFather `_ to create and edit - games, their short names will act as unique identifiers. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`title`, :attr:`description` and :attr:`photo` are equal. - - Args: - title (:obj:`str`): Title of the game. - description (:obj:`str`): Description of the game. - photo (List[:class:`telegram.PhotoSize`]): Photo that will be displayed in the game message - in chats. - text (:obj:`str`, optional): Brief description of the game or high scores included in the - game message. Can be automatically edited to include current high scores for the game - when the bot calls :meth:`telegram.Bot.set_game_score`, or manually edited - using :meth:`telegram.Bot.edit_message_text`. - 0-4096 characters. Also found as ``telegram.constants.MAX_MESSAGE_LENGTH``. - text_entities (List[:class:`telegram.MessageEntity`], optional): Special entities that - appear in text, such as usernames, URLs, bot commands, etc. - animation (:class:`telegram.Animation`, optional): Animation that will be displayed in the - game message in chats. Upload via `BotFather `_. - - Attributes: - title (:obj:`str`): Title of the game. - description (:obj:`str`): Description of the game. - photo (List[:class:`telegram.PhotoSize`]): Photo that will be displayed in the game message - in chats. - text (:obj:`str`): Optional. Brief description of the game or high scores included in the - game message. Can be automatically edited to include current high scores for the game - when the bot calls :meth:`telegram.Bot.set_game_score`, or manually edited - using :meth:`telegram.Bot.edit_message_text`. - text_entities (List[:class:`telegram.MessageEntity`]): Optional. Special entities that - appear in text, such as usernames, URLs, bot commands, etc. - animation (:class:`telegram.Animation`): Optional. Animation that will be displayed in the - game message in chats. Upload via `BotFather `_. - - """ - - __slots__ = ( - 'title', - 'photo', - 'description', - 'text_entities', - 'text', - 'animation', - '_id_attrs', - ) - - def __init__( - self, - title: str, - description: str, - photo: List[PhotoSize], - text: str = None, - text_entities: List[MessageEntity] = None, - animation: Animation = None, - **_kwargs: Any, - ): - # Required - self.title = title - self.description = description - self.photo = photo - # Optionals - self.text = text - self.text_entities = text_entities or [] - self.animation = animation - - self._id_attrs = (self.title, self.description, self.photo) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Game']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['photo'] = PhotoSize.de_list(data.get('photo'), bot) - data['text_entities'] = MessageEntity.de_list(data.get('text_entities'), bot) - data['animation'] = Animation.de_json(data.get('animation'), bot) - - return cls(**data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['photo'] = [p.to_dict() for p in self.photo] - if self.text_entities: - data['text_entities'] = [x.to_dict() for x in self.text_entities] - - return data - - def parse_text_entity(self, entity: MessageEntity) -> str: - """Returns the text from a given :class:`telegram.MessageEntity`. - - Note: - This method is present because Telegram calculates the offset and length in - UTF-16 codepoint pairs, which some versions of Python don't handle automatically. - (That is, you can't just slice ``Message.text`` with the offset and length.) - - Args: - entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must - be an entity that belongs to this message. - - Returns: - :obj:`str`: The text of the given entity. - - Raises: - RuntimeError: If this game has no text. - - """ - if not self.text: - raise RuntimeError("This Game has no 'text'.") - - # Is it a narrow build, if so we don't need to convert - if sys.maxunicode == 0xFFFF: - return self.text[entity.offset : entity.offset + entity.length] - entity_text = self.text.encode('utf-16-le') - entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2] - - return entity_text.decode('utf-16-le') - - def parse_text_entities(self, types: List[str] = None) -> Dict[MessageEntity, str]: - """ - Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`. - It contains entities from this message filtered by their ``type`` attribute as the key, and - the text that each entity belongs to as the value of the :obj:`dict`. - - Note: - This method should always be used instead of the :attr:`text_entities` attribute, since - it calculates the correct substring from the message text based on UTF-16 codepoints. - See :attr:`parse_text_entity` for more info. - - Args: - types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the - ``type`` attribute of an entity is contained in this list, it will be returned. - Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`. - - Returns: - Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to - the text that belongs to them, calculated based on UTF-16 codepoints. - - """ - if types is None: - types = MessageEntity.ALL_TYPES - - return { - entity: self.parse_text_entity(entity) - for entity in (self.text_entities or []) - if entity.type in types - } - - def __hash__(self) -> int: - return hash((self.title, self.description, tuple(p for p in self.photo))) diff --git a/telegramer/include/telegram/games/gamehighscore.py b/telegramer/include/telegram/games/gamehighscore.py deleted file mode 100644 index 5967dda..0000000 --- a/telegramer/include/telegram/games/gamehighscore.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram GameHighScore.""" - -from typing import TYPE_CHECKING, Optional - -from telegram import TelegramObject, User -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class GameHighScore(TelegramObject): - """This object represents one row of the high scores table for a game. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`position`, :attr:`user` and :attr:`score` are equal. - - Args: - position (:obj:`int`): Position in high score table for the game. - user (:class:`telegram.User`): User. - score (:obj:`int`): Score. - - Attributes: - position (:obj:`int`): Position in high score table for the game. - user (:class:`telegram.User`): User. - score (:obj:`int`): Score. - - """ - - __slots__ = ('position', 'user', 'score', '_id_attrs') - - def __init__(self, position: int, user: User, score: int): - self.position = position - self.user = user - self.score = score - - self._id_attrs = (self.position, self.user, self.score) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['GameHighScore']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['user'] = User.de_json(data.get('user'), bot) - - return cls(**data) diff --git a/telegramer/include/telegram/inline/__init__.py b/telegramer/include/telegram/inline/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/inline/inlinekeyboardbutton.py b/telegramer/include/telegram/inline/inlinekeyboardbutton.py deleted file mode 100644 index 49b6e07..0000000 --- a/telegramer/include/telegram/inline/inlinekeyboardbutton.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram InlineKeyboardButton.""" - -from typing import TYPE_CHECKING, Any - -from telegram import TelegramObject - -if TYPE_CHECKING: - from telegram import CallbackGame, LoginUrl - - -class InlineKeyboardButton(TelegramObject): - """This object represents one button of an inline keyboard. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`text`, :attr:`url`, :attr:`login_url`, :attr:`callback_data`, - :attr:`switch_inline_query`, :attr:`switch_inline_query_current_chat`, :attr:`callback_game` - and :attr:`pay` are equal. - - Note: - * You must use exactly one of the optional fields. Mind that :attr:`callback_game` is not - working as expected. Putting a game short name in it might, but is not guaranteed to - work. - * If your bot allows for arbitrary callback data, in keyboards returned in a response - from telegram, :attr:`callback_data` maybe be an instance of - :class:`telegram.ext.InvalidCallbackData`. This will be the case, if the data - associated with the button was already deleted. - - .. versionadded:: 13.6 - - * Since Bot API 5.5, it's now allowed to mention users by their ID in inline keyboards. - This will only work in Telegram versions released after December 7, 2021. - Older clients will display *unsupported message*. - - Warning: - If your bot allows your arbitrary callback data, buttons whose callback data is a - non-hashable object will become unhashable. Trying to evaluate ``hash(button)`` will - result in a :class:`TypeError`. - - .. versionchanged:: 13.6 - - Args: - text (:obj:`str`): Label text on the button. - url (:obj:`str`, optional): HTTP or tg:// url to be opened when the button is pressed. - Links ``tg://user?id=`` can be used to mention a user by - their ID without using a username, if this is allowed by their privacy settings. - - .. versionchanged:: 13.9 - You can now mention a user using ``tg://user?id=``. - login_url (:class:`telegram.LoginUrl`, optional): An HTTP URL used to automatically - authorize the user. Can be used as a replacement for the Telegram Login Widget. - callback_data (:obj:`str` | :obj:`Any`, optional): Data to be sent in a callback query to - the bot when button is pressed, UTF-8 1-64 bytes. If the bot instance allows arbitrary - callback data, anything can be passed. - switch_inline_query (:obj:`str`, optional): If set, pressing the button will prompt the - user to select one of their chats, open that chat and insert the bot's username and the - specified inline query in the input field. Can be empty, in which case just the bot's - username will be inserted. This offers an easy way for users to start using your bot - in inline mode when they are currently in a private chat with it. Especially useful - when combined with switch_pm* actions - in this case the user will be automatically - returned to the chat they switched from, skipping the chat selection screen. - switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will - insert the bot's username and the specified inline query in the current chat's input - field. Can be empty, in which case only the bot's username will be inserted. This - offers a quick way for the user to open your bot in inline mode in the same chat - good - for selecting something from multiple options. - callback_game (:class:`telegram.CallbackGame`, optional): Description of the game that will - be launched when the user presses the button. This type of button must always be - the ``first`` button in the first row. - pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button - must always be the `first` button in the first row and can only be used in invoice - messages. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - text (:obj:`str`): Label text on the button. - url (:obj:`str`): Optional. HTTP or tg:// url to be opened when the button is pressed. - Links ``tg://user?id=`` can be used to mention a user by - their ID without using a username, if this is allowed by their privacy settings. - - .. versionchanged:: 13.9 - You can now mention a user using ``tg://user?id=``. - login_url (:class:`telegram.LoginUrl`): Optional. An HTTP URL used to automatically - authorize the user. Can be used as a replacement for the Telegram Login Widget. - callback_data (:obj:`str` | :obj:`object`): Optional. Data to be sent in a callback query - to the bot when button is pressed, UTF-8 1-64 bytes. - switch_inline_query (:obj:`str`): Optional. Will prompt the user to select one of their - chats, open that chat and insert the bot's username and the specified inline query in - the input field. Can be empty, in which case just the bot’s username will be inserted. - switch_inline_query_current_chat (:obj:`str`): Optional. Will insert the bot's username and - the specified inline query in the current chat's input field. Can be empty, in which - case just the bot’s username will be inserted. - callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will - be launched when the user presses the button. - pay (:obj:`bool`): Optional. Specify :obj:`True`, to send a Pay button. - - """ - - __slots__ = ( - 'callback_game', - 'url', - 'switch_inline_query_current_chat', - 'callback_data', - 'pay', - 'switch_inline_query', - 'text', - '_id_attrs', - 'login_url', - ) - - def __init__( - self, - text: str, - url: str = None, - callback_data: object = None, - switch_inline_query: str = None, - switch_inline_query_current_chat: str = None, - callback_game: 'CallbackGame' = None, - pay: bool = None, - login_url: 'LoginUrl' = None, - **_kwargs: Any, - ): - # Required - self.text = text - - # Optionals - self.url = url - self.login_url = login_url - self.callback_data = callback_data - self.switch_inline_query = switch_inline_query - self.switch_inline_query_current_chat = switch_inline_query_current_chat - self.callback_game = callback_game - self.pay = pay - self._id_attrs = () - self._set_id_attrs() - - def _set_id_attrs(self) -> None: - self._id_attrs = ( - self.text, - self.url, - self.login_url, - self.callback_data, - self.switch_inline_query, - self.switch_inline_query_current_chat, - self.callback_game, - self.pay, - ) - - def update_callback_data(self, callback_data: object) -> None: - """ - Sets :attr:`callback_data` to the passed object. Intended to be used by - :class:`telegram.ext.CallbackDataCache`. - - .. versionadded:: 13.6 - - Args: - callback_data (:obj:`obj`): The new callback data. - """ - self.callback_data = callback_data - self._set_id_attrs() diff --git a/telegramer/include/telegram/inline/inlinekeyboardmarkup.py b/telegramer/include/telegram/inline/inlinekeyboardmarkup.py deleted file mode 100644 index 61724f0..0000000 --- a/telegramer/include/telegram/inline/inlinekeyboardmarkup.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram InlineKeyboardMarkup.""" - -from typing import TYPE_CHECKING, Any, List, Optional - -from telegram import InlineKeyboardButton, ReplyMarkup -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class InlineKeyboardMarkup(ReplyMarkup): - """ - This object represents an inline keyboard that appears right next to the message it belongs to. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their the size of :attr:`inline_keyboard` and all the buttons are equal. - - Args: - inline_keyboard (List[List[:class:`telegram.InlineKeyboardButton`]]): List of button rows, - each represented by a list of InlineKeyboardButton objects. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - inline_keyboard (List[List[:class:`telegram.InlineKeyboardButton`]]): List of button rows, - each represented by a list of InlineKeyboardButton objects. - - """ - - __slots__ = ('inline_keyboard', '_id_attrs') - - def __init__(self, inline_keyboard: List[List[InlineKeyboardButton]], **_kwargs: Any): - # Required - self.inline_keyboard = inline_keyboard - - self._id_attrs = (self.inline_keyboard,) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['inline_keyboard'] = [] - for inline_keyboard in self.inline_keyboard: - data['inline_keyboard'].append([x.to_dict() for x in inline_keyboard]) - - return data - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineKeyboardMarkup']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - keyboard = [] - for row in data['inline_keyboard']: - tmp = [] - for col in row: - btn = InlineKeyboardButton.de_json(col, bot) - if btn: - tmp.append(btn) - keyboard.append(tmp) - - return cls(keyboard) - - @classmethod - def from_button(cls, button: InlineKeyboardButton, **kwargs: object) -> 'InlineKeyboardMarkup': - """Shortcut for:: - - InlineKeyboardMarkup([[button]], **kwargs) - - Return an InlineKeyboardMarkup from a single InlineKeyboardButton - - Args: - button (:class:`telegram.InlineKeyboardButton`): The button to use in the markup - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - """ - return cls([[button]], **kwargs) - - @classmethod - def from_row( - cls, button_row: List[InlineKeyboardButton], **kwargs: object - ) -> 'InlineKeyboardMarkup': - """Shortcut for:: - - InlineKeyboardMarkup([button_row], **kwargs) - - Return an InlineKeyboardMarkup from a single row of InlineKeyboardButtons - - Args: - button_row (List[:class:`telegram.InlineKeyboardButton`]): The button to use in the - markup - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - """ - return cls([button_row], **kwargs) - - @classmethod - def from_column( - cls, button_column: List[InlineKeyboardButton], **kwargs: object - ) -> 'InlineKeyboardMarkup': - """Shortcut for:: - - InlineKeyboardMarkup([[button] for button in button_column], **kwargs) - - Return an InlineKeyboardMarkup from a single column of InlineKeyboardButtons - - Args: - button_column (List[:class:`telegram.InlineKeyboardButton`]): The button to use in the - markup - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - """ - button_grid = [[button] for button in button_column] - return cls(button_grid, **kwargs) - - def __hash__(self) -> int: - return hash(tuple(tuple(button for button in row) for row in self.inline_keyboard)) diff --git a/telegramer/include/telegram/inline/inlinequery.py b/telegramer/include/telegram/inline/inlinequery.py deleted file mode 100644 index 1230151..0000000 --- a/telegramer/include/telegram/inline/inlinequery.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0902,R0913 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram InlineQuery.""" - -from typing import TYPE_CHECKING, Any, Optional, Union, Callable, ClassVar, Sequence - -from telegram import Location, TelegramObject, User, constants -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, InlineQueryResult - - -class InlineQuery(TelegramObject): - """ - This object represents an incoming inline query. When the user sends an empty query, your bot - could return some default or trending results. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Note: - In Python ``from`` is a reserved word, use ``from_user`` instead. - - Args: - id (:obj:`str`): Unique identifier for this query. - from_user (:class:`telegram.User`): Sender. - query (:obj:`str`): Text of the query (up to 256 characters). - offset (:obj:`str`): Offset of the results to be returned, can be controlled by the bot. - chat_type (:obj:`str`, optional): Type of the chat, from which the inline query was sent. - Can be either :attr:`telegram.Chat.SENDER` for a private chat with the inline query - sender, :attr:`telegram.Chat.PRIVATE`, :attr:`telegram.Chat.GROUP`, - :attr:`telegram.Chat.SUPERGROUP` or :attr:`telegram.Chat.CHANNEL`. The chat type should - be always known for requests sent from official clients and most third-party clients, - unless the request was sent from a secret chat. - - .. versionadded:: 13.5 - location (:class:`telegram.Location`, optional): Sender location, only for bots that - request user location. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - id (:obj:`str`): Unique identifier for this query. - from_user (:class:`telegram.User`): Sender. - query (:obj:`str`): Text of the query (up to 256 characters). - offset (:obj:`str`): Offset of the results to be returned, can be controlled by the bot. - location (:class:`telegram.Location`): Optional. Sender location, only for bots that - request user location. - chat_type (:obj:`str`, optional): Type of the chat, from which the inline query was sent. - - .. versionadded:: 13.5 - - """ - - __slots__ = ('bot', 'location', 'chat_type', 'id', 'offset', 'from_user', 'query', '_id_attrs') - - def __init__( - self, - id: str, # pylint: disable=W0622 - from_user: User, - query: str, - offset: str, - location: Location = None, - bot: 'Bot' = None, - chat_type: str = None, - **_kwargs: Any, - ): - # Required - self.id = id # pylint: disable=C0103 - self.from_user = from_user - self.query = query - self.offset = offset - - # Optional - self.location = location - self.chat_type = chat_type - - self.bot = bot - self._id_attrs = (self.id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineQuery']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['from_user'] = User.de_json(data.get('from'), bot) - data['location'] = Location.de_json(data.get('location'), bot) - - return cls(bot=bot, **data) - - def answer( - self, - results: Union[ - Sequence['InlineQueryResult'], Callable[[int], Optional[Sequence['InlineQueryResult']]] - ], - cache_time: int = 300, - is_personal: bool = None, - next_offset: str = None, - switch_pm_text: str = None, - switch_pm_parameter: str = None, - timeout: ODVInput[float] = DEFAULT_NONE, - current_offset: str = None, - api_kwargs: JSONDict = None, - auto_pagination: bool = False, - ) -> bool: - """Shortcut for:: - - bot.answer_inline_query(update.inline_query.id, - *args, - current_offset=self.offset if auto_pagination else None, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.answer_inline_query`. - - Args: - auto_pagination (:obj:`bool`, optional): If set to :obj:`True`, :attr:`offset` will be - passed as :attr:`current_offset` to :meth:`telegram.Bot.answer_inline_query`. - Defaults to :obj:`False`. - - Raises: - TypeError: If both :attr:`current_offset` and :attr:`auto_pagination` are supplied. - """ - if current_offset and auto_pagination: - # We raise TypeError instead of ValueError for backwards compatibility with versions - # which didn't check this here but let Python do the checking - raise TypeError('current_offset and auto_pagination are mutually exclusive!') - return self.bot.answer_inline_query( - inline_query_id=self.id, - current_offset=self.offset if auto_pagination else current_offset, - results=results, - cache_time=cache_time, - is_personal=is_personal, - next_offset=next_offset, - switch_pm_text=switch_pm_text, - switch_pm_parameter=switch_pm_parameter, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - MAX_RESULTS: ClassVar[int] = constants.MAX_INLINE_QUERY_RESULTS - """ - :const:`telegram.constants.MAX_INLINE_QUERY_RESULTS` - - .. versionadded:: 13.2 - """ diff --git a/telegramer/include/telegram/inline/inlinequeryresult.py b/telegramer/include/telegram/inline/inlinequeryresult.py deleted file mode 100644 index f3227d4..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresult.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=W0622 -"""This module contains the classes that represent Telegram InlineQueryResult.""" - -from typing import Any - -from telegram import TelegramObject -from telegram.utils.types import JSONDict - - -class InlineQueryResult(TelegramObject): - """Baseclass for the InlineQueryResult* classes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Note: - All URLs passed in inline query results will be available to end users and therefore must - be assumed to be *public*. - - Args: - type (:obj:`str`): Type of the result. - id (:obj:`str`): Unique identifier for this result, 1-64 Bytes. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): Type of the result. - id (:obj:`str`): Unique identifier for this result, 1-64 Bytes. - - """ - - __slots__ = ('type', 'id', '_id_attrs') - - def __init__(self, type: str, id: str, **_kwargs: Any): - # Required - self.type = str(type) - self.id = str(id) # pylint: disable=C0103 - - self._id_attrs = (self.id,) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - # pylint: disable=E1101 - if ( - hasattr(self, 'caption_entities') - and self.caption_entities # type: ignore[attr-defined] - ): - data['caption_entities'] = [ - ce.to_dict() for ce in self.caption_entities # type: ignore[attr-defined] - ] - - return data diff --git a/telegramer/include/telegram/inline/inlinequeryresultarticle.py b/telegramer/include/telegram/inline/inlinequeryresultarticle.py deleted file mode 100644 index ecc975c..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultarticle.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultArticle.""" - -from typing import TYPE_CHECKING, Any - -from telegram import InlineQueryResult - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultArticle(InlineQueryResult): - """This object represents a Telegram InlineQueryResultArticle. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 Bytes. - title (:obj:`str`): Title of the result. - input_message_content (:class:`telegram.InputMessageContent`): Content of the message to - be sent. - reply_markup (:class:`telegram.ReplyMarkup`, optional): Inline keyboard attached to - the message - url (:obj:`str`, optional): URL of the result. - hide_url (:obj:`bool`, optional): Pass :obj:`True`, if you don't want the URL to be shown - in the message. - description (:obj:`str`, optional): Short description of the result. - thumb_url (:obj:`str`, optional): Url of the thumbnail for the result. - thumb_width (:obj:`int`, optional): Thumbnail width. - thumb_height (:obj:`int`, optional): Thumbnail height. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'article'. - id (:obj:`str`): Unique identifier for this result, 1-64 Bytes. - title (:obj:`str`): Title of the result. - input_message_content (:class:`telegram.InputMessageContent`): Content of the message to - be sent. - reply_markup (:class:`telegram.ReplyMarkup`): Optional. Inline keyboard attached to - the message. - url (:obj:`str`): Optional. URL of the result. - hide_url (:obj:`bool`): Optional. Pass :obj:`True`, if you don't want the URL to be shown - in the message. - description (:obj:`str`): Optional. Short description of the result. - thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result. - thumb_width (:obj:`int`): Optional. Thumbnail width. - thumb_height (:obj:`int`): Optional. Thumbnail height. - - """ - - __slots__ = ( - 'reply_markup', - 'thumb_width', - 'thumb_height', - 'hide_url', - 'url', - 'title', - 'description', - 'input_message_content', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - title: str, - input_message_content: 'InputMessageContent', - reply_markup: 'ReplyMarkup' = None, - url: str = None, - hide_url: bool = None, - description: str = None, - thumb_url: str = None, - thumb_width: int = None, - thumb_height: int = None, - **_kwargs: Any, - ): - - # Required - super().__init__('article', id) - self.title = title - self.input_message_content = input_message_content - - # Optional - self.reply_markup = reply_markup - self.url = url - self.hide_url = hide_url - self.description = description - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height diff --git a/telegramer/include/telegram/inline/inlinequeryresultaudio.py b/telegramer/include/telegram/inline/inlinequeryresultaudio.py deleted file mode 100644 index 9f06c62..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultaudio.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultAudio.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultAudio(InlineQueryResult): - """ - Represents a link to an mp3 audio file. By default, this audio file will be sent by the user. - Alternatively, you can use :attr:`input_message_content` to send a message with the specified - content instead of the audio. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - audio_url (:obj:`str`): A valid URL for the audio file. - title (:obj:`str`): Title. - performer (:obj:`str`, optional): Performer. - audio_duration (:obj:`str`, optional): Audio duration in seconds. - caption (:obj:`str`, optional): Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the audio. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'audio'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - audio_url (:obj:`str`): A valid URL for the audio file. - title (:obj:`str`): Title. - performer (:obj:`str`): Optional. Performer. - audio_duration (:obj:`str`): Optional. Audio duration in seconds. - caption (:obj:`str`): Optional. Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the audio. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'caption', - 'title', - 'parse_mode', - 'audio_url', - 'performer', - 'input_message_content', - 'audio_duration', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - audio_url: str, - title: str, - performer: str = None, - audio_duration: int = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - - # Required - super().__init__('audio', id) - self.audio_url = audio_url - self.title = title - - # Optionals - self.performer = performer - self.audio_duration = audio_duration - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcachedaudio.py b/telegramer/include/telegram/inline/inlinequeryresultcachedaudio.py deleted file mode 100644 index f92096a..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcachedaudio.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultCachedAudio.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedAudio(InlineQueryResult): - """ - Represents a link to an mp3 audio file stored on the Telegram servers. By default, this audio - file will be sent by the user. Alternatively, you can use :attr:`input_message_content` to - send a message with the specified content instead of the audio. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - audio_file_id (:obj:`str`): A valid file identifier for the audio file. - caption (:obj:`str`, optional): Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the audio. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'audio'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - audio_file_id (:obj:`str`): A valid file identifier for the audio file. - caption (:obj:`str`): Optional. Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the audio. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'caption', - 'parse_mode', - 'audio_file_id', - 'input_message_content', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - audio_file_id: str, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('audio', id) - self.audio_file_id = audio_file_id - - # Optionals - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcacheddocument.py b/telegramer/include/telegram/inline/inlinequeryresultcacheddocument.py deleted file mode 100644 index 4b76b74..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcacheddocument.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=W0622 -"""This module contains the classes that represent Telegram InlineQueryResultCachedDocument.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedDocument(InlineQueryResult): - """ - Represents a link to a file stored on the Telegram servers. By default, this file will be sent - by the user with an optional caption. Alternatively, you can use :attr:`input_message_content` - to send a message with the specified content instead of the file. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - title (:obj:`str`): Title for the result. - document_file_id (:obj:`str`): A valid file identifier for the file. - description (:obj:`str`, optional): Short description of the result. - caption (:obj:`str`, optional): Caption of the document to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption.. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the file. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'document'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - title (:obj:`str`): Title for the result. - document_file_id (:obj:`str`): A valid file identifier for the file. - description (:obj:`str`): Optional. Short description of the result. - caption (:obj:`str`): Optional. Caption of the document to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption.. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the file. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'document_file_id', - 'caption', - 'title', - 'description', - 'parse_mode', - 'input_message_content', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - title: str, - document_file_id: str, - description: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('document', id) - self.title = title - self.document_file_id = document_file_id - - # Optionals - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcachedgif.py b/telegramer/include/telegram/inline/inlinequeryresultcachedgif.py deleted file mode 100644 index 8cd3ad7..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcachedgif.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultCachedGif.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedGif(InlineQueryResult): - """ - Represents a link to an animated GIF file stored on the Telegram servers. By default, this - animated GIF file will be sent by the user with an optional caption. Alternatively, you can - use :attr:`input_message_content` to send a message with specified content instead of - the animation. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - gif_file_id (:obj:`str`): A valid file identifier for the GIF file. - title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional): - caption (:obj:`str`, optional): Caption of the GIF file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the gif. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'gif'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - gif_file_id (:obj:`str`): A valid file identifier for the GIF file. - title (:obj:`str`): Optional. Title for the result. - caption (:obj:`str`): Optional. Caption of the GIF file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the gif. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'caption', - 'title', - 'input_message_content', - 'parse_mode', - 'gif_file_id', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - gif_file_id: str, - title: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('gif', id) - self.gif_file_id = gif_file_id - - # Optionals - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcachedmpeg4gif.py b/telegramer/include/telegram/inline/inlinequeryresultcachedmpeg4gif.py deleted file mode 100644 index 8f1d457..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcachedmpeg4gif.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultMpeg4Gif.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): - """ - Represents a link to a video animation (H.264/MPEG-4 AVC video without sound) stored on the - Telegram servers. By default, this animated MPEG-4 file will be sent by the user with an - optional caption. Alternatively, you can use :attr:`input_message_content` to send a message - with the specified content instead of the animation. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - mpeg4_file_id (:obj:`str`): A valid file identifier for the MP4 file. - title (:obj:`str`, optional): Title for the result. - caption (:obj:`str`, optional): Caption of the MPEG-4 file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the MPEG-4 file. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'mpeg4_gif'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - mpeg4_file_id (:obj:`str`): A valid file identifier for the MP4 file. - title (:obj:`str`): Optional. Title for the result. - caption (:obj:`str`): Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the MPEG-4 file. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'mpeg4_file_id', - 'caption', - 'title', - 'parse_mode', - 'input_message_content', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - mpeg4_file_id: str, - title: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('mpeg4_gif', id) - self.mpeg4_file_id = mpeg4_file_id - - # Optionals - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcachedphoto.py b/telegramer/include/telegram/inline/inlinequeryresultcachedphoto.py deleted file mode 100644 index 8bba69b..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcachedphoto.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=W0622 -"""This module contains the classes that represent Telegram InlineQueryResultPhoto""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedPhoto(InlineQueryResult): - """ - Represents a link to a photo stored on the Telegram servers. By default, this photo will be - sent by the user with an optional caption. Alternatively, you can use - :attr:`input_message_content` to send a message with the specified content instead - of the photo. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - photo_file_id (:obj:`str`): A valid file identifier of the photo. - title (:obj:`str`, optional): Title for the result. - description (:obj:`str`, optional): Short description of the result. - caption (:obj:`str`, optional): Caption of the photo to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the photo. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'photo'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - photo_file_id (:obj:`str`): A valid file identifier of the photo. - title (:obj:`str`): Optional. Title for the result. - description (:obj:`str`): Optional. Short description of the result. - caption (:obj:`str`): Optional. Caption of the photo to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the photo. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'caption', - 'title', - 'description', - 'parse_mode', - 'photo_file_id', - 'input_message_content', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - photo_file_id: str, - title: str = None, - description: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('photo', id) - self.photo_file_id = photo_file_id - - # Optionals - self.title = title - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcachedsticker.py b/telegramer/include/telegram/inline/inlinequeryresultcachedsticker.py deleted file mode 100644 index 0425fa5..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcachedsticker.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultCachedSticker.""" - -from typing import TYPE_CHECKING, Any - -from telegram import InlineQueryResult - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedSticker(InlineQueryResult): - """ - Represents a link to a sticker stored on the Telegram servers. By default, this sticker will - be sent by the user. Alternatively, you can use :attr:`input_message_content` to send a - message with the specified content instead of the sticker. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - sticker_file_id (:obj:`str`): A valid file identifier of the sticker. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the sticker. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'sticker`. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - sticker_file_id (:obj:`str`): A valid file identifier of the sticker. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the sticker. - - """ - - __slots__ = ('reply_markup', 'input_message_content', 'sticker_file_id') - - def __init__( - self, - id: str, # pylint: disable=W0622 - sticker_file_id: str, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - **_kwargs: Any, - ): - # Required - super().__init__('sticker', id) - self.sticker_file_id = sticker_file_id - - # Optionals - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcachedvideo.py b/telegramer/include/telegram/inline/inlinequeryresultcachedvideo.py deleted file mode 100644 index 3283794..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcachedvideo.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultCachedVideo.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedVideo(InlineQueryResult): - """ - Represents a link to a video file stored on the Telegram servers. By default, this video file - will be sent by the user with an optional caption. Alternatively, you can use - :attr:`input_message_content` to send a message with the specified content instead - of the video. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - video_file_id (:obj:`str`): A valid file identifier for the video file. - title (:obj:`str`): Title for the result. - description (:obj:`str`, optional): Short description of the result. - caption (:obj:`str`, optional): Caption of the video to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the video. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'video'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - video_file_id (:obj:`str`): A valid file identifier for the video file. - title (:obj:`str`): Title for the result. - description (:obj:`str`): Optional. Short description of the result. - caption (:obj:`str`): Optional. Caption of the video to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the video. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'caption', - 'title', - 'description', - 'parse_mode', - 'input_message_content', - 'video_file_id', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - video_file_id: str, - title: str, - description: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('video', id) - self.video_file_id = video_file_id - self.title = title - - # Optionals - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcachedvoice.py b/telegramer/include/telegram/inline/inlinequeryresultcachedvoice.py deleted file mode 100644 index f5ff9bf..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcachedvoice.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultCachedVoice.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultCachedVoice(InlineQueryResult): - """ - Represents a link to a voice message stored on the Telegram servers. By default, this voice - message will be sent by the user. Alternatively, you can use :attr:`input_message_content` to - send a message with the specified content instead of the voice message. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - voice_file_id (:obj:`str`): A valid file identifier for the voice message. - title (:obj:`str`): Voice message title. - caption (:obj:`str`, optional): Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the voice message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'voice'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - voice_file_id (:obj:`str`): A valid file identifier for the voice message. - title (:obj:`str`): Voice message title. - caption (:obj:`str`): Optional. Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the voice message. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'caption', - 'title', - 'parse_mode', - 'voice_file_id', - 'input_message_content', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - voice_file_id: str, - title: str, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('voice', id) - self.voice_file_id = voice_file_id - self.title = title - - # Optionals - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultcontact.py b/telegramer/include/telegram/inline/inlinequeryresultcontact.py deleted file mode 100644 index df4f6c9..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultcontact.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultContact.""" - -from typing import TYPE_CHECKING, Any - -from telegram import InlineQueryResult - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultContact(InlineQueryResult): - """ - Represents a contact with a phone number. By default, this contact will be sent by the user. - Alternatively, you can use :attr:`input_message_content` to send a message with the specified - content instead of the contact. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - phone_number (:obj:`str`): Contact's phone number. - first_name (:obj:`str`): Contact's first name. - last_name (:obj:`str`, optional): Contact's last name. - vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard, - 0-2048 bytes. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the contact. - thumb_url (:obj:`str`, optional): Url of the thumbnail for the result. - thumb_width (:obj:`int`, optional): Thumbnail width. - thumb_height (:obj:`int`, optional): Thumbnail height. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'contact'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - phone_number (:obj:`str`): Contact's phone number. - first_name (:obj:`str`): Contact's first name. - last_name (:obj:`str`): Optional. Contact's last name. - vcard (:obj:`str`): Optional. Additional data about the contact in the form of a vCard, - 0-2048 bytes. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the contact. - thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result. - thumb_width (:obj:`int`): Optional. Thumbnail width. - thumb_height (:obj:`int`): Optional. Thumbnail height. - - """ - - __slots__ = ( - 'reply_markup', - 'thumb_width', - 'thumb_height', - 'vcard', - 'first_name', - 'last_name', - 'phone_number', - 'input_message_content', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - phone_number: str, - first_name: str, - last_name: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - thumb_url: str = None, - thumb_width: int = None, - thumb_height: int = None, - vcard: str = None, - **_kwargs: Any, - ): - # Required - super().__init__('contact', id) - self.phone_number = phone_number - self.first_name = first_name - - # Optionals - self.last_name = last_name - self.vcard = vcard - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height diff --git a/telegramer/include/telegram/inline/inlinequeryresultdocument.py b/telegramer/include/telegram/inline/inlinequeryresultdocument.py deleted file mode 100644 index 42555aa..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultdocument.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultDocument""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultDocument(InlineQueryResult): - """ - Represents a link to a file. By default, this file will be sent by the user with an optional - caption. Alternatively, you can use :attr:`input_message_content` to send a message with the - specified content instead of the file. Currently, only .PDF and .ZIP files can be sent - using this method. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - title (:obj:`str`): Title for the result. - caption (:obj:`str`, optional): Caption of the document to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - document_url (:obj:`str`): A valid URL for the file. - mime_type (:obj:`str`): Mime type of the content of the file, either "application/pdf" - or "application/zip". - description (:obj:`str`, optional): Short description of the result. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the file. - thumb_url (:obj:`str`, optional): URL of the thumbnail (jpeg only) for the file. - thumb_width (:obj:`int`, optional): Thumbnail width. - thumb_height (:obj:`int`, optional): Thumbnail height. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'document'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - title (:obj:`str`): Title for the result. - caption (:obj:`str`): Optional. Caption of the document to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - document_url (:obj:`str`): A valid URL for the file. - mime_type (:obj:`str`): Mime type of the content of the file, either "application/pdf" - or "application/zip". - description (:obj:`str`): Optional. Short description of the result. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the file. - thumb_url (:obj:`str`): Optional. URL of the thumbnail (jpeg only) for the file. - thumb_width (:obj:`int`): Optional. Thumbnail width. - thumb_height (:obj:`int`): Optional. Thumbnail height. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'document_url', - 'thumb_width', - 'thumb_height', - 'caption', - 'title', - 'description', - 'parse_mode', - 'mime_type', - 'thumb_url', - 'input_message_content', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - document_url: str, - title: str, - mime_type: str, - caption: str = None, - description: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - thumb_url: str = None, - thumb_width: int = None, - thumb_height: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('document', id) - self.document_url = document_url - self.title = title - self.mime_type = mime_type - - # Optionals - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.description = description - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height diff --git a/telegramer/include/telegram/inline/inlinequeryresultgame.py b/telegramer/include/telegram/inline/inlinequeryresultgame.py deleted file mode 100644 index 02e4a7d..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultgame.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultGame.""" - -from typing import TYPE_CHECKING, Any - -from telegram import InlineQueryResult - -if TYPE_CHECKING: - from telegram import ReplyMarkup - - -class InlineQueryResultGame(InlineQueryResult): - """Represents a :class:`telegram.Game`. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - game_short_name (:obj:`str`): Short name of the game. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'game'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - game_short_name (:obj:`str`): Short name of the game. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - - """ - - __slots__ = ('reply_markup', 'game_short_name') - - def __init__( - self, - id: str, # pylint: disable=W0622 - game_short_name: str, - reply_markup: 'ReplyMarkup' = None, - **_kwargs: Any, - ): - # Required - super().__init__('game', id) - self.id = id # pylint: disable=W0622 - self.game_short_name = game_short_name - - self.reply_markup = reply_markup diff --git a/telegramer/include/telegram/inline/inlinequeryresultgif.py b/telegramer/include/telegram/inline/inlinequeryresultgif.py deleted file mode 100644 index e879dc0..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultgif.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=W0622 -"""This module contains the classes that represent Telegram InlineQueryResultGif.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultGif(InlineQueryResult): - """ - Represents a link to an animated GIF file. By default, this animated GIF file will be sent by - the user with optional caption. Alternatively, you can use :attr:`input_message_content` to - send a message with the specified content instead of the animation. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - gif_url (:obj:`str`): A valid URL for the GIF file. File size must not exceed 1MB. - gif_width (:obj:`int`, optional): Width of the GIF. - gif_height (:obj:`int`, optional): Height of the GIF. - gif_duration (:obj:`int`, optional): Duration of the GIF - thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for - the result. - thumb_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of - ``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``. - title (:obj:`str`, optional): Title for the result. - caption (:obj:`str`, optional): Caption of the GIF file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the GIF animation. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'gif'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - gif_url (:obj:`str`): A valid URL for the GIF file. File size must not exceed 1MB. - gif_width (:obj:`int`): Optional. Width of the GIF. - gif_height (:obj:`int`): Optional. Height of the GIF. - gif_duration (:obj:`int`): Optional. Duration of the GIF. - thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for - the result. - thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail. - title (:obj:`str`): Optional. Title for the result. - caption (:obj:`str`): Optional. Caption of the GIF file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the GIF animation. - - """ - - __slots__ = ( - 'reply_markup', - 'gif_height', - 'thumb_mime_type', - 'caption_entities', - 'gif_width', - 'title', - 'caption', - 'parse_mode', - 'gif_duration', - 'input_message_content', - 'gif_url', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - gif_url: str, - thumb_url: str, - gif_width: int = None, - gif_height: int = None, - title: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - gif_duration: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb_mime_type: str = None, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - - # Required - super().__init__('gif', id) - self.gif_url = gif_url - self.thumb_url = thumb_url - - # Optionals - self.gif_width = gif_width - self.gif_height = gif_height - self.gif_duration = gif_duration - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_mime_type = thumb_mime_type diff --git a/telegramer/include/telegram/inline/inlinequeryresultlocation.py b/telegramer/include/telegram/inline/inlinequeryresultlocation.py deleted file mode 100644 index e55d90b..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultlocation.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultLocation.""" - -from typing import TYPE_CHECKING, Any - -from telegram import InlineQueryResult - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultLocation(InlineQueryResult): - """ - Represents a location on a map. By default, the location will be sent by the user. - Alternatively, you can use :attr:`input_message_content` to send a message with the specified - content instead of the location. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - latitude (:obj:`float`): Location latitude in degrees. - longitude (:obj:`float`): Location longitude in degrees. - title (:obj:`str`): Location title. - horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the location, - measured in meters; 0-1500. - live_period (:obj:`int`, optional): Period in seconds for which the location can be - updated, should be between 60 and 86400. - heading (:obj:`int`, optional): For live locations, a direction in which the user is - moving, in degrees. Must be between 1 and 360 if specified. - proximity_alert_radius (:obj:`int`, optional): For live locations, a maximum distance for - proximity alerts about approaching another chat member, in meters. Must be between 1 - and 100000 if specified. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the location. - thumb_url (:obj:`str`, optional): Url of the thumbnail for the result. - thumb_width (:obj:`int`, optional): Thumbnail width. - thumb_height (:obj:`int`, optional): Thumbnail height. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'location'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - latitude (:obj:`float`): Location latitude in degrees. - longitude (:obj:`float`): Location longitude in degrees. - title (:obj:`str`): Location title. - horizontal_accuracy (:obj:`float`): Optional. The radius of uncertainty for the location, - measured in meters. - live_period (:obj:`int`): Optional. Period in seconds for which the location can be - updated, should be between 60 and 86400. - heading (:obj:`int`): Optional. For live locations, a direction in which the user is - moving, in degrees. - proximity_alert_radius (:obj:`int`): Optional. For live locations, a maximum distance for - proximity alerts about approaching another chat member, in meters. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the location. - thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result. - thumb_width (:obj:`int`): Optional. Thumbnail width. - thumb_height (:obj:`int`): Optional. Thumbnail height. - - """ - - __slots__ = ( - 'longitude', - 'reply_markup', - 'thumb_width', - 'thumb_height', - 'heading', - 'title', - 'live_period', - 'proximity_alert_radius', - 'input_message_content', - 'latitude', - 'horizontal_accuracy', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - latitude: float, - longitude: float, - title: str, - live_period: int = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - thumb_url: str = None, - thumb_width: int = None, - thumb_height: int = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - **_kwargs: Any, - ): - # Required - super().__init__('location', id) - self.latitude = float(latitude) - self.longitude = float(longitude) - self.title = title - - # Optionals - self.live_period = live_period - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height - self.horizontal_accuracy = float(horizontal_accuracy) if horizontal_accuracy else None - self.heading = int(heading) if heading else None - self.proximity_alert_radius = ( - int(proximity_alert_radius) if proximity_alert_radius else None - ) diff --git a/telegramer/include/telegram/inline/inlinequeryresultmpeg4gif.py b/telegramer/include/telegram/inline/inlinequeryresultmpeg4gif.py deleted file mode 100644 index 5f73218..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultmpeg4gif.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultMpeg4Gif.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultMpeg4Gif(InlineQueryResult): - """ - Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). By default, this - animated MPEG-4 file will be sent by the user with optional caption. Alternatively, you can - use :attr:`input_message_content` to send a message with the specified content instead of the - animation. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - mpeg4_url (:obj:`str`): A valid URL for the MP4 file. File size must not exceed 1MB. - mpeg4_width (:obj:`int`, optional): Video width. - mpeg4_height (:obj:`int`, optional): Video height. - mpeg4_duration (:obj:`int`, optional): Video duration. - thumb_url (:obj:`str`): URL of the static thumbnail (jpeg or gif) for the result. - thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of - ``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``. - title (:obj:`str`, optional): Title for the result. - caption (:obj:`str`, optional): Caption of the MPEG-4 file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the video animation. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'mpeg4_gif'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - mpeg4_url (:obj:`str`): A valid URL for the MP4 file. File size must not exceed 1MB. - mpeg4_width (:obj:`int`): Optional. Video width. - mpeg4_height (:obj:`int`): Optional. Video height. - mpeg4_duration (:obj:`int`): Optional. Video duration. - thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for - the result. - thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail. - title (:obj:`str`): Optional. Title for the result. - caption (:obj:`str`): Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters - after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the video animation. - - """ - - __slots__ = ( - 'reply_markup', - 'thumb_mime_type', - 'caption_entities', - 'mpeg4_duration', - 'mpeg4_width', - 'title', - 'caption', - 'parse_mode', - 'input_message_content', - 'mpeg4_url', - 'mpeg4_height', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - mpeg4_url: str, - thumb_url: str, - mpeg4_width: int = None, - mpeg4_height: int = None, - title: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - mpeg4_duration: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb_mime_type: str = None, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - - # Required - super().__init__('mpeg4_gif', id) - self.mpeg4_url = mpeg4_url - self.thumb_url = thumb_url - - # Optional - self.mpeg4_width = mpeg4_width - self.mpeg4_height = mpeg4_height - self.mpeg4_duration = mpeg4_duration - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_mime_type = thumb_mime_type diff --git a/telegramer/include/telegram/inline/inlinequeryresultphoto.py b/telegramer/include/telegram/inline/inlinequeryresultphoto.py deleted file mode 100644 index e6de727..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultphoto.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultPhoto.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultPhoto(InlineQueryResult): - """ - Represents a link to a photo. By default, this photo will be sent by the user with optional - caption. Alternatively, you can use :attr:`input_message_content` to send a message with the - specified content instead of the photo. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - photo_url (:obj:`str`): A valid URL of the photo. Photo must be in jpeg format. Photo size - must not exceed 5MB. - thumb_url (:obj:`str`): URL of the thumbnail for the photo. - photo_width (:obj:`int`, optional): Width of the photo. - photo_height (:obj:`int`, optional): Height of the photo. - title (:obj:`str`, optional): Title for the result. - description (:obj:`str`, optional): Short description of the result. - caption (:obj:`str`, optional): Caption of the photo to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the photo. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'photo'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - photo_url (:obj:`str`): A valid URL of the photo. Photo must be in jpeg format. Photo size - must not exceed 5MB. - thumb_url (:obj:`str`): URL of the thumbnail for the photo. - photo_width (:obj:`int`): Optional. Width of the photo. - photo_height (:obj:`int`): Optional. Height of the photo. - title (:obj:`str`): Optional. Title for the result. - description (:obj:`str`): Optional. Short description of the result. - caption (:obj:`str`): Optional. Caption of the photo to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the photo. - - """ - - __slots__ = ( - 'photo_url', - 'reply_markup', - 'caption_entities', - 'photo_width', - 'caption', - 'title', - 'description', - 'parse_mode', - 'input_message_content', - 'photo_height', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - photo_url: str, - thumb_url: str, - photo_width: int = None, - photo_height: int = None, - title: str = None, - description: str = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - super().__init__('photo', id) - self.photo_url = photo_url - self.thumb_url = thumb_url - - # Optionals - self.photo_width = int(photo_width) if photo_width is not None else None - self.photo_height = int(photo_height) if photo_height is not None else None - self.title = title - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultvenue.py b/telegramer/include/telegram/inline/inlinequeryresultvenue.py deleted file mode 100644 index 4a64a10..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultvenue.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultVenue.""" - -from typing import TYPE_CHECKING, Any - -from telegram import InlineQueryResult - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultVenue(InlineQueryResult): - """ - Represents a venue. By default, the venue will be sent by the user. Alternatively, you can - use :attr:`input_message_content` to send a message with the specified content instead of the - venue. - - Note: - Foursquare details and Google Pace details are mutually exclusive. However, this - behaviour is undocumented and might be changed by Telegram. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 Bytes. - latitude (:obj:`float`): Latitude of the venue location in degrees. - longitude (:obj:`float`): Longitude of the venue location in degrees. - title (:obj:`str`): Title of the venue. - address (:obj:`str`): Address of the venue. - foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue if known. - foursquare_type (:obj:`str`, optional): Foursquare type of the venue, if known. - (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or - "food/icecream".) - google_place_id (:obj:`str`, optional): Google Places identifier of the venue. - google_place_type (:obj:`str`, optional): Google Places type of the venue. (See - `supported types `_.) - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the location. - thumb_url (:obj:`str`, optional): Url of the thumbnail for the result. - thumb_width (:obj:`int`, optional): Thumbnail width. - thumb_height (:obj:`int`, optional): Thumbnail height. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'venue'. - id (:obj:`str`): Unique identifier for this result, 1-64 Bytes. - latitude (:obj:`float`): Latitude of the venue location in degrees. - longitude (:obj:`float`): Longitude of the venue location in degrees. - title (:obj:`str`): Title of the venue. - address (:obj:`str`): Address of the venue. - foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue if known. - foursquare_type (:obj:`str`): Optional. Foursquare type of the venue, if known. - google_place_id (:obj:`str`): Optional. Google Places identifier of the venue. - google_place_type (:obj:`str`): Optional. Google Places type of the venue. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the venue. - thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result. - thumb_width (:obj:`int`): Optional. Thumbnail width. - thumb_height (:obj:`int`): Optional. Thumbnail height. - - """ - - __slots__ = ( - 'longitude', - 'reply_markup', - 'google_place_type', - 'thumb_width', - 'thumb_height', - 'title', - 'address', - 'foursquare_id', - 'foursquare_type', - 'google_place_id', - 'input_message_content', - 'latitude', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - latitude: float, - longitude: float, - title: str, - address: str, - foursquare_id: str = None, - foursquare_type: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - thumb_url: str = None, - thumb_width: int = None, - thumb_height: int = None, - google_place_id: str = None, - google_place_type: str = None, - **_kwargs: Any, - ): - - # Required - super().__init__('venue', id) - self.latitude = latitude - self.longitude = longitude - self.title = title - self.address = address - - # Optional - self.foursquare_id = foursquare_id - self.foursquare_type = foursquare_type - self.google_place_id = google_place_id - self.google_place_type = google_place_type - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height diff --git a/telegramer/include/telegram/inline/inlinequeryresultvideo.py b/telegramer/include/telegram/inline/inlinequeryresultvideo.py deleted file mode 100644 index 098e3b0..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultvideo.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultVideo.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultVideo(InlineQueryResult): - """ - Represents a link to a page containing an embedded video player or a video file. By default, - this video file will be sent by the user with an optional caption. Alternatively, you can use - :attr:`input_message_content` to send a message with the specified content instead of - the video. - - Note: - If an InlineQueryResultVideo message contains an embedded video (e.g., YouTube), you must - replace its content using :attr:`input_message_content`. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - video_url (:obj:`str`): A valid URL for the embedded video player or video file. - mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4". - thumb_url (:obj:`str`): URL of the thumbnail (jpeg only) for the video. - title (:obj:`str`): Title for the result. - caption (:obj:`str`, optional): Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - video_width (:obj:`int`, optional): Video width. - video_height (:obj:`int`, optional): Video height. - video_duration (:obj:`int`, optional): Video duration in seconds. - description (:obj:`str`, optional): Short description of the result. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the video. This field is required if - InlineQueryResultVideo is used to send an HTML-page as a result - (e.g., a YouTube video). - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'video'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - video_url (:obj:`str`): A valid URL for the embedded video player or video file. - mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4". - thumb_url (:obj:`str`): URL of the thumbnail (jpeg only) for the video. - title (:obj:`str`): Title for the result. - caption (:obj:`str`): Optional. Caption of the video to be sent, 0-1024 characters after - entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - video_width (:obj:`int`): Optional. Video width. - video_height (:obj:`int`): Optional. Video height. - video_duration (:obj:`int`): Optional. Video duration in seconds. - description (:obj:`str`): Optional. Short description of the result. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the video. This field is required if - InlineQueryResultVideo is used to send an HTML-page as a result - (e.g., a YouTube video). - - """ - - __slots__ = ( - 'video_url', - 'reply_markup', - 'caption_entities', - 'caption', - 'title', - 'description', - 'video_duration', - 'parse_mode', - 'mime_type', - 'input_message_content', - 'video_height', - 'video_width', - 'thumb_url', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - video_url: str, - mime_type: str, - thumb_url: str, - title: str, - caption: str = None, - video_width: int = None, - video_height: int = None, - video_duration: int = None, - description: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - - # Required - super().__init__('video', id) - self.video_url = video_url - self.mime_type = mime_type - self.thumb_url = thumb_url - self.title = title - - # Optional - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.video_width = video_width - self.video_height = video_height - self.video_duration = video_duration - self.description = description - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inlinequeryresultvoice.py b/telegramer/include/telegram/inline/inlinequeryresultvoice.py deleted file mode 100644 index 960f41b..0000000 --- a/telegramer/include/telegram/inline/inlinequeryresultvoice.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InlineQueryResultVoice.""" - -from typing import TYPE_CHECKING, Any, Union, Tuple, List - -from telegram import InlineQueryResult, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import ODVInput - -if TYPE_CHECKING: - from telegram import InputMessageContent, ReplyMarkup - - -class InlineQueryResultVoice(InlineQueryResult): - """ - Represents a link to a voice recording in an .ogg container encoded with OPUS. By default, - this voice recording will be sent by the user. Alternatively, you can use - :attr:`input_message_content` to send a message with the specified content instead of the - the voice message. - - Args: - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - voice_url (:obj:`str`): A valid URL for the voice recording. - title (:obj:`str`): Recording title. - caption (:obj:`str`, optional): Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - voice_duration (:obj:`int`, optional): Recording duration in seconds. - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the - message to be sent instead of the voice recording. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): 'voice'. - id (:obj:`str`): Unique identifier for this result, 1-64 bytes. - voice_url (:obj:`str`): A valid URL for the voice recording. - title (:obj:`str`): Recording title. - caption (:obj:`str`): Optional. Caption, 0-1024 characters after entities parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in the media caption. See the constants - in :class:`telegram.ParseMode` for the available modes. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - voice_duration (:obj:`int`): Optional. Recording duration in seconds. - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the - message to be sent instead of the voice recording. - - """ - - __slots__ = ( - 'reply_markup', - 'caption_entities', - 'voice_duration', - 'caption', - 'title', - 'voice_url', - 'parse_mode', - 'input_message_content', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - voice_url: str, - title: str, - voice_duration: int = None, - caption: str = None, - reply_markup: 'ReplyMarkup' = None, - input_message_content: 'InputMessageContent' = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - - # Required - super().__init__('voice', id) - self.voice_url = voice_url - self.title = title - - # Optional - self.voice_duration = voice_duration - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content diff --git a/telegramer/include/telegram/inline/inputcontactmessagecontent.py b/telegramer/include/telegram/inline/inputcontactmessagecontent.py deleted file mode 100644 index fe7b9d7..0000000 --- a/telegramer/include/telegram/inline/inputcontactmessagecontent.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InputContactMessageContent.""" - -from typing import Any - -from telegram import InputMessageContent - - -class InputContactMessageContent(InputMessageContent): - """Represents the content of a contact message to be sent as the result of an inline query. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`phone_number` is equal. - - Args: - phone_number (:obj:`str`): Contact's phone number. - first_name (:obj:`str`): Contact's first name. - last_name (:obj:`str`, optional): Contact's last name. - vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard, - 0-2048 bytes. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - phone_number (:obj:`str`): Contact's phone number. - first_name (:obj:`str`): Contact's first name. - last_name (:obj:`str`): Optional. Contact's last name. - vcard (:obj:`str`): Optional. Additional data about the contact in the form of a vCard, - 0-2048 bytes. - - """ - - __slots__ = ('vcard', 'first_name', 'last_name', 'phone_number', '_id_attrs') - - def __init__( - self, - phone_number: str, - first_name: str, - last_name: str = None, - vcard: str = None, - **_kwargs: Any, - ): - # Required - self.phone_number = phone_number - self.first_name = first_name - # Optionals - self.last_name = last_name - self.vcard = vcard - - self._id_attrs = (self.phone_number,) diff --git a/telegramer/include/telegram/inline/inputinvoicemessagecontent.py b/telegramer/include/telegram/inline/inputinvoicemessagecontent.py deleted file mode 100644 index 622f0ed..0000000 --- a/telegramer/include/telegram/inline/inputinvoicemessagecontent.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains a class that represents a Telegram InputInvoiceMessageContent.""" - -from typing import Any, List, Optional, TYPE_CHECKING - -from telegram import InputMessageContent, LabeledPrice -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class InputInvoiceMessageContent(InputMessageContent): - """ - Represents the content of a invoice message to be sent as the result of an inline query. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`title`, :attr:`description`, :attr:`payload`, - :attr:`provider_token`, :attr:`currency` and :attr:`prices` are equal. - - .. versionadded:: 13.5 - - Args: - title (:obj:`str`): Product name, 1-32 characters - description (:obj:`str`): Product description, 1-255 characters - payload (:obj:`str`):Bot-defined invoice payload, 1-128 bytes. This will not be displayed - to the user, use for your internal processes. - provider_token (:obj:`str`): Payment provider token, obtained via - `@Botfather `_. - currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on - `currencies `_ - prices (List[:class:`telegram.LabeledPrice`]): Price breakdown, a JSON-serialized list of - components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, - etc.) - max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the smallest - units of the currency (integer, not float/double). For example, for a maximum tip of - US$ 1.45 pass ``max_tip_amount = 145``. See the ``exp`` parameter in - `currencies.json `_, it - shows the number of digits past the decimal point for each currency (2 for the majority - of currencies). Defaults to ``0``. - suggested_tip_amounts (List[:obj:`int`], optional): A JSON-serialized array of suggested - amounts of tip in the smallest units of the currency (integer, not float/double). At - most 4 suggested tip amounts can be specified. The suggested tip amounts must be - positive, passed in a strictly increased order and must not exceed - :attr:`max_tip_amount`. - provider_data (:obj:`str`, optional): A JSON-serialized object for data about the invoice, - which will be shared with the payment provider. A detailed description of the required - fields should be provided by the payment provider. - photo_url (:obj:`str`, optional): URL of the product photo for the invoice. Can be a photo - of the goods or a marketing image for a service. People like it better when they see - what they are paying for. - photo_size (:obj:`int`, optional): Photo size. - photo_width (:obj:`int`, optional): Photo width. - photo_height (:obj:`int`, optional): Photo height. - need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full name to - complete the order. - need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's - phone number to complete the order - need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email - address to complete the order. - need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's - shipping address to complete the order - send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's phone - number should be sent to provider. - send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email address - should be sent to provider. - is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on the - shipping method. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - title (:obj:`str`): Product name, 1-32 characters - description (:obj:`str`): Product description, 1-255 characters - payload (:obj:`str`):Bot-defined invoice payload, 1-128 bytes. This will not be displayed - to the user, use for your internal processes. - provider_token (:obj:`str`): Payment provider token, obtained via - `@Botfather `_. - currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on - `currencies `_ - prices (List[:class:`telegram.LabeledPrice`]): Price breakdown, a JSON-serialized list of - components. - max_tip_amount (:obj:`int`): Optional. The maximum accepted amount for tips in the smallest - units of the currency (integer, not float/double). - suggested_tip_amounts (List[:obj:`int`]): Optional. A JSON-serialized array of suggested - amounts of tip in the smallest units of the currency (integer, not float/double). - provider_data (:obj:`str`): Optional. A JSON-serialized object for data about the invoice, - which will be shared with the payment provider. - photo_url (:obj:`str`): Optional. URL of the product photo for the invoice. - photo_size (:obj:`int`): Optional. Photo size. - photo_width (:obj:`int`): Optional. Photo width. - photo_height (:obj:`int`): Optional. Photo height. - need_name (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's full name to - complete the order. - need_phone_number (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's - phone number to complete the order - need_email (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's email - address to complete the order. - need_shipping_address (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's - shipping address to complete the order - send_phone_number_to_provider (:obj:`bool`): Optional. Pass :obj:`True`, if user's phone - number should be sent to provider. - send_email_to_provider (:obj:`bool`): Optional. Pass :obj:`True`, if user's email address - should be sent to provider. - is_flexible (:obj:`bool`): Optional. Pass :obj:`True`, if the final price depends on the - shipping method. - - """ - - __slots__ = ( - 'title', - 'description', - 'payload', - 'provider_token', - 'currency', - 'prices', - 'max_tip_amount', - 'suggested_tip_amounts', - 'provider_data', - 'photo_url', - 'photo_size', - 'photo_width', - 'photo_height', - 'need_name', - 'need_phone_number', - 'need_email', - 'need_shipping_address', - 'send_phone_number_to_provider', - 'send_email_to_provider', - 'is_flexible', - '_id_attrs', - ) - - def __init__( - self, - title: str, - description: str, - payload: str, - provider_token: str, - currency: str, - prices: List[LabeledPrice], - max_tip_amount: int = None, - suggested_tip_amounts: List[int] = None, - provider_data: str = None, - photo_url: str = None, - photo_size: int = None, - photo_width: int = None, - photo_height: int = None, - need_name: bool = None, - need_phone_number: bool = None, - need_email: bool = None, - need_shipping_address: bool = None, - send_phone_number_to_provider: bool = None, - send_email_to_provider: bool = None, - is_flexible: bool = None, - **_kwargs: Any, - ): - # Required - self.title = title - self.description = description - self.payload = payload - self.provider_token = provider_token - self.currency = currency - self.prices = prices - # Optionals - self.max_tip_amount = int(max_tip_amount) if max_tip_amount else None - self.suggested_tip_amounts = ( - [int(sta) for sta in suggested_tip_amounts] if suggested_tip_amounts else None - ) - self.provider_data = provider_data - self.photo_url = photo_url - self.photo_size = int(photo_size) if photo_size else None - self.photo_width = int(photo_width) if photo_width else None - self.photo_height = int(photo_height) if photo_height else None - self.need_name = need_name - self.need_phone_number = need_phone_number - self.need_email = need_email - self.need_shipping_address = need_shipping_address - self.send_phone_number_to_provider = send_phone_number_to_provider - self.send_email_to_provider = send_email_to_provider - self.is_flexible = is_flexible - - self._id_attrs = ( - self.title, - self.description, - self.payload, - self.provider_token, - self.currency, - self.prices, - ) - - def __hash__(self) -> int: - # we override this as self.prices is a list and not hashable - prices = tuple(self.prices) - return hash( - ( - self.title, - self.description, - self.payload, - self.provider_token, - self.currency, - prices, - ) - ) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['prices'] = [price.to_dict() for price in self.prices] - - return data - - @classmethod - def de_json( - cls, data: Optional[JSONDict], bot: 'Bot' - ) -> Optional['InputInvoiceMessageContent']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['prices'] = LabeledPrice.de_list(data.get('prices'), bot) - - return cls(**data, bot=bot) diff --git a/telegramer/include/telegram/inline/inputlocationmessagecontent.py b/telegramer/include/telegram/inline/inputlocationmessagecontent.py deleted file mode 100644 index 22760bf..0000000 --- a/telegramer/include/telegram/inline/inputlocationmessagecontent.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InputLocationMessageContent.""" - -from typing import Any - -from telegram import InputMessageContent - - -class InputLocationMessageContent(InputMessageContent): - # fmt: off - """ - Represents the content of a location message to be sent as the result of an inline query. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`latitude` and :attr:`longitude` are equal. - - Args: - latitude (:obj:`float`): Latitude of the location in degrees. - longitude (:obj:`float`): Longitude of the location in degrees. - horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the location, - measured in meters; 0-1500. - live_period (:obj:`int`, optional): Period in seconds for which the location can be - updated, should be between 60 and 86400. - heading (:obj:`int`, optional): For live locations, a direction in which the user is - moving, in degrees. Must be between 1 and 360 if specified. - proximity_alert_radius (:obj:`int`, optional): For live locations, a maximum distance for - proximity alerts about approaching another chat member, in meters. Must be between 1 - and 100000 if specified. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - latitude (:obj:`float`): Latitude of the location in degrees. - longitude (:obj:`float`): Longitude of the location in degrees. - horizontal_accuracy (:obj:`float`): Optional. The radius of uncertainty for the location, - measured in meters. - live_period (:obj:`int`): Optional. Period in seconds for which the location can be - updated. - heading (:obj:`int`): Optional. For live locations, a direction in which the user is - moving, in degrees. - proximity_alert_radius (:obj:`int`): Optional. For live locations, a maximum distance for - proximity alerts about approaching another chat member, in meters. - - """ - - __slots__ = ('longitude', 'horizontal_accuracy', 'proximity_alert_radius', 'live_period', - 'latitude', 'heading', '_id_attrs') - # fmt: on - - def __init__( - self, - latitude: float, - longitude: float, - live_period: int = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - **_kwargs: Any, - ): - # Required - self.latitude = latitude - self.longitude = longitude - - # Optionals - self.live_period = int(live_period) if live_period else None - self.horizontal_accuracy = float(horizontal_accuracy) if horizontal_accuracy else None - self.heading = int(heading) if heading else None - self.proximity_alert_radius = ( - int(proximity_alert_radius) if proximity_alert_radius else None - ) - - self._id_attrs = (self.latitude, self.longitude) diff --git a/telegramer/include/telegram/inline/inputmessagecontent.py b/telegramer/include/telegram/inline/inputmessagecontent.py deleted file mode 100644 index 4362c72..0000000 --- a/telegramer/include/telegram/inline/inputmessagecontent.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InputMessageContent.""" - -from telegram import TelegramObject - - -class InputMessageContent(TelegramObject): - """Base class for Telegram InputMessageContent Objects. - - See: :class:`telegram.InputContactMessageContent`, - :class:`telegram.InputInvoiceMessageContent`, - :class:`telegram.InputLocationMessageContent`, :class:`telegram.InputTextMessageContent` and - :class:`telegram.InputVenueMessageContent` for more details. - - """ - - __slots__ = () diff --git a/telegramer/include/telegram/inline/inputtextmessagecontent.py b/telegramer/include/telegram/inline/inputtextmessagecontent.py deleted file mode 100644 index c068aa7..0000000 --- a/telegramer/include/telegram/inline/inputtextmessagecontent.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InputTextMessageContent.""" - -from typing import Any, Union, Tuple, List - -from telegram import InputMessageContent, MessageEntity -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - - -class InputTextMessageContent(InputMessageContent): - """ - Represents the content of a text message to be sent as the result of an inline query. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`message_text` is equal. - - Args: - message_text (:obj:`str`): Text of the message to be sent, 1-4096 characters after entities - parsing. Also found as :attr:`telegram.constants.MAX_MESSAGE_LENGTH`. - parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in your bot's message. See the constants - in :class:`telegram.ParseMode` for the available modes. - entities (List[:class:`telegram.MessageEntity`], optional): List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in the - sent message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - message_text (:obj:`str`): Text of the message to be sent, 1-4096 characters after entities - parsing. - parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show - bold, italic, fixed-width text or inline URLs in your bot's message. See the constants - in :class:`telegram.ParseMode` for the available modes. - entities (List[:class:`telegram.MessageEntity`]): Optional. List of special - entities that appear in the caption, which can be specified instead of - :attr:`parse_mode`. - disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in the - sent message. - - """ - - __slots__ = ('disable_web_page_preview', 'parse_mode', 'entities', 'message_text', '_id_attrs') - - def __init__( - self, - message_text: str, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None, - **_kwargs: Any, - ): - # Required - self.message_text = message_text - # Optionals - self.parse_mode = parse_mode - self.entities = entities - self.disable_web_page_preview = disable_web_page_preview - - self._id_attrs = (self.message_text,) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - if self.entities: - data['entities'] = [ce.to_dict() for ce in self.entities] - - return data diff --git a/telegramer/include/telegram/inline/inputvenuemessagecontent.py b/telegramer/include/telegram/inline/inputvenuemessagecontent.py deleted file mode 100644 index c13107d..0000000 --- a/telegramer/include/telegram/inline/inputvenuemessagecontent.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the classes that represent Telegram InputVenueMessageContent.""" - -from typing import Any - -from telegram import InputMessageContent - - -class InputVenueMessageContent(InputMessageContent): - """Represents the content of a venue message to be sent as the result of an inline query. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`latitude`, :attr:`longitude` and :attr:`title` - are equal. - - Note: - Foursquare details and Google Pace details are mutually exclusive. However, this - behaviour is undocumented and might be changed by Telegram. - - Args: - latitude (:obj:`float`): Latitude of the location in degrees. - longitude (:obj:`float`): Longitude of the location in degrees. - title (:obj:`str`): Name of the venue. - address (:obj:`str`): Address of the venue. - foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue, if known. - foursquare_type (:obj:`str`, optional): Foursquare type of the venue, if known. - (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or - "food/icecream".) - google_place_id (:obj:`str`, optional): Google Places identifier of the venue. - google_place_type (:obj:`str`, optional): Google Places type of the venue. (See - `supported types `_.) - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - latitude (:obj:`float`): Latitude of the location in degrees. - longitude (:obj:`float`): Longitude of the location in degrees. - title (:obj:`str`): Name of the venue. - address (:obj:`str`): Address of the venue. - foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue, if known. - foursquare_type (:obj:`str`): Optional. Foursquare type of the venue, if known. - google_place_id (:obj:`str`): Optional. Google Places identifier of the venue. - google_place_type (:obj:`str`): Optional. Google Places type of the venue. - - """ - - __slots__ = ( - 'longitude', - 'google_place_type', - 'title', - 'address', - 'foursquare_id', - 'foursquare_type', - 'google_place_id', - 'latitude', - '_id_attrs', - ) - - def __init__( - self, - latitude: float, - longitude: float, - title: str, - address: str, - foursquare_id: str = None, - foursquare_type: str = None, - google_place_id: str = None, - google_place_type: str = None, - **_kwargs: Any, - ): - # Required - self.latitude = latitude - self.longitude = longitude - self.title = title - self.address = address - # Optionals - self.foursquare_id = foursquare_id - self.foursquare_type = foursquare_type - self.google_place_id = google_place_id - self.google_place_type = google_place_type - - self._id_attrs = ( - self.latitude, - self.longitude, - self.title, - ) diff --git a/telegramer/include/telegram/keyboardbutton.py b/telegramer/include/telegram/keyboardbutton.py deleted file mode 100644 index fe2bd87..0000000 --- a/telegramer/include/telegram/keyboardbutton.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram KeyboardButton.""" - -from typing import Any - -from telegram import TelegramObject, KeyboardButtonPollType - - -class KeyboardButton(TelegramObject): - """ - This object represents one button of the reply keyboard. For simple text buttons String can be - used instead of this object to specify text of the button. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`text`, :attr:`request_contact`, :attr:`request_location` and - :attr:`request_poll` are equal. - - Note: - * Optional fields are mutually exclusive. - * :attr:`request_contact` and :attr:`request_location` options will only work in Telegram - versions released after 9 April, 2016. Older clients will ignore them. - * :attr:`request_poll` option will only work in Telegram versions released after 23 - January, 2020. Older clients will receive unsupported message. - - Args: - text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be - sent to the bot as a message when the button is pressed. - request_contact (:obj:`bool`, optional): If :obj:`True`, the user's phone number will be - sent as a contact when the button is pressed. Available in private chats only. - request_location (:obj:`bool`, optional): If :obj:`True`, the user's current location will - be sent when the button is pressed. Available in private chats only. - request_poll (:class:`KeyboardButtonPollType`, optional): If specified, the user will be - asked to create a poll and send it to the bot when the button is pressed. Available in - private chats only. - - Attributes: - text (:obj:`str`): Text of the button. - request_contact (:obj:`bool`): Optional. The user's phone number will be sent. - request_location (:obj:`bool`): Optional. The user's current location will be sent. - request_poll (:class:`KeyboardButtonPollType`): Optional. If the user should create a poll. - - """ - - __slots__ = ('request_location', 'request_contact', 'request_poll', 'text', '_id_attrs') - - def __init__( - self, - text: str, - request_contact: bool = None, - request_location: bool = None, - request_poll: KeyboardButtonPollType = None, - **_kwargs: Any, - ): - # Required - self.text = text - # Optionals - self.request_contact = request_contact - self.request_location = request_location - self.request_poll = request_poll - - self._id_attrs = ( - self.text, - self.request_contact, - self.request_location, - self.request_poll, - ) diff --git a/telegramer/include/telegram/keyboardbuttonpolltype.py b/telegramer/include/telegram/keyboardbuttonpolltype.py deleted file mode 100644 index 813ce97..0000000 --- a/telegramer/include/telegram/keyboardbuttonpolltype.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a type of a Telegram Poll.""" -from typing import Any - -from telegram import TelegramObject - - -class KeyboardButtonPollType(TelegramObject): - """This object represents type of a poll, which is allowed to be created - and sent when the corresponding button is pressed. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`type` is equal. - - Attributes: - type (:obj:`str`): Optional. If :attr:`telegram.Poll.QUIZ` is passed, the user will be - allowed to create only polls in the quiz mode. If :attr:`telegram.Poll.REGULAR` is - passed, only regular polls will be allowed. Otherwise, the user will be allowed to - create a poll of any type. - """ - - __slots__ = ('type', '_id_attrs') - - def __init__(self, type: str = None, **_kwargs: Any): # pylint: disable=W0622 - self.type = type - - self._id_attrs = (self.type,) diff --git a/telegramer/include/telegram/loginurl.py b/telegramer/include/telegram/loginurl.py deleted file mode 100644 index 7d6dc56..0000000 --- a/telegramer/include/telegram/loginurl.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram LoginUrl.""" -from typing import Any - -from telegram import TelegramObject - - -class LoginUrl(TelegramObject): - """This object represents a parameter of the inline keyboard button used to automatically - authorize a user. Serves as a great replacement for the Telegram Login Widget when the user is - coming from Telegram. All the user needs to do is tap/click a button and confirm that they want - to log in. Telegram apps support these buttons as of version 5.7. - - Sample bot: `@discussbot `_ - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`url` is equal. - - Note: - You must always check the hash of the received data to verify the authentication - and the integrity of the data as described in - `Checking authorization `_ - - Args: - url (:obj:`str`): An HTTP URL to be opened with user authorization data added to the query - string when the button is pressed. If the user refuses to provide authorization data, - the original URL without information about the user will be opened. The data added is - the same as described in - `Receiving authorization data - `_ - forward_text (:obj:`str`, optional): New text of the button in forwarded messages. - bot_username (:obj:`str`, optional): Username of a bot, which will be used for user - authorization. See - `Setting up a bot `_ - for more details. If not specified, the current - bot's username will be assumed. The url's domain must be the same as the domain linked - with the bot. See - `Linking your domain to the bot - `_ - for more details. - request_write_access (:obj:`bool`, optional): Pass :obj:`True` to request the permission - for your bot to send messages to the user. - - Attributes: - url (:obj:`str`): An HTTP URL to be opened with user authorization data. - forward_text (:obj:`str`): Optional. New text of the button in forwarded messages. - bot_username (:obj:`str`): Optional. Username of a bot, which will be used for user - authorization. - request_write_access (:obj:`bool`): Optional. Pass :obj:`True` to request the permission - for your bot to send messages to the user. - - """ - - __slots__ = ('bot_username', 'request_write_access', 'url', 'forward_text', '_id_attrs') - - def __init__( - self, - url: str, - forward_text: bool = None, - bot_username: str = None, - request_write_access: bool = None, - **_kwargs: Any, - ): - # Required - self.url = url - # Optional - self.forward_text = forward_text - self.bot_username = bot_username - self.request_write_access = request_write_access - - self._id_attrs = (self.url,) diff --git a/telegramer/include/telegram/message.py b/telegramer/include/telegram/message.py deleted file mode 100644 index 74d750f..0000000 --- a/telegramer/include/telegram/message.py +++ /dev/null @@ -1,3010 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0902,R0913 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Message.""" -import datetime -import sys -from html import escape -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, ClassVar, Tuple - -from telegram import ( - Animation, - Audio, - Chat, - Contact, - Dice, - Document, - Game, - InlineKeyboardMarkup, - Invoice, - Location, - MessageEntity, - ParseMode, - PassportData, - PhotoSize, - Poll, - Sticker, - SuccessfulPayment, - TelegramObject, - User, - Venue, - Video, - VideoNote, - Voice, - VoiceChatStarted, - VoiceChatEnded, - VoiceChatParticipantsInvited, - ProximityAlertTriggered, - ReplyMarkup, - MessageAutoDeleteTimerChanged, - VoiceChatScheduled, -) -from telegram.utils.helpers import ( - escape_markdown, - from_timestamp, - to_timestamp, - DEFAULT_NONE, - DEFAULT_20, -) -from telegram.utils.types import JSONDict, FileInput, ODVInput, DVInput - -if TYPE_CHECKING: - from telegram import ( - Bot, - GameHighScore, - InputMedia, - MessageId, - InputMediaAudio, - InputMediaDocument, - InputMediaPhoto, - InputMediaVideo, - LabeledPrice, - ) - - -class Message(TelegramObject): - # fmt: off - """This object represents a message. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`message_id` and :attr:`chat` are equal. - - Note: - In Python ``from`` is a reserved word, use ``from_user`` instead. - - Args: - message_id (:obj:`int`): Unique message identifier inside this chat. - from_user (:class:`telegram.User`, optional): Sender of the message; empty for messages - sent to channels. For backward compatibility, this will contain a fake sender user in - non-channel chats, if the message was sent on behalf of a chat. - sender_chat (:class:`telegram.Chat`, optional): Sender of the message, sent on behalf of a - chat. For example, the channel itself for channel posts, the supergroup itself for - messages from anonymous group administrators, the linked channel for messages - automatically forwarded to the discussion group. For backward compatibility, - :attr:`from_user` contains a fake sender user in non-channel chats, if the message was - sent on behalf of a chat. - date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to - :class:`datetime.datetime`. - chat (:class:`telegram.Chat`): Conversation the message belongs to. - forward_from (:class:`telegram.User`, optional): For forwarded messages, sender of - the original message. - forward_from_chat (:class:`telegram.Chat`, optional): For messages forwarded from channels - or from anonymous administrators, information about the original sender chat. - forward_from_message_id (:obj:`int`, optional): For forwarded channel posts, identifier of - the original message in the channel. - forward_sender_name (:obj:`str`, optional): Sender's name for messages forwarded from users - who disallow adding a link to their account in forwarded messages. - forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the - original message was sent in Unix time. Converted to :class:`datetime.datetime`. - is_automatic_forward (:obj:`bool`, optional): :obj:`True`, if the message is a channel post - that was automatically forwarded to the connected discussion group. - - .. versionadded:: 13.9 - reply_to_message (:class:`telegram.Message`, optional): For replies, the original message. - edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix - time. Converted to :class:`datetime.datetime`. - has_protected_content (:obj:`bool`, optional): :obj:`True`, if the message can't be - forwarded. - - .. versionadded:: 13.9 - media_group_id (:obj:`str`, optional): The unique identifier of a media message group this - message belongs to. - text (str, optional): For text messages, the actual UTF-8 text of the message, 0-4096 - characters. Also found as :attr:`telegram.constants.MAX_MESSAGE_LENGTH`. - entities (List[:class:`telegram.MessageEntity`], optional): For text messages, special - entities like usernames, URLs, bot commands, etc. that appear in the text. See - :attr:`parse_entity` and :attr:`parse_entities` methods for how to use properly. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. For Messages with a - Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the - caption. See :attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities` - methods for how to use properly. - audio (:class:`telegram.Audio`, optional): Message is an audio file, information - about the file. - document (:class:`telegram.Document`, optional): Message is a general file, information - about the file. - animation (:class:`telegram.Animation`, optional): Message is an animation, information - about the animation. For backward compatibility, when this field is set, the document - field will also be set. - game (:class:`telegram.Game`, optional): Message is a game, information about the game. - photo (List[:class:`telegram.PhotoSize`], optional): Message is a photo, available - sizes of the photo. - sticker (:class:`telegram.Sticker`, optional): Message is a sticker, information - about the sticker. - video (:class:`telegram.Video`, optional): Message is a video, information about the video. - voice (:class:`telegram.Voice`, optional): Message is a voice message, information about - the file. - video_note (:class:`telegram.VideoNote`, optional): Message is a video note, information - about the video message. - new_chat_members (List[:class:`telegram.User`], optional): New members that were added to - the group or supergroup and information about them (the bot itself may be one of these - members). - caption (:obj:`str`, optional): Caption for the animation, audio, document, photo, video - or voice, 0-1024 characters. - contact (:class:`telegram.Contact`, optional): Message is a shared contact, information - about the contact. - location (:class:`telegram.Location`, optional): Message is a shared location, information - about the location. - venue (:class:`telegram.Venue`, optional): Message is a venue, information about the venue. - For backward compatibility, when this field is set, the location field will also be - set. - left_chat_member (:class:`telegram.User`, optional): A member was removed from the group, - information about them (this member may be the bot itself). - new_chat_title (:obj:`str`, optional): A chat title was changed to this value. - new_chat_photo (List[:class:`telegram.PhotoSize`], optional): A chat photo was changed to - this value. - delete_chat_photo (:obj:`bool`, optional): Service message: The chat photo was deleted. - group_chat_created (:obj:`bool`, optional): Service message: The group has been created. - supergroup_chat_created (:obj:`bool`, optional): Service message: The supergroup has been - created. This field can't be received in a message coming through updates, because bot - can't be a member of a supergroup when it is created. It can only be found in - :attr:`reply_to_message` if someone replies to a very first message in a directly - created supergroup. - channel_chat_created (:obj:`bool`, optional): Service message: The channel has been - created. This field can't be received in a message coming through updates, because bot - can't be a member of a channel when it is created. It can only be found in - :attr:`reply_to_message` if someone replies to a very first message in a channel. - message_auto_delete_timer_changed (:class:`telegram.MessageAutoDeleteTimerChanged`, \ - optional): Service message: auto-delete timer settings changed in the chat. - - .. versionadded:: 13.4 - migrate_to_chat_id (:obj:`int`, optional): The group has been migrated to a supergroup with - the specified identifier. This number may be greater than 32 bits and some programming - languages may have difficulty/silent defects in interpreting it. But it is smaller than - 52 bits, so a signed 64 bit integer or double-precision float type are safe for storing - this identifier. - migrate_from_chat_id (:obj:`int`, optional): The supergroup has been migrated from a group - with the specified identifier. This number may be greater than 32 bits and some - programming languages may have difficulty/silent defects in interpreting it. But it is - smaller than 52 bits, so a signed 64 bit integer or double-precision float type are - safe for storing this identifier. - pinned_message (:class:`telegram.Message`, optional): Specified message was pinned. Note - that the Message object in this field will not contain further :attr:`reply_to_message` - fields even if it is itself a reply. - invoice (:class:`telegram.Invoice`, optional): Message is an invoice for a payment, - information about the invoice. - successful_payment (:class:`telegram.SuccessfulPayment`, optional): Message is a service - message about a successful payment, information about the payment. - connected_website (:obj:`str`, optional): The domain name of the website on which the user - has logged in. - forward_signature (:obj:`str`, optional): For messages forwarded from channels, signature - of the post author if present. - author_signature (:obj:`str`, optional): Signature of the post author for messages in - channels, or the custom title of an anonymous group administrator. - passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data. - poll (:class:`telegram.Poll`, optional): Message is a native poll, - information about the poll. - dice (:class:`telegram.Dice`, optional): Message is a dice with random value from 1 to 6. - via_bot (:class:`telegram.User`, optional): Message was sent through an inline bot. - proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`, optional): Service - message. A user in the chat triggered another user's proximity alert while sharing - Live Location. - voice_chat_scheduled (:class:`telegram.VoiceChatScheduled`, optional): Service message: - voice chat scheduled. - - .. versionadded:: 13.5 - voice_chat_started (:class:`telegram.VoiceChatStarted`, optional): Service message: voice - chat started. - - .. versionadded:: 13.4 - voice_chat_ended (:class:`telegram.VoiceChatEnded`, optional): Service message: voice chat - ended. - - .. versionadded:: 13.4 - voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited` optional): - Service message: new participants invited to a voice chat. - - .. versionadded:: 13.4 - reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached - to the message. ``login_url`` buttons are represented as ordinary url buttons. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - - Attributes: - message_id (:obj:`int`): Unique message identifier inside this chat. - from_user (:class:`telegram.User`): Optional. Sender of the message; empty for messages - sent to channels. For backward compatibility, this will contain a fake sender user in - non-channel chats, if the message was sent on behalf of a chat. - sender_chat (:class:`telegram.Chat`): Optional. Sender of the message, sent on behalf of a - chat. For backward compatibility, :attr:`from_user` contains a fake sender user in - non-channel chats, if the message was sent on behalf of a chat. - date (:class:`datetime.datetime`): Date the message was sent. - chat (:class:`telegram.Chat`): Conversation the message belongs to. - forward_from (:class:`telegram.User`): Optional. Sender of the original message. - forward_from_chat (:class:`telegram.Chat`): Optional. For messages forwarded from channels - or from anonymous administrators, information about the original sender chat. - forward_from_message_id (:obj:`int`): Optional. Identifier of the original message in the - channel. - forward_date (:class:`datetime.datetime`): Optional. Date the original message was sent. - is_automatic_forward (:obj:`bool`): Optional. :obj:`True`, if the message is a channel post - that was automatically forwarded to the connected discussion group. - - .. versionadded:: 13.9 - reply_to_message (:class:`telegram.Message`): Optional. For replies, the original message. - Note that the Message object in this field will not contain further - ``reply_to_message`` fields even if it itself is a reply. - edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited. - has_protected_content (:obj:`bool`): Optional. :obj:`True`, if the message can't be - forwarded. - - .. versionadded:: 13.9 - media_group_id (:obj:`str`): Optional. The unique identifier of a media message group this - message belongs to. - text (:obj:`str`): Optional. The actual UTF-8 text of the message. - entities (List[:class:`telegram.MessageEntity`]): Optional. Special entities like - usernames, URLs, bot commands, etc. that appear in the text. See - :attr:`Message.parse_entity` and :attr:`parse_entities` methods for how to use - properly. - caption_entities (List[:class:`telegram.MessageEntity`]): Optional. Special entities like - usernames, URLs, bot commands, etc. that appear in the caption. See - :attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities` methods for how - to use properly. - audio (:class:`telegram.Audio`): Optional. Information about the file. - document (:class:`telegram.Document`): Optional. Information about the file. - animation (:class:`telegram.Animation`) Optional. Information about the file. - For backward compatibility, when this field is set, the document field will also be - set. - game (:class:`telegram.Game`): Optional. Information about the game. - photo (List[:class:`telegram.PhotoSize`]): Optional. Available sizes of the photo. - sticker (:class:`telegram.Sticker`): Optional. Information about the sticker. - video (:class:`telegram.Video`): Optional. Information about the video. - voice (:class:`telegram.Voice`): Optional. Information about the file. - video_note (:class:`telegram.VideoNote`): Optional. Information about the video message. - new_chat_members (List[:class:`telegram.User`]): Optional. Information about new members to - the chat. (the bot itself may be one of these members). - caption (:obj:`str`): Optional. Caption for the document, photo or video, 0-1024 - characters. - contact (:class:`telegram.Contact`): Optional. Information about the contact. - location (:class:`telegram.Location`): Optional. Information about the location. - venue (:class:`telegram.Venue`): Optional. Information about the venue. - left_chat_member (:class:`telegram.User`): Optional. Information about the user that left - the group. (this member may be the bot itself). - new_chat_title (:obj:`str`): Optional. A chat title was changed to this value. - new_chat_photo (List[:class:`telegram.PhotoSize`]): Optional. A chat photo was changed to - this value. - delete_chat_photo (:obj:`bool`): Optional. The chat photo was deleted. - group_chat_created (:obj:`bool`): Optional. The group has been created. - supergroup_chat_created (:obj:`bool`): Optional. The supergroup has been created. - channel_chat_created (:obj:`bool`): Optional. The channel has been created. - message_auto_delete_timer_changed (:class:`telegram.MessageAutoDeleteTimerChanged`): - Optional. Service message: auto-delete timer settings changed in the chat. - - .. versionadded:: 13.4 - migrate_to_chat_id (:obj:`int`): Optional. The group has been migrated to a supergroup with - the specified identifier. - migrate_from_chat_id (:obj:`int`): Optional. The supergroup has been migrated from a group - with the specified identifier. - pinned_message (:class:`telegram.message`): Optional. Specified message was pinned. - invoice (:class:`telegram.Invoice`): Optional. Information about the invoice. - successful_payment (:class:`telegram.SuccessfulPayment`): Optional. Information about the - payment. - connected_website (:obj:`str`): Optional. The domain name of the website on which the user - has logged in. - forward_signature (:obj:`str`): Optional. Signature of the post author for messages - forwarded from channels. - forward_sender_name (:obj:`str`): Optional. Sender's name for messages forwarded from users - who disallow adding a link to their account in forwarded messages. - author_signature (:obj:`str`): Optional. Signature of the post author for messages in - channels, or the custom title of an anonymous group administrator. - passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data. - poll (:class:`telegram.Poll`): Optional. Message is a native poll, - information about the poll. - dice (:class:`telegram.Dice`): Optional. Message is a dice. - via_bot (:class:`telegram.User`): Optional. Bot through which the message was sent. - proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`): Optional. Service - message. A user in the chat triggered another user's proximity alert while sharing - Live Location. - voice_chat_scheduled (:class:`telegram.VoiceChatScheduled`): Optional. Service message: - voice chat scheduled. - - .. versionadded:: 13.5 - voice_chat_started (:class:`telegram.VoiceChatStarted`): Optional. Service message: voice - chat started. - - .. versionadded:: 13.4 - voice_chat_ended (:class:`telegram.VoiceChatEnded`): Optional. Service message: voice chat - ended. - - .. versionadded:: 13.4 - voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited`): Optional. - Service message: new participants invited to a voice chat. - - .. versionadded:: 13.4 - reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached - to the message. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - # fmt: on - __slots__ = ( - 'reply_markup', - 'audio', - 'contact', - 'migrate_to_chat_id', - 'forward_signature', - 'chat', - 'successful_payment', - 'game', - 'text', - 'forward_sender_name', - 'document', - 'new_chat_title', - 'forward_date', - 'group_chat_created', - 'media_group_id', - 'caption', - 'video', - 'bot', - 'entities', - 'via_bot', - 'new_chat_members', - 'connected_website', - 'animation', - 'migrate_from_chat_id', - 'forward_from', - 'sticker', - 'location', - 'venue', - 'edit_date', - 'reply_to_message', - 'passport_data', - 'pinned_message', - 'forward_from_chat', - 'new_chat_photo', - 'message_id', - 'delete_chat_photo', - 'from_user', - 'author_signature', - 'proximity_alert_triggered', - 'sender_chat', - 'dice', - 'forward_from_message_id', - 'caption_entities', - 'voice', - 'date', - 'supergroup_chat_created', - 'poll', - 'left_chat_member', - 'photo', - 'channel_chat_created', - 'invoice', - 'video_note', - '_effective_attachment', - 'message_auto_delete_timer_changed', - 'voice_chat_ended', - 'voice_chat_participants_invited', - 'voice_chat_started', - 'voice_chat_scheduled', - 'is_automatic_forward', - 'has_protected_content', - '_id_attrs', - ) - - ATTACHMENT_TYPES: ClassVar[List[str]] = [ - 'audio', - 'game', - 'animation', - 'document', - 'photo', - 'sticker', - 'video', - 'voice', - 'video_note', - 'contact', - 'location', - 'venue', - 'invoice', - 'successful_payment', - ] - MESSAGE_TYPES: ClassVar[List[str]] = [ - 'text', - 'new_chat_members', - 'left_chat_member', - 'new_chat_title', - 'new_chat_photo', - 'delete_chat_photo', - 'group_chat_created', - 'supergroup_chat_created', - 'channel_chat_created', - 'message_auto_delete_timer_changed', - 'migrate_to_chat_id', - 'migrate_from_chat_id', - 'pinned_message', - 'poll', - 'dice', - 'passport_data', - 'proximity_alert_triggered', - 'voice_chat_scheduled', - 'voice_chat_started', - 'voice_chat_ended', - 'voice_chat_participants_invited', - ] + ATTACHMENT_TYPES - - def __init__( - self, - message_id: int, - date: datetime.datetime, - chat: Chat, - from_user: User = None, - forward_from: User = None, - forward_from_chat: Chat = None, - forward_from_message_id: int = None, - forward_date: datetime.datetime = None, - reply_to_message: 'Message' = None, - edit_date: datetime.datetime = None, - text: str = None, - entities: List['MessageEntity'] = None, - caption_entities: List['MessageEntity'] = None, - audio: Audio = None, - document: Document = None, - game: Game = None, - photo: List[PhotoSize] = None, - sticker: Sticker = None, - video: Video = None, - voice: Voice = None, - video_note: VideoNote = None, - new_chat_members: List[User] = None, - caption: str = None, - contact: Contact = None, - location: Location = None, - venue: Venue = None, - left_chat_member: User = None, - new_chat_title: str = None, - new_chat_photo: List[PhotoSize] = None, - delete_chat_photo: bool = False, - group_chat_created: bool = False, - supergroup_chat_created: bool = False, - channel_chat_created: bool = False, - migrate_to_chat_id: int = None, - migrate_from_chat_id: int = None, - pinned_message: 'Message' = None, - invoice: Invoice = None, - successful_payment: SuccessfulPayment = None, - forward_signature: str = None, - author_signature: str = None, - media_group_id: str = None, - connected_website: str = None, - animation: Animation = None, - passport_data: PassportData = None, - poll: Poll = None, - forward_sender_name: str = None, - reply_markup: InlineKeyboardMarkup = None, - bot: 'Bot' = None, - dice: Dice = None, - via_bot: User = None, - proximity_alert_triggered: ProximityAlertTriggered = None, - sender_chat: Chat = None, - voice_chat_started: VoiceChatStarted = None, - voice_chat_ended: VoiceChatEnded = None, - voice_chat_participants_invited: VoiceChatParticipantsInvited = None, - message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = None, - voice_chat_scheduled: VoiceChatScheduled = None, - is_automatic_forward: bool = None, - has_protected_content: bool = None, - **_kwargs: Any, - ): - # Required - self.message_id = int(message_id) - # Optionals - self.from_user = from_user - self.sender_chat = sender_chat - self.date = date - self.chat = chat - self.forward_from = forward_from - self.forward_from_chat = forward_from_chat - self.forward_date = forward_date - self.is_automatic_forward = is_automatic_forward - self.reply_to_message = reply_to_message - self.edit_date = edit_date - self.has_protected_content = has_protected_content - self.text = text - self.entities = entities or [] - self.caption_entities = caption_entities or [] - self.audio = audio - self.game = game - self.document = document - self.photo = photo or [] - self.sticker = sticker - self.video = video - self.voice = voice - self.video_note = video_note - self.caption = caption - self.contact = contact - self.location = location - self.venue = venue - self.new_chat_members = new_chat_members or [] - self.left_chat_member = left_chat_member - self.new_chat_title = new_chat_title - self.new_chat_photo = new_chat_photo or [] - self.delete_chat_photo = bool(delete_chat_photo) - self.group_chat_created = bool(group_chat_created) - self.supergroup_chat_created = bool(supergroup_chat_created) - self.migrate_to_chat_id = migrate_to_chat_id - self.migrate_from_chat_id = migrate_from_chat_id - self.channel_chat_created = bool(channel_chat_created) - self.message_auto_delete_timer_changed = message_auto_delete_timer_changed - self.pinned_message = pinned_message - self.forward_from_message_id = forward_from_message_id - self.invoice = invoice - self.successful_payment = successful_payment - self.connected_website = connected_website - self.forward_signature = forward_signature - self.forward_sender_name = forward_sender_name - self.author_signature = author_signature - self.media_group_id = media_group_id - self.animation = animation - self.passport_data = passport_data - self.poll = poll - self.dice = dice - self.via_bot = via_bot - self.proximity_alert_triggered = proximity_alert_triggered - self.voice_chat_scheduled = voice_chat_scheduled - self.voice_chat_started = voice_chat_started - self.voice_chat_ended = voice_chat_ended - self.voice_chat_participants_invited = voice_chat_participants_invited - self.reply_markup = reply_markup - self.bot = bot - - self._effective_attachment = DEFAULT_NONE - - self._id_attrs = (self.message_id, self.chat) - - @property - def chat_id(self) -> int: - """:obj:`int`: Shortcut for :attr:`telegram.Chat.id` for :attr:`chat`.""" - return self.chat.id - - @property - def link(self) -> Optional[str]: - """:obj:`str`: Convenience property. If the chat of the message is not - a private chat or normal group, returns a t.me link of the message. - """ - if self.chat.type not in [Chat.PRIVATE, Chat.GROUP]: - if self.chat.username: - to_link = self.chat.username - else: - # Get rid of leading -100 for supergroups - to_link = f"c/{str(self.chat.id)[4:]}" - return f"https://t.me/{to_link}/{self.message_id}" - return None - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Message']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['from_user'] = User.de_json(data.get('from'), bot) - data['sender_chat'] = Chat.de_json(data.get('sender_chat'), bot) - data['date'] = from_timestamp(data['date']) - data['chat'] = Chat.de_json(data.get('chat'), bot) - data['entities'] = MessageEntity.de_list(data.get('entities'), bot) - data['caption_entities'] = MessageEntity.de_list(data.get('caption_entities'), bot) - data['forward_from'] = User.de_json(data.get('forward_from'), bot) - data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'), bot) - data['forward_date'] = from_timestamp(data.get('forward_date')) - data['reply_to_message'] = Message.de_json(data.get('reply_to_message'), bot) - data['edit_date'] = from_timestamp(data.get('edit_date')) - data['audio'] = Audio.de_json(data.get('audio'), bot) - data['document'] = Document.de_json(data.get('document'), bot) - data['animation'] = Animation.de_json(data.get('animation'), bot) - data['game'] = Game.de_json(data.get('game'), bot) - data['photo'] = PhotoSize.de_list(data.get('photo'), bot) - data['sticker'] = Sticker.de_json(data.get('sticker'), bot) - data['video'] = Video.de_json(data.get('video'), bot) - data['voice'] = Voice.de_json(data.get('voice'), bot) - data['video_note'] = VideoNote.de_json(data.get('video_note'), bot) - data['contact'] = Contact.de_json(data.get('contact'), bot) - data['location'] = Location.de_json(data.get('location'), bot) - data['venue'] = Venue.de_json(data.get('venue'), bot) - data['new_chat_members'] = User.de_list(data.get('new_chat_members'), bot) - data['left_chat_member'] = User.de_json(data.get('left_chat_member'), bot) - data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'), bot) - data['message_auto_delete_timer_changed'] = MessageAutoDeleteTimerChanged.de_json( - data.get('message_auto_delete_timer_changed'), bot - ) - data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot) - data['invoice'] = Invoice.de_json(data.get('invoice'), bot) - data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot) - data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot) - data['poll'] = Poll.de_json(data.get('poll'), bot) - data['dice'] = Dice.de_json(data.get('dice'), bot) - data['via_bot'] = User.de_json(data.get('via_bot'), bot) - data['proximity_alert_triggered'] = ProximityAlertTriggered.de_json( - data.get('proximity_alert_triggered'), bot - ) - data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot) - data['voice_chat_scheduled'] = VoiceChatScheduled.de_json( - data.get('voice_chat_scheduled'), bot - ) - data['voice_chat_started'] = VoiceChatStarted.de_json(data.get('voice_chat_started'), bot) - data['voice_chat_ended'] = VoiceChatEnded.de_json(data.get('voice_chat_ended'), bot) - data['voice_chat_participants_invited'] = VoiceChatParticipantsInvited.de_json( - data.get('voice_chat_participants_invited'), bot - ) - return cls(bot=bot, **data) - - @property - def effective_attachment( - self, - ) -> Union[ - Contact, - Document, - Animation, - Game, - Invoice, - Location, - List[PhotoSize], - Sticker, - SuccessfulPayment, - Venue, - Video, - VideoNote, - Voice, - None, - ]: - """ - :class:`telegram.Audio` - or :class:`telegram.Contact` - or :class:`telegram.Document` - or :class:`telegram.Animation` - or :class:`telegram.Game` - or :class:`telegram.Invoice` - or :class:`telegram.Location` - or List[:class:`telegram.PhotoSize`] - or :class:`telegram.Sticker` - or :class:`telegram.SuccessfulPayment` - or :class:`telegram.Venue` - or :class:`telegram.Video` - or :class:`telegram.VideoNote` - or :class:`telegram.Voice`: The attachment that this message was sent with. May be - :obj:`None` if no attachment was sent. - - """ - if self._effective_attachment is not DEFAULT_NONE: - return self._effective_attachment # type: ignore - - for i in Message.ATTACHMENT_TYPES: - if getattr(self, i, None): - self._effective_attachment = getattr(self, i) - break - else: - self._effective_attachment = None - - return self._effective_attachment # type: ignore - - def __getitem__(self, item: str) -> Any: # pylint: disable=R1710 - return self.chat.id if item == 'chat_id' else super().__getitem__(item) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - # Required - data['date'] = to_timestamp(self.date) - # Optionals - if self.forward_date: - data['forward_date'] = to_timestamp(self.forward_date) - if self.edit_date: - data['edit_date'] = to_timestamp(self.edit_date) - if self.photo: - data['photo'] = [p.to_dict() for p in self.photo] - if self.entities: - data['entities'] = [e.to_dict() for e in self.entities] - if self.caption_entities: - data['caption_entities'] = [e.to_dict() for e in self.caption_entities] - if self.new_chat_photo: - data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo] - if self.new_chat_members: - data['new_chat_members'] = [u.to_dict() for u in self.new_chat_members] - - return data - - def _quote(self, quote: Optional[bool], reply_to_message_id: Optional[int]) -> Optional[int]: - """Modify kwargs for replying with or without quoting.""" - if reply_to_message_id is not None: - return reply_to_message_id - - if quote is not None: - if quote: - return self.message_id - - else: - if self.bot.defaults: - default_quote = self.bot.defaults.quote - else: - default_quote = None - if (default_quote is None and self.chat.type != Chat.PRIVATE) or default_quote: - return self.message_id - - return None - - def reply_text( - self, - text: str, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_message(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the message is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_message( - chat_id=self.chat_id, - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - entities=entities, - protect_content=protect_content, - ) - - def reply_markdown( - self, - text: str, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_message( - update.effective_message.chat_id, - parse_mode=ParseMode.MARKDOWN, - *args, - **kwargs, - ) - - Sends a message with Markdown version 1 formatting. - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`. - - Note: - :attr:`telegram.ParseMode.MARKDOWN` is a legacy mode, retained by Telegram for - backward compatibility. You should use :meth:`reply_markdown_v2` instead. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the message is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_message( - chat_id=self.chat_id, - text=text, - parse_mode=ParseMode.MARKDOWN, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - entities=entities, - protect_content=protect_content, - ) - - def reply_markdown_v2( - self, - text: str, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_message( - update.effective_message.chat_id, - parse_mode=ParseMode.MARKDOWN_V2, - *args, - **kwargs, - ) - - Sends a message with markdown version 2 formatting. - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the message is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_message( - chat_id=self.chat_id, - text=text, - parse_mode=ParseMode.MARKDOWN_V2, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - entities=entities, - protect_content=protect_content, - ) - - def reply_html( - self, - text: str, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_message( - update.effective_message.chat_id, - parse_mode=ParseMode.HTML, - *args, - **kwargs, - ) - - Sends a message with HTML formatting. - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the message is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_message( - chat_id=self.chat_id, - text=text, - parse_mode=ParseMode.HTML, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - entities=entities, - protect_content=protect_content, - ) - - def reply_media_group( - self, - media: List[ - Union['InputMediaAudio', 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo'] - ], - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - protect_content: bool = None, - ) -> List['Message']: - """Shortcut for:: - - bot.send_media_group(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_media_group`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the media group is sent as an - actual reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - Returns: - List[:class:`telegram.Message`]: An array of the sent Messages. - - Raises: - :class:`telegram.error.TelegramError` - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_media_group( - chat_id=self.chat_id, - media=media, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def reply_photo( - self, - photo: Union[FileInput, 'PhotoSize'], - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_photo(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_photo`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the photo is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_photo( - chat_id=self.chat_id, - photo=photo, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def reply_audio( - self, - audio: Union[FileInput, 'Audio'], - duration: int = None, - performer: str = None, - title: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_audio(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_audio`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the audio is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_audio( - chat_id=self.chat_id, - audio=audio, - duration=duration, - performer=performer, - title=title, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def reply_document( - self, - document: Union[FileInput, 'Document'], - filename: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - disable_content_type_detection: bool = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_document(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_document`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the document is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_document( - chat_id=self.chat_id, - document=document, - filename=filename, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - thumb=thumb, - api_kwargs=api_kwargs, - disable_content_type_detection=disable_content_type_detection, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - protect_content=protect_content, - ) - - def reply_animation( - self, - animation: Union[FileInput, 'Animation'], - duration: int = None, - width: int = None, - height: int = None, - thumb: FileInput = None, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_animation(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_animation`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the animation is sent as an - actual reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_animation( - chat_id=self.chat_id, - animation=animation, - duration=duration, - width=width, - height=height, - thumb=thumb, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def reply_sticker( - self, - sticker: Union[FileInput, 'Sticker'], - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_sticker(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_sticker`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the sticker is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_sticker( - chat_id=self.chat_id, - sticker=sticker, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def reply_video( - self, - video: Union[FileInput, 'Video'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - width: int = None, - height: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - supports_streaming: bool = None, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_video(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_video`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the video is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_video( - chat_id=self.chat_id, - video=video, - duration=duration, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - width=width, - height=height, - parse_mode=parse_mode, - supports_streaming=supports_streaming, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def reply_video_note( - self, - video_note: Union[FileInput, 'VideoNote'], - duration: int = None, - length: int = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - filename: str = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_video_note(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_video_note`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the video note is sent as an - actual reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_video_note( - chat_id=self.chat_id, - video_note=video_note, - duration=duration, - length=length, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - filename=filename, - protect_content=protect_content, - ) - - def reply_voice( - self, - voice: Union[FileInput, 'Voice'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_voice(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_voice`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the voice note is sent as an - actual reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_voice( - chat_id=self.chat_id, - voice=voice, - duration=duration, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def reply_location( - self, - latitude: float = None, - longitude: float = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - location: Location = None, - live_period: int = None, - api_kwargs: JSONDict = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_location(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_location`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the location is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_location( - chat_id=self.chat_id, - latitude=latitude, - longitude=longitude, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - location=location, - live_period=live_period, - api_kwargs=api_kwargs, - horizontal_accuracy=horizontal_accuracy, - heading=heading, - proximity_alert_radius=proximity_alert_radius, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def reply_venue( - self, - latitude: float = None, - longitude: float = None, - title: str = None, - address: str = None, - foursquare_id: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - venue: Venue = None, - foursquare_type: str = None, - api_kwargs: JSONDict = None, - google_place_id: str = None, - google_place_type: str = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_venue(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_venue`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the venue is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_venue( - chat_id=self.chat_id, - latitude=latitude, - longitude=longitude, - title=title, - address=address, - foursquare_id=foursquare_id, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - venue=venue, - foursquare_type=foursquare_type, - api_kwargs=api_kwargs, - google_place_id=google_place_id, - google_place_type=google_place_type, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def reply_contact( - self, - phone_number: str = None, - first_name: str = None, - last_name: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - contact: Contact = None, - vcard: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_contact(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_contact`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the contact is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in - private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_contact( - chat_id=self.chat_id, - phone_number=phone_number, - first_name=first_name, - last_name=last_name, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - contact=contact, - vcard=vcard, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def reply_poll( - self, - question: str, - options: List[str], - is_anonymous: bool = True, - type: str = Poll.REGULAR, # pylint: disable=W0622 - allows_multiple_answers: bool = False, - correct_option_id: int = None, - is_closed: bool = None, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - explanation: str = None, - explanation_parse_mode: ODVInput[str] = DEFAULT_NONE, - open_period: int = None, - close_date: Union[int, datetime.datetime] = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_poll(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_poll`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the poll is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_poll( - chat_id=self.chat_id, - question=question, - options=options, - is_anonymous=is_anonymous, - type=type, - allows_multiple_answers=allows_multiple_answers, - correct_option_id=correct_option_id, - is_closed=is_closed, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - explanation=explanation, - explanation_parse_mode=explanation_parse_mode, - open_period=open_period, - close_date=close_date, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - explanation_entities=explanation_entities, - protect_content=protect_content, - ) - - def reply_dice( - self, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - emoji: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_dice(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_dice`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the dice is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` - in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_dice( - chat_id=self.chat_id, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - emoji=emoji, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def reply_chat_action( - self, - action: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.send_chat_action(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_chat_action`. - - .. versionadded:: 13.2 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.send_chat_action( - chat_id=self.chat_id, - action=action, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def reply_game( - self, - game_short_name: str, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_game(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_game`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the game is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` - in private chats. - - .. versionadded:: 13.2 - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_game( - chat_id=self.chat_id, - game_short_name=game_short_name, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def reply_invoice( - self, - title: str, - description: str, - payload: str, - provider_token: str, - currency: str, - prices: List['LabeledPrice'], - start_parameter: str = None, - photo_url: str = None, - photo_size: int = None, - photo_width: int = None, - photo_height: int = None, - need_name: bool = None, - need_phone_number: bool = None, - need_email: bool = None, - need_shipping_address: bool = None, - is_flexible: bool = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'InlineKeyboardMarkup' = None, - provider_data: Union[str, object] = None, - send_phone_number_to_provider: bool = None, - send_email_to_provider: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - quote: bool = None, - max_tip_amount: int = None, - suggested_tip_amounts: List[int] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_invoice(update.effective_message.chat_id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_invoice`. - - Warning: - As of API 5.2 :attr:`start_parameter` is an optional argument and therefore the order - of the arguments had to be changed. Use keyword arguments to make sure that the - arguments are passed correctly. - - .. versionadded:: 13.2 - - .. versionchanged:: 13.5 - As of Bot API 5.2, the parameter :attr:`start_parameter` is optional. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the invoice is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this - parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` - in private chats. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.send_invoice( - chat_id=self.chat_id, - title=title, - description=description, - payload=payload, - provider_token=provider_token, - currency=currency, - prices=prices, - start_parameter=start_parameter, - photo_url=photo_url, - photo_size=photo_size, - photo_width=photo_width, - photo_height=photo_height, - need_name=need_name, - need_phone_number=need_phone_number, - need_email=need_email, - need_shipping_address=need_shipping_address, - is_flexible=is_flexible, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - provider_data=provider_data, - send_phone_number_to_provider=send_phone_number_to_provider, - send_email_to_provider=send_email_to_provider, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - max_tip_amount=max_tip_amount, - suggested_tip_amounts=suggested_tip_amounts, - protect_content=protect_content, - ) - - def forward( - self, - chat_id: Union[int, str], - disable_notification: DVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.forward_message(chat_id=chat_id, - from_chat_id=update.effective_message.chat_id, - message_id=update.effective_message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.forward_message`. - - Note: - Since the release of Bot API 5.5 it can be impossible to forward messages from - some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and - :attr:`telegram.Chat.has_protected_content` to check this. - - As a workaround, it is still possible to use :meth:`copy`. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, instance representing the message forwarded. - - """ - return self.bot.forward_message( - chat_id=chat_id, - from_chat_id=self.chat_id, - message_id=self.message_id, - disable_notification=disable_notification, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def copy( - self, - chat_id: Union[int, str], - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> 'MessageId': - """Shortcut for:: - - bot.copy_message(chat_id=chat_id, - from_chat_id=update.effective_message.chat_id, - message_id=update.effective_message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.copy_message`. - - Returns: - :class:`telegram.MessageId`: On success, returns the MessageId of the sent message. - - """ - return self.bot.copy_message( - chat_id=chat_id, - from_chat_id=self.chat_id, - message_id=self.message_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def reply_copy( - self, - from_chat_id: Union[str, int], - message_id: int, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: ReplyMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - quote: bool = None, - protect_content: bool = None, - ) -> 'MessageId': - """Shortcut for:: - - bot.copy_message(chat_id=message.chat.id, - from_chat_id=from_chat_id, - message_id=message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.copy_message`. - - Args: - quote (:obj:`bool`, optional): If set to :obj:`True`, the copy is sent as an actual - reply to this message. If ``reply_to_message_id`` is passed in ``kwargs``, - this parameter will be ignored. Default: :obj:`True` in group chats and - :obj:`False` in private chats. - - .. versionadded:: 13.1 - - Returns: - :class:`telegram.MessageId`: On success, returns the MessageId of the sent message. - - """ - reply_to_message_id = self._quote(quote, reply_to_message_id) - return self.bot.copy_message( - chat_id=self.chat_id, - from_chat_id=from_chat_id, - message_id=message_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def edit_text( - self, - text: str, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - ) -> Union['Message', bool]: - """Shortcut for:: - - bot.edit_message_text(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.edit_message_text`. - - Note: - You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise ``True`` is returned. - - """ - return self.bot.edit_message_text( - chat_id=self.chat_id, - message_id=self.message_id, - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - entities=entities, - inline_message_id=None, - ) - - def edit_caption( - self, - caption: str = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - ) -> Union['Message', bool]: - """Shortcut for:: - - bot.edit_message_caption(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_caption`. - - Note: - You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise ``True`` is returned. - - """ - return self.bot.edit_message_caption( - chat_id=self.chat_id, - message_id=self.message_id, - caption=caption, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - caption_entities=caption_entities, - inline_message_id=None, - ) - - def edit_media( - self, - media: 'InputMedia' = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union['Message', bool]: - """Shortcut for:: - - bot.edit_message_media(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_media`. - - Note: - You can only edit messages that the bot sent itself(i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise ``True`` is returned. - - """ - return self.bot.edit_message_media( - chat_id=self.chat_id, - message_id=self.message_id, - media=media, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - inline_message_id=None, - ) - - def edit_reply_markup( - self, - reply_markup: Optional['InlineKeyboardMarkup'] = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union['Message', bool]: - """Shortcut for:: - - bot.edit_message_reply_markup(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_reply_markup`. - - Note: - You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise ``True`` is returned. - """ - return self.bot.edit_message_reply_markup( - chat_id=self.chat_id, - message_id=self.message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - inline_message_id=None, - ) - - def edit_live_location( - self, - latitude: float = None, - longitude: float = None, - location: Location = None, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - ) -> Union['Message', bool]: - """Shortcut for:: - - bot.edit_message_live_location(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.edit_message_live_location`. - - Note: - You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - """ - return self.bot.edit_message_live_location( - chat_id=self.chat_id, - message_id=self.message_id, - latitude=latitude, - longitude=longitude, - location=location, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - horizontal_accuracy=horizontal_accuracy, - heading=heading, - proximity_alert_radius=proximity_alert_radius, - inline_message_id=None, - ) - - def stop_live_location( - self, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union['Message', bool]: - """Shortcut for:: - - bot.stop_message_live_location(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.stop_message_live_location`. - - Note: - You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - """ - return self.bot.stop_message_live_location( - chat_id=self.chat_id, - message_id=self.message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - inline_message_id=None, - ) - - def set_game_score( - self, - user_id: Union[int, str], - score: int, - force: bool = None, - disable_edit_message: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Union['Message', bool]: - """Shortcut for:: - - bot.set_game_score(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.set_game_score`. - - Note: - You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - :class:`telegram.Message`: On success, if edited message is sent by the bot, the - edited Message is returned, otherwise :obj:`True` is returned. - """ - return self.bot.set_game_score( - chat_id=self.chat_id, - message_id=self.message_id, - user_id=user_id, - score=score, - force=force, - disable_edit_message=disable_edit_message, - timeout=timeout, - api_kwargs=api_kwargs, - inline_message_id=None, - ) - - def get_game_high_scores( - self, - user_id: Union[int, str], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> List['GameHighScore']: - """Shortcut for:: - - bot.get_game_high_scores(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.get_game_high_scores`. - - Note: - You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family - of methods) or channel posts, if the bot is an admin in that channel. However, this - behaviour is undocumented and might be changed by Telegram. - - Returns: - List[:class:`telegram.GameHighScore`] - """ - return self.bot.get_game_high_scores( - chat_id=self.chat_id, - message_id=self.message_id, - user_id=user_id, - timeout=timeout, - api_kwargs=api_kwargs, - inline_message_id=None, - ) - - def delete( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.delete_message(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.delete_message`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.delete_message( - chat_id=self.chat_id, - message_id=self.message_id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def stop_poll( - self, - reply_markup: InlineKeyboardMarkup = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Poll: - """Shortcut for:: - - bot.stop_poll(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.stop_poll`. - - Returns: - :class:`telegram.Poll`: On success, the stopped Poll with the final results is - returned. - - """ - return self.bot.stop_poll( - chat_id=self.chat_id, - message_id=self.message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def pin( - self, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.pin_chat_message(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.pin_chat_message`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.pin_chat_message( - chat_id=self.chat_id, - message_id=self.message_id, - disable_notification=disable_notification, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def unpin( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.unpin_chat_message(chat_id=message.chat_id, - message_id=message.message_id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.unpin_chat_message`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unpin_chat_message( - chat_id=self.chat_id, - message_id=self.message_id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def parse_entity(self, entity: MessageEntity) -> str: - """Returns the text from a given :class:`telegram.MessageEntity`. - - Note: - This method is present because Telegram calculates the offset and length in - UTF-16 codepoint pairs, which some versions of Python don't handle automatically. - (That is, you can't just slice ``Message.text`` with the offset and length.) - - Args: - entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must - be an entity that belongs to this message. - - Returns: - :obj:`str`: The text of the given entity. - - Raises: - RuntimeError: If the message has no text. - - """ - if not self.text: - raise RuntimeError("This Message has no 'text'.") - - # Is it a narrow build, if so we don't need to convert - if sys.maxunicode == 0xFFFF: - return self.text[entity.offset : entity.offset + entity.length] - - entity_text = self.text.encode('utf-16-le') - entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2] - return entity_text.decode('utf-16-le') - - def parse_caption_entity(self, entity: MessageEntity) -> str: - """Returns the text from a given :class:`telegram.MessageEntity`. - - Note: - This method is present because Telegram calculates the offset and length in - UTF-16 codepoint pairs, which some versions of Python don't handle automatically. - (That is, you can't just slice ``Message.caption`` with the offset and length.) - - Args: - entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must - be an entity that belongs to this message. - - Returns: - :obj:`str`: The text of the given entity. - - Raises: - RuntimeError: If the message has no caption. - - """ - if not self.caption: - raise RuntimeError("This Message has no 'caption'.") - - # Is it a narrow build, if so we don't need to convert - if sys.maxunicode == 0xFFFF: - return self.caption[entity.offset : entity.offset + entity.length] - - entity_text = self.caption.encode('utf-16-le') - entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2] - return entity_text.decode('utf-16-le') - - def parse_entities(self, types: List[str] = None) -> Dict[MessageEntity, str]: - """ - Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`. - It contains entities from this message filtered by their - :attr:`telegram.MessageEntity.type` attribute as the key, and the text that each entity - belongs to as the value of the :obj:`dict`. - - Note: - This method should always be used instead of the :attr:`entities` attribute, since it - calculates the correct substring from the message text based on UTF-16 codepoints. - See :attr:`parse_entity` for more info. - - Args: - types (List[:obj:`str`], optional): List of :class:`telegram.MessageEntity` types as - strings. If the ``type`` attribute of an entity is contained in this list, it will - be returned. Defaults to a list of all types. All types can be found as constants - in :class:`telegram.MessageEntity`. - - Returns: - Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to - the text that belongs to them, calculated based on UTF-16 codepoints. - - """ - if types is None: - types = MessageEntity.ALL_TYPES - - return { - entity: self.parse_entity(entity) - for entity in (self.entities or []) - if entity.type in types - } - - def parse_caption_entities(self, types: List[str] = None) -> Dict[MessageEntity, str]: - """ - Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`. - It contains entities from this message's caption filtered by their - :attr:`telegram.MessageEntity.type` attribute as the key, and the text that each entity - belongs to as the value of the :obj:`dict`. - - Note: - This method should always be used instead of the :attr:`caption_entities` attribute, - since it calculates the correct substring from the message text based on UTF-16 - codepoints. See :attr:`parse_entity` for more info. - - Args: - types (List[:obj:`str`], optional): List of :class:`telegram.MessageEntity` types as - strings. If the ``type`` attribute of an entity is contained in this list, it will - be returned. Defaults to a list of all types. All types can be found as constants - in :class:`telegram.MessageEntity`. - - Returns: - Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to - the text that belongs to them, calculated based on UTF-16 codepoints. - - """ - if types is None: - types = MessageEntity.ALL_TYPES - - return { - entity: self.parse_caption_entity(entity) - for entity in (self.caption_entities or []) - if entity.type in types - } - - @staticmethod - def _parse_html( - message_text: Optional[str], - entities: Dict[MessageEntity, str], - urled: bool = False, - offset: int = 0, - ) -> Optional[str]: - if message_text is None: - return None - - if sys.maxunicode != 0xFFFF: - message_text = message_text.encode('utf-16-le') # type: ignore - - html_text = '' - last_offset = 0 - - sorted_entities = sorted(entities.items(), key=(lambda item: item[0].offset)) - parsed_entities = [] - - for (entity, text) in sorted_entities: - if entity not in parsed_entities: - nested_entities = { - e: t - for (e, t) in sorted_entities - if e.offset >= entity.offset - and e.offset + e.length <= entity.offset + entity.length - and e != entity - } - parsed_entities.extend(list(nested_entities.keys())) - - orig_text = text - text = escape(text) - - if nested_entities: - text = Message._parse_html( - orig_text, nested_entities, urled=urled, offset=entity.offset - ) - - if entity.type == MessageEntity.TEXT_LINK: - insert = f'{text}' - elif entity.type == MessageEntity.TEXT_MENTION and entity.user: - insert = f'{text}' - elif entity.type == MessageEntity.URL and urled: - insert = f'{text}' - elif entity.type == MessageEntity.BOLD: - insert = '' + text + '' - elif entity.type == MessageEntity.ITALIC: - insert = '' + text + '' - elif entity.type == MessageEntity.CODE: - insert = '' + text + '' - elif entity.type == MessageEntity.PRE: - if entity.language: - insert = f'
{text}
' - else: - insert = '
' + text + '
' - elif entity.type == MessageEntity.UNDERLINE: - insert = '' + text + '' - elif entity.type == MessageEntity.STRIKETHROUGH: - insert = '' + text + '' - elif entity.type == MessageEntity.SPOILER: - insert = f'{text}' - else: - insert = text - - if offset == 0: - if sys.maxunicode == 0xFFFF: - html_text += ( - escape(message_text[last_offset : entity.offset - offset]) + insert - ) - else: - html_text += ( - escape( - message_text[ # type: ignore - last_offset * 2 : (entity.offset - offset) * 2 - ].decode('utf-16-le') - ) - + insert - ) - else: - if sys.maxunicode == 0xFFFF: - html_text += message_text[last_offset : entity.offset - offset] + insert - else: - html_text += ( - message_text[ # type: ignore - last_offset * 2 : (entity.offset - offset) * 2 - ].decode('utf-16-le') - + insert - ) - - last_offset = entity.offset - offset + entity.length - - if offset == 0: - if sys.maxunicode == 0xFFFF: - html_text += escape(message_text[last_offset:]) - else: - html_text += escape( - message_text[last_offset * 2 :].decode('utf-16-le') # type: ignore - ) - else: - if sys.maxunicode == 0xFFFF: - html_text += message_text[last_offset:] - else: - html_text += message_text[last_offset * 2 :].decode('utf-16-le') # type: ignore - - return html_text - - @property - def text_html(self) -> str: - """Creates an HTML-formatted string from the markup entities found in the message. - - Use this if you want to retrieve the message text with the entities formatted as HTML in - the same way the original message was formatted. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as HTML. - - Returns: - :obj:`str`: Message text with entities formatted as HTML. - - """ - return self._parse_html(self.text, self.parse_entities(), urled=False) - - @property - def text_html_urled(self) -> str: - """Creates an HTML-formatted string from the markup entities found in the message. - - Use this if you want to retrieve the message text with the entities formatted as HTML. - This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as HTML. - - Returns: - :obj:`str`: Message text with entities formatted as HTML. - - """ - return self._parse_html(self.text, self.parse_entities(), urled=True) - - @property - def caption_html(self) -> str: - """Creates an HTML-formatted string from the markup entities found in the message's - caption. - - Use this if you want to retrieve the message caption with the caption entities formatted as - HTML in the same way the original message was formatted. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as HTML. - - Returns: - :obj:`str`: Message caption with caption entities formatted as HTML. - """ - return self._parse_html(self.caption, self.parse_caption_entities(), urled=False) - - @property - def caption_html_urled(self) -> str: - """Creates an HTML-formatted string from the markup entities found in the message's - caption. - - Use this if you want to retrieve the message caption with the caption entities formatted as - HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as HTML. - - Returns: - :obj:`str`: Message caption with caption entities formatted as HTML. - """ - return self._parse_html(self.caption, self.parse_caption_entities(), urled=True) - - @staticmethod - def _parse_markdown( - message_text: Optional[str], - entities: Dict[MessageEntity, str], - urled: bool = False, - version: int = 1, - offset: int = 0, - ) -> Optional[str]: - version = int(version) - - if message_text is None: - return None - - if sys.maxunicode != 0xFFFF: - message_text = message_text.encode('utf-16-le') # type: ignore - - markdown_text = '' - last_offset = 0 - - sorted_entities = sorted(entities.items(), key=(lambda item: item[0].offset)) - parsed_entities = [] - - for (entity, text) in sorted_entities: - if entity not in parsed_entities: - nested_entities = { - e: t - for (e, t) in sorted_entities - if e.offset >= entity.offset - and e.offset + e.length <= entity.offset + entity.length - and e != entity - } - parsed_entities.extend(list(nested_entities.keys())) - - orig_text = text - text = escape_markdown(text, version=version) - - if nested_entities: - if version < 2: - raise ValueError( - 'Nested entities are not supported for Markdown ' 'version 1' - ) - - text = Message._parse_markdown( - orig_text, - nested_entities, - urled=urled, - offset=entity.offset, - version=version, - ) - - if entity.type == MessageEntity.TEXT_LINK: - if version == 1: - url = entity.url - else: - # Links need special escaping. Also can't have entities nested within - url = escape_markdown( - entity.url, version=version, entity_type=MessageEntity.TEXT_LINK - ) - insert = f'[{text}]({url})' - elif entity.type == MessageEntity.TEXT_MENTION and entity.user: - insert = f'[{text}](tg://user?id={entity.user.id})' - elif entity.type == MessageEntity.URL and urled: - if version == 1: - link = orig_text - else: - link = text - insert = f'[{link}]({orig_text})' - elif entity.type == MessageEntity.BOLD: - insert = '*' + text + '*' - elif entity.type == MessageEntity.ITALIC: - insert = '_' + text + '_' - elif entity.type == MessageEntity.CODE: - # Monospace needs special escaping. Also can't have entities nested within - insert = ( - '`' - + escape_markdown( - orig_text, version=version, entity_type=MessageEntity.CODE - ) - + '`' - ) - elif entity.type == MessageEntity.PRE: - # Monospace needs special escaping. Also can't have entities nested within - code = escape_markdown( - orig_text, version=version, entity_type=MessageEntity.PRE - ) - if entity.language: - prefix = '```' + entity.language + '\n' - else: - if code.startswith('\\'): - prefix = '```' - else: - prefix = '```\n' - insert = prefix + code + '```' - elif entity.type == MessageEntity.UNDERLINE: - if version == 1: - raise ValueError( - 'Underline entities are not supported for Markdown ' 'version 1' - ) - insert = '__' + text + '__' - elif entity.type == MessageEntity.STRIKETHROUGH: - if version == 1: - raise ValueError( - 'Strikethrough entities are not supported for Markdown ' 'version 1' - ) - insert = '~' + text + '~' - elif entity.type == MessageEntity.SPOILER: - if version == 1: - raise ValueError( - "Spoiler entities are not supported for Markdown version 1" - ) - insert = f"||{text}||" - else: - insert = text - - if offset == 0: - if sys.maxunicode == 0xFFFF: - markdown_text += ( - escape_markdown( - message_text[last_offset : entity.offset - offset], version=version - ) - + insert - ) - else: - markdown_text += ( - escape_markdown( - message_text[ # type: ignore - last_offset * 2 : (entity.offset - offset) * 2 - ].decode('utf-16-le'), - version=version, - ) - + insert - ) - else: - if sys.maxunicode == 0xFFFF: - markdown_text += ( - message_text[last_offset : entity.offset - offset] + insert - ) - else: - markdown_text += ( - message_text[ # type: ignore - last_offset * 2 : (entity.offset - offset) * 2 - ].decode('utf-16-le') - + insert - ) - - last_offset = entity.offset - offset + entity.length - - if offset == 0: - if sys.maxunicode == 0xFFFF: - markdown_text += escape_markdown(message_text[last_offset:], version=version) - else: - markdown_text += escape_markdown( - message_text[last_offset * 2 :].decode('utf-16-le'), # type: ignore - version=version, - ) - else: - if sys.maxunicode == 0xFFFF: - markdown_text += message_text[last_offset:] - else: - markdown_text += message_text[last_offset * 2 :].decode( # type: ignore - 'utf-16-le' - ) - - return markdown_text - - @property - def text_markdown(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message - using :class:`telegram.ParseMode.MARKDOWN`. - - Use this if you want to retrieve the message text with the entities formatted as Markdown - in the same way the original message was formatted. - - Note: - :attr:`telegram.ParseMode.MARKDOWN` is is a legacy mode, retained by Telegram for - backward compatibility. You should use :meth:`text_markdown_v2` instead. - - Returns: - :obj:`str`: Message text with entities formatted as Markdown. - - Raises: - :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested - entities. - - """ - return self._parse_markdown(self.text, self.parse_entities(), urled=False) - - @property - def text_markdown_v2(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message - using :class:`telegram.ParseMode.MARKDOWN_V2`. - - Use this if you want to retrieve the message text with the entities formatted as Markdown - in the same way the original message was formatted. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as Markdown V2. - - Returns: - :obj:`str`: Message text with entities formatted as Markdown. - """ - return self._parse_markdown(self.text, self.parse_entities(), urled=False, version=2) - - @property - def text_markdown_urled(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message - using :class:`telegram.ParseMode.MARKDOWN`. - - Use this if you want to retrieve the message text with the entities formatted as Markdown. - This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - - Note: - :attr:`telegram.ParseMode.MARKDOWN` is is a legacy mode, retained by Telegram for - backward compatibility. You should use :meth:`text_markdown_v2_urled` instead. - - Returns: - :obj:`str`: Message text with entities formatted as Markdown. - - Raises: - :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested - entities. - - """ - return self._parse_markdown(self.text, self.parse_entities(), urled=True) - - @property - def text_markdown_v2_urled(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message - using :class:`telegram.ParseMode.MARKDOWN_V2`. - - Use this if you want to retrieve the message text with the entities formatted as Markdown. - This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as Markdown V2. - - Returns: - :obj:`str`: Message text with entities formatted as Markdown. - """ - return self._parse_markdown(self.text, self.parse_entities(), urled=True, version=2) - - @property - def caption_markdown(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message's - caption using :class:`telegram.ParseMode.MARKDOWN`. - - Use this if you want to retrieve the message caption with the caption entities formatted as - Markdown in the same way the original message was formatted. - - Note: - :attr:`telegram.ParseMode.MARKDOWN` is is a legacy mode, retained by Telegram for - backward compatibility. You should use :meth:`caption_markdown_v2` instead. - - Returns: - :obj:`str`: Message caption with caption entities formatted as Markdown. - - Raises: - :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested - entities. - - """ - return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=False) - - @property - def caption_markdown_v2(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message's - caption using :class:`telegram.ParseMode.MARKDOWN_V2`. - - Use this if you want to retrieve the message caption with the caption entities formatted as - Markdown in the same way the original message was formatted. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as Markdown V2. - - Returns: - :obj:`str`: Message caption with caption entities formatted as Markdown. - """ - return self._parse_markdown( - self.caption, self.parse_caption_entities(), urled=False, version=2 - ) - - @property - def caption_markdown_urled(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message's - caption using :class:`telegram.ParseMode.MARKDOWN`. - - Use this if you want to retrieve the message caption with the caption entities formatted as - Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - - Note: - :attr:`telegram.ParseMode.MARKDOWN` is is a legacy mode, retained by Telegram for - backward compatibility. You should use :meth:`caption_markdown_v2_urled` instead. - - Returns: - :obj:`str`: Message caption with caption entities formatted as Markdown. - - Raises: - :exc:`ValueError`: If the message contains underline, strikethrough, spoiler or nested - entities. - - """ - return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=True) - - @property - def caption_markdown_v2_urled(self) -> str: - """Creates an Markdown-formatted string from the markup entities found in the message's - caption using :class:`telegram.ParseMode.MARKDOWN_V2`. - - Use this if you want to retrieve the message caption with the caption entities formatted as - Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - - .. versionchanged:: 13.10 - Spoiler entities are now formatted as Markdown V2. - - Returns: - :obj:`str`: Message caption with caption entities formatted as Markdown. - """ - return self._parse_markdown( - self.caption, self.parse_caption_entities(), urled=True, version=2 - ) diff --git a/telegramer/include/telegram/messageautodeletetimerchanged.py b/telegramer/include/telegram/messageautodeletetimerchanged.py deleted file mode 100644 index 21af1fb..0000000 --- a/telegramer/include/telegram/messageautodeletetimerchanged.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a change in the Telegram message auto -deletion. -""" - -from typing import Any - -from telegram import TelegramObject - - -class MessageAutoDeleteTimerChanged(TelegramObject): - """This object represents a service message about a change in auto-delete timer settings. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`message_auto_delete_time` is equal. - - .. versionadded:: 13.4 - - Args: - message_auto_delete_time (:obj:`int`): New auto-delete time for messages in the - chat. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - message_auto_delete_time (:obj:`int`): New auto-delete time for messages in the - chat. - - """ - - __slots__ = ('message_auto_delete_time', '_id_attrs') - - def __init__( - self, - message_auto_delete_time: int, - **_kwargs: Any, - ): - self.message_auto_delete_time = int(message_auto_delete_time) - - self._id_attrs = (self.message_auto_delete_time,) diff --git a/telegramer/include/telegram/messageentity.py b/telegramer/include/telegram/messageentity.py deleted file mode 100644 index d4e1620..0000000 --- a/telegramer/include/telegram/messageentity.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram MessageEntity.""" - -from typing import TYPE_CHECKING, Any, List, Optional, ClassVar - -from telegram import TelegramObject, User, constants -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class MessageEntity(TelegramObject): - """ - This object represents one special entity in a text message. For example, hashtags, - usernames, URLs, etc. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`type`, :attr:`offset` and :attr:`length` are equal. - - Args: - type (:obj:`str`): Type of the entity. Currently, can be mention (@username), hashtag, - bot_command, url, email, phone_number, bold (bold text), italic (italic text), - strikethrough, spoiler (spoiler message), code (monowidth string), pre - (monowidth block), text_link (for clickable text URLs), text_mention - (for users without usernames). - offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity. - length (:obj:`int`): Length of the entity in UTF-16 code units. - url (:obj:`str`, optional): For :attr:`TEXT_LINK` only, url that will be opened after - user taps on the text. - user (:class:`telegram.User`, optional): For :attr:`TEXT_MENTION` only, the mentioned - user. - language (:obj:`str`, optional): For :attr:`PRE` only, the programming language of - the entity text. - - Attributes: - type (:obj:`str`): Type of the entity. - offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity. - length (:obj:`int`): Length of the entity in UTF-16 code units. - url (:obj:`str`): Optional. Url that will be opened after user taps on the text. - user (:class:`telegram.User`): Optional. The mentioned user. - language (:obj:`str`): Optional. Programming language of the entity text. - - """ - - __slots__ = ('length', 'url', 'user', 'type', 'language', 'offset', '_id_attrs') - - def __init__( - self, - type: str, # pylint: disable=W0622 - offset: int, - length: int, - url: str = None, - user: User = None, - language: str = None, - **_kwargs: Any, - ): - # Required - self.type = type - self.offset = offset - self.length = length - # Optionals - self.url = url - self.user = user - self.language = language - - self._id_attrs = (self.type, self.offset, self.length) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MessageEntity']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['user'] = User.de_json(data.get('user'), bot) - - return cls(**data) - - MENTION: ClassVar[str] = constants.MESSAGEENTITY_MENTION - """:const:`telegram.constants.MESSAGEENTITY_MENTION`""" - HASHTAG: ClassVar[str] = constants.MESSAGEENTITY_HASHTAG - """:const:`telegram.constants.MESSAGEENTITY_HASHTAG`""" - CASHTAG: ClassVar[str] = constants.MESSAGEENTITY_CASHTAG - """:const:`telegram.constants.MESSAGEENTITY_CASHTAG`""" - PHONE_NUMBER: ClassVar[str] = constants.MESSAGEENTITY_PHONE_NUMBER - """:const:`telegram.constants.MESSAGEENTITY_PHONE_NUMBER`""" - BOT_COMMAND: ClassVar[str] = constants.MESSAGEENTITY_BOT_COMMAND - """:const:`telegram.constants.MESSAGEENTITY_BOT_COMMAND`""" - URL: ClassVar[str] = constants.MESSAGEENTITY_URL - """:const:`telegram.constants.MESSAGEENTITY_URL`""" - EMAIL: ClassVar[str] = constants.MESSAGEENTITY_EMAIL - """:const:`telegram.constants.MESSAGEENTITY_EMAIL`""" - BOLD: ClassVar[str] = constants.MESSAGEENTITY_BOLD - """:const:`telegram.constants.MESSAGEENTITY_BOLD`""" - ITALIC: ClassVar[str] = constants.MESSAGEENTITY_ITALIC - """:const:`telegram.constants.MESSAGEENTITY_ITALIC`""" - CODE: ClassVar[str] = constants.MESSAGEENTITY_CODE - """:const:`telegram.constants.MESSAGEENTITY_CODE`""" - PRE: ClassVar[str] = constants.MESSAGEENTITY_PRE - """:const:`telegram.constants.MESSAGEENTITY_PRE`""" - TEXT_LINK: ClassVar[str] = constants.MESSAGEENTITY_TEXT_LINK - """:const:`telegram.constants.MESSAGEENTITY_TEXT_LINK`""" - TEXT_MENTION: ClassVar[str] = constants.MESSAGEENTITY_TEXT_MENTION - """:const:`telegram.constants.MESSAGEENTITY_TEXT_MENTION`""" - UNDERLINE: ClassVar[str] = constants.MESSAGEENTITY_UNDERLINE - """:const:`telegram.constants.MESSAGEENTITY_UNDERLINE`""" - STRIKETHROUGH: ClassVar[str] = constants.MESSAGEENTITY_STRIKETHROUGH - """:const:`telegram.constants.MESSAGEENTITY_STRIKETHROUGH`""" - SPOILER: ClassVar[str] = constants.MESSAGEENTITY_SPOILER - """:const:`telegram.constants.MESSAGEENTITY_SPOILER` - - .. versionadded:: 13.10 - """ - ALL_TYPES: ClassVar[List[str]] = constants.MESSAGEENTITY_ALL_TYPES - """:const:`telegram.constants.MESSAGEENTITY_ALL_TYPES`\n - List of all the types""" diff --git a/telegramer/include/telegram/messageid.py b/telegramer/include/telegram/messageid.py deleted file mode 100644 index df7f47c..0000000 --- a/telegramer/include/telegram/messageid.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents an instance of a Telegram MessageId.""" -from typing import Any - -from telegram import TelegramObject - - -class MessageId(TelegramObject): - """This object represents a unique message identifier. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`message_id` is equal. - - Attributes: - message_id (:obj:`int`): Unique message identifier - """ - - __slots__ = ('message_id', '_id_attrs') - - def __init__(self, message_id: int, **_kwargs: Any): - self.message_id = int(message_id) - - self._id_attrs = (self.message_id,) diff --git a/telegramer/include/telegram/parsemode.py b/telegramer/include/telegram/parsemode.py deleted file mode 100644 index 38cf051..0000000 --- a/telegramer/include/telegram/parsemode.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Message Parse Modes.""" -from typing import ClassVar - -from telegram import constants -from telegram.utils.deprecate import set_new_attribute_deprecated - - -class ParseMode: - """This object represents a Telegram Message Parse Modes.""" - - __slots__ = ('__dict__',) - - MARKDOWN: ClassVar[str] = constants.PARSEMODE_MARKDOWN - """:const:`telegram.constants.PARSEMODE_MARKDOWN`\n - - Note: - :attr:`MARKDOWN` is a legacy mode, retained by Telegram for backward compatibility. - You should use :attr:`MARKDOWN_V2` instead. - """ - MARKDOWN_V2: ClassVar[str] = constants.PARSEMODE_MARKDOWN_V2 - """:const:`telegram.constants.PARSEMODE_MARKDOWN_V2`""" - HTML: ClassVar[str] = constants.PARSEMODE_HTML - """:const:`telegram.constants.PARSEMODE_HTML`""" - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) diff --git a/telegramer/include/telegram/passport/__init__.py b/telegramer/include/telegram/passport/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/passport/credentials.py b/telegramer/include/telegram/passport/credentials.py deleted file mode 100644 index d27bd31..0000000 --- a/telegramer/include/telegram/passport/credentials.py +++ /dev/null @@ -1,497 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0114, W0622 -try: - import ujson as json -except ImportError: - import json # type: ignore[no-redef] - -from base64 import b64decode -from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union, no_type_check - -try: - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric.padding import MGF1, OAEP - from cryptography.hazmat.primitives.ciphers import Cipher - from cryptography.hazmat.primitives.ciphers.algorithms import AES - from cryptography.hazmat.primitives.ciphers.modes import CBC - from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512, Hash - - CRYPTO_INSTALLED = True -except ImportError: - default_backend = None - MGF1, OAEP, Cipher, AES, CBC = (None, None, None, None, None) # type: ignore[misc] - SHA1, SHA256, SHA512, Hash = (None, None, None, None) # type: ignore[misc] - - CRYPTO_INSTALLED = False - -from telegram import TelegramError, TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class TelegramDecryptionError(TelegramError): - """Something went wrong with decryption.""" - - __slots__ = ('_msg',) - - def __init__(self, message: Union[str, Exception]): - super().__init__(f"TelegramDecryptionError: {message}") - self._msg = str(message) - - def __reduce__(self) -> Tuple[type, Tuple[str]]: - return self.__class__, (self._msg,) - - -@no_type_check -def decrypt(secret, hash, data): - """ - Decrypt per telegram docs at https://core.telegram.org/passport. - - Args: - secret (:obj:`str` or :obj:`bytes`): The encryption secret, either as bytes or as a - base64 encoded string. - hash (:obj:`str` or :obj:`bytes`): The hash, either as bytes or as a - base64 encoded string. - data (:obj:`str` or :obj:`bytes`): The data to decrypt, either as bytes or as a - base64 encoded string. - file (:obj:`bool`): Force data to be treated as raw data, instead of trying to - b64decode it. - - Raises: - :class:`TelegramDecryptionError`: Given hash does not match hash of decrypted data. - - Returns: - :obj:`bytes`: The decrypted data as bytes. - - """ - if not CRYPTO_INSTALLED: - raise RuntimeError( - 'To use Telegram Passports, PTB must be installed via `pip install ' - 'python-telegram-bot[passport]`.' - ) - # Make a SHA512 hash of secret + update - digest = Hash(SHA512(), backend=default_backend()) - digest.update(secret + hash) - secret_hash_hash = digest.finalize() - # First 32 chars is our key, next 16 is the initialisation vector - key, init_vector = secret_hash_hash[:32], secret_hash_hash[32 : 32 + 16] - # Init a AES-CBC cipher and decrypt the data - cipher = Cipher(AES(key), CBC(init_vector), backend=default_backend()) - decryptor = cipher.decryptor() - data = decryptor.update(data) + decryptor.finalize() - # Calculate SHA256 hash of the decrypted data - digest = Hash(SHA256(), backend=default_backend()) - digest.update(data) - data_hash = digest.finalize() - # If the newly calculated hash did not match the one telegram gave us - if data_hash != hash: - # Raise a error that is caught inside telegram.PassportData and transformed into a warning - raise TelegramDecryptionError(f"Hashes are not equal! {data_hash} != {hash}") - # Return data without padding - return data[data[0] :] - - -@no_type_check -def decrypt_json(secret, hash, data): - """Decrypts data using secret and hash and then decodes utf-8 string and loads json""" - return json.loads(decrypt(secret, hash, data).decode('utf-8')) - - -class EncryptedCredentials(TelegramObject): - """Contains data required for decrypting and authenticating EncryptedPassportElement. See the - Telegram Passport Documentation for a complete description of the data decryption and - authentication processes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`data`, :attr:`hash` and :attr:`secret` are equal. - - Note: - This object is decrypted only when originating from - :obj:`telegram.PassportData.decrypted_credentials`. - - Args: - data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's - nonce, data hashes and secrets used for EncryptedPassportElement decryption and - authentication or base64 encrypted data. - hash (:obj:`str`): Base64-encoded data hash for data authentication. - secret (:obj:`str`): Decrypted or encrypted secret used for decryption. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's - nonce, data hashes and secrets used for EncryptedPassportElement decryption and - authentication or base64 encrypted data. - hash (:obj:`str`): Base64-encoded data hash for data authentication. - secret (:obj:`str`): Decrypted or encrypted secret used for decryption. - - """ - - __slots__ = ( - 'hash', - 'secret', - 'bot', - 'data', - '_id_attrs', - '_decrypted_secret', - '_decrypted_data', - ) - - def __init__(self, data: str, hash: str, secret: str, bot: 'Bot' = None, **_kwargs: Any): - # Required - self.data = data - self.hash = hash - self.secret = secret - - self._id_attrs = (self.data, self.hash, self.secret) - - self.bot = bot - self._decrypted_secret = None - self._decrypted_data: Optional['Credentials'] = None - - @property - def decrypted_secret(self) -> str: - """ - :obj:`str`: Lazily decrypt and return secret. - - Raises: - telegram.TelegramDecryptionError: Decryption failed. Usually due to bad - private/public key but can also suggest malformed/tampered data. - """ - if self._decrypted_secret is None: - if not CRYPTO_INSTALLED: - raise RuntimeError( - 'To use Telegram Passports, PTB must be installed via `pip install ' - 'python-telegram-bot[passport]`.' - ) - # Try decrypting according to step 1 at - # https://core.telegram.org/passport#decrypting-data - # We make sure to base64 decode the secret first. - # Telegram says to use OAEP padding so we do that. The Mask Generation Function - # is the default for OAEP, the algorithm is the default for PHP which is what - # Telegram's backend servers run. - try: - self._decrypted_secret = self.bot.private_key.decrypt( - b64decode(self.secret), - OAEP(mgf=MGF1(algorithm=SHA1()), algorithm=SHA1(), label=None), # skipcq - ) - except ValueError as exception: - # If decryption fails raise exception - raise TelegramDecryptionError(exception) from exception - return self._decrypted_secret - - @property - def decrypted_data(self) -> 'Credentials': - """ - :class:`telegram.Credentials`: Lazily decrypt and return credentials data. This object - also contains the user specified nonce as - `decrypted_data.nonce`. - - Raises: - telegram.TelegramDecryptionError: Decryption failed. Usually due to bad - private/public key but can also suggest malformed/tampered data. - """ - if self._decrypted_data is None: - self._decrypted_data = Credentials.de_json( - decrypt_json(self.decrypted_secret, b64decode(self.hash), b64decode(self.data)), - self.bot, - ) - return self._decrypted_data - - -class Credentials(TelegramObject): - """ - Attributes: - secure_data (:class:`telegram.SecureData`): Credentials for encrypted data - nonce (:obj:`str`): Bot-specified nonce - """ - - __slots__ = ('bot', 'nonce', 'secure_data') - - def __init__(self, secure_data: 'SecureData', nonce: str, bot: 'Bot' = None, **_kwargs: Any): - # Required - self.secure_data = secure_data - self.nonce = nonce - - self.bot = bot - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Credentials']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['secure_data'] = SecureData.de_json(data.get('secure_data'), bot=bot) - - return cls(bot=bot, **data) - - -class SecureData(TelegramObject): - """ - This object represents the credentials that were used to decrypt the encrypted data. - All fields are optional and depend on fields that were requested. - - Attributes: - personal_details (:class:`telegram.SecureValue`, optional): Credentials for encrypted - personal details. - passport (:class:`telegram.SecureValue`, optional): Credentials for encrypted passport. - internal_passport (:class:`telegram.SecureValue`, optional): Credentials for encrypted - internal passport. - driver_license (:class:`telegram.SecureValue`, optional): Credentials for encrypted - driver license. - identity_card (:class:`telegram.SecureValue`, optional): Credentials for encrypted ID card - address (:class:`telegram.SecureValue`, optional): Credentials for encrypted - residential address. - utility_bill (:class:`telegram.SecureValue`, optional): Credentials for encrypted - utility bill. - bank_statement (:class:`telegram.SecureValue`, optional): Credentials for encrypted - bank statement. - rental_agreement (:class:`telegram.SecureValue`, optional): Credentials for encrypted - rental agreement. - passport_registration (:class:`telegram.SecureValue`, optional): Credentials for encrypted - registration from internal passport. - temporary_registration (:class:`telegram.SecureValue`, optional): Credentials for encrypted - temporary registration. - """ - - __slots__ = ( - 'bot', - 'utility_bill', - 'personal_details', - 'temporary_registration', - 'address', - 'driver_license', - 'rental_agreement', - 'internal_passport', - 'identity_card', - 'bank_statement', - 'passport', - 'passport_registration', - ) - - def __init__( - self, - personal_details: 'SecureValue' = None, - passport: 'SecureValue' = None, - internal_passport: 'SecureValue' = None, - driver_license: 'SecureValue' = None, - identity_card: 'SecureValue' = None, - address: 'SecureValue' = None, - utility_bill: 'SecureValue' = None, - bank_statement: 'SecureValue' = None, - rental_agreement: 'SecureValue' = None, - passport_registration: 'SecureValue' = None, - temporary_registration: 'SecureValue' = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Optionals - self.temporary_registration = temporary_registration - self.passport_registration = passport_registration - self.rental_agreement = rental_agreement - self.bank_statement = bank_statement - self.utility_bill = utility_bill - self.address = address - self.identity_card = identity_card - self.driver_license = driver_license - self.internal_passport = internal_passport - self.passport = passport - self.personal_details = personal_details - - self.bot = bot - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SecureData']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['temporary_registration'] = SecureValue.de_json( - data.get('temporary_registration'), bot=bot - ) - data['passport_registration'] = SecureValue.de_json( - data.get('passport_registration'), bot=bot - ) - data['rental_agreement'] = SecureValue.de_json(data.get('rental_agreement'), bot=bot) - data['bank_statement'] = SecureValue.de_json(data.get('bank_statement'), bot=bot) - data['utility_bill'] = SecureValue.de_json(data.get('utility_bill'), bot=bot) - data['address'] = SecureValue.de_json(data.get('address'), bot=bot) - data['identity_card'] = SecureValue.de_json(data.get('identity_card'), bot=bot) - data['driver_license'] = SecureValue.de_json(data.get('driver_license'), bot=bot) - data['internal_passport'] = SecureValue.de_json(data.get('internal_passport'), bot=bot) - data['passport'] = SecureValue.de_json(data.get('passport'), bot=bot) - data['personal_details'] = SecureValue.de_json(data.get('personal_details'), bot=bot) - - return cls(bot=bot, **data) - - -class SecureValue(TelegramObject): - """ - This object represents the credentials that were used to decrypt the encrypted value. - All fields are optional and depend on the type of field. - - Attributes: - data (:class:`telegram.DataCredentials`, optional): Credentials for encrypted Telegram - Passport data. Available for "personal_details", "passport", "driver_license", - "identity_card", "identity_passport" and "address" types. - front_side (:class:`telegram.FileCredentials`, optional): Credentials for encrypted - document's front side. Available for "passport", "driver_license", "identity_card" - and "internal_passport". - reverse_side (:class:`telegram.FileCredentials`, optional): Credentials for encrypted - document's reverse side. Available for "driver_license" and "identity_card". - selfie (:class:`telegram.FileCredentials`, optional): Credentials for encrypted selfie - of the user with a document. Can be available for "passport", "driver_license", - "identity_card" and "internal_passport". - translation (List[:class:`telegram.FileCredentials`], optional): Credentials for an - encrypted translation of the document. Available for "passport", "driver_license", - "identity_card", "internal_passport", "utility_bill", "bank_statement", - "rental_agreement", "passport_registration" and "temporary_registration". - files (List[:class:`telegram.FileCredentials`], optional): Credentials for encrypted - files. Available for "utility_bill", "bank_statement", "rental_agreement", - "passport_registration" and "temporary_registration" types. - - """ - - __slots__ = ('data', 'front_side', 'reverse_side', 'selfie', 'files', 'translation', 'bot') - - def __init__( - self, - data: 'DataCredentials' = None, - front_side: 'FileCredentials' = None, - reverse_side: 'FileCredentials' = None, - selfie: 'FileCredentials' = None, - files: List['FileCredentials'] = None, - translation: List['FileCredentials'] = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - self.data = data - self.front_side = front_side - self.reverse_side = reverse_side - self.selfie = selfie - self.files = files - self.translation = translation - - self.bot = bot - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SecureValue']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['data'] = DataCredentials.de_json(data.get('data'), bot=bot) - data['front_side'] = FileCredentials.de_json(data.get('front_side'), bot=bot) - data['reverse_side'] = FileCredentials.de_json(data.get('reverse_side'), bot=bot) - data['selfie'] = FileCredentials.de_json(data.get('selfie'), bot=bot) - data['files'] = FileCredentials.de_list(data.get('files'), bot=bot) - data['translation'] = FileCredentials.de_list(data.get('translation'), bot=bot) - - return cls(bot=bot, **data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['files'] = [p.to_dict() for p in self.files] - data['translation'] = [p.to_dict() for p in self.translation] - - return data - - -class _CredentialsBase(TelegramObject): - """Base class for DataCredentials and FileCredentials.""" - - __slots__ = ('hash', 'secret', 'file_hash', 'data_hash', 'bot') - - def __init__(self, hash: str, secret: str, bot: 'Bot' = None, **_kwargs: Any): - self.hash = hash - self.secret = secret - - # Aliases just be be sure - self.file_hash = self.hash - self.data_hash = self.hash - - self.bot = bot - - -class DataCredentials(_CredentialsBase): - """ - These credentials can be used to decrypt encrypted data from the data field in - EncryptedPassportData. - - Args: - data_hash (:obj:`str`): Checksum of encrypted data - secret (:obj:`str`): Secret of encrypted data - - Attributes: - hash (:obj:`str`): Checksum of encrypted data - secret (:obj:`str`): Secret of encrypted data - """ - - __slots__ = () - - def __init__(self, data_hash: str, secret: str, **_kwargs: Any): - super().__init__(data_hash, secret, **_kwargs) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - del data['file_hash'] - del data['hash'] - - return data - - -class FileCredentials(_CredentialsBase): - """ - These credentials can be used to decrypt encrypted files from the front_side, - reverse_side, selfie and files fields in EncryptedPassportData. - - Args: - file_hash (:obj:`str`): Checksum of encrypted file - secret (:obj:`str`): Secret of encrypted file - - Attributes: - hash (:obj:`str`): Checksum of encrypted file - secret (:obj:`str`): Secret of encrypted file - """ - - __slots__ = () - - def __init__(self, file_hash: str, secret: str, **_kwargs: Any): - super().__init__(file_hash, secret, **_kwargs) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - del data['data_hash'] - del data['hash'] - - return data diff --git a/telegramer/include/telegram/passport/data.py b/telegramer/include/telegram/passport/data.py deleted file mode 100644 index e1d38b5..0000000 --- a/telegramer/include/telegram/passport/data.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0114 -from typing import TYPE_CHECKING, Any - -from telegram import TelegramObject - -if TYPE_CHECKING: - from telegram import Bot - - -class PersonalDetails(TelegramObject): - """ - This object represents personal details. - - Attributes: - first_name (:obj:`str`): First Name. - middle_name (:obj:`str`): Optional. First Name. - last_name (:obj:`str`): Last Name. - birth_date (:obj:`str`): Date of birth in DD.MM.YYYY format. - gender (:obj:`str`): Gender, male or female. - country_code (:obj:`str`): Citizenship (ISO 3166-1 alpha-2 country code). - residence_country_code (:obj:`str`): Country of residence (ISO 3166-1 alpha-2 country - code). - first_name_native (:obj:`str`): First Name in the language of the user's country of - residence. - middle_name_native (:obj:`str`): Optional. Middle Name in the language of the user's - country of residence. - last_name_native (:obj:`str`): Last Name in the language of the user's country of - residence. - """ - - __slots__ = ( - 'middle_name', - 'first_name_native', - 'last_name_native', - 'residence_country_code', - 'first_name', - 'last_name', - 'country_code', - 'gender', - 'bot', - 'middle_name_native', - 'birth_date', - ) - - def __init__( - self, - first_name: str, - last_name: str, - birth_date: str, - gender: str, - country_code: str, - residence_country_code: str, - first_name_native: str = None, - last_name_native: str = None, - middle_name: str = None, - middle_name_native: str = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.first_name = first_name - self.last_name = last_name - self.middle_name = middle_name - self.birth_date = birth_date - self.gender = gender - self.country_code = country_code - self.residence_country_code = residence_country_code - self.first_name_native = first_name_native - self.last_name_native = last_name_native - self.middle_name_native = middle_name_native - - self.bot = bot - - -class ResidentialAddress(TelegramObject): - """ - This object represents a residential address. - - Attributes: - street_line1 (:obj:`str`): First line for the address. - street_line2 (:obj:`str`): Optional. Second line for the address. - city (:obj:`str`): City. - state (:obj:`str`): Optional. State. - country_code (:obj:`str`): ISO 3166-1 alpha-2 country code. - post_code (:obj:`str`): Address post code. - """ - - __slots__ = ( - 'post_code', - 'city', - 'country_code', - 'street_line2', - 'street_line1', - 'bot', - 'state', - ) - - def __init__( - self, - street_line1: str, - street_line2: str, - city: str, - state: str, - country_code: str, - post_code: str, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.street_line1 = street_line1 - self.street_line2 = street_line2 - self.city = city - self.state = state - self.country_code = country_code - self.post_code = post_code - - self.bot = bot - - -class IdDocumentData(TelegramObject): - """ - This object represents the data of an identity document. - - Attributes: - document_no (:obj:`str`): Document number. - expiry_date (:obj:`str`): Optional. Date of expiry, in DD.MM.YYYY format. - """ - - __slots__ = ('document_no', 'bot', 'expiry_date') - - def __init__(self, document_no: str, expiry_date: str, bot: 'Bot' = None, **_kwargs: Any): - self.document_no = document_no - self.expiry_date = expiry_date - - self.bot = bot diff --git a/telegramer/include/telegram/passport/encryptedpassportelement.py b/telegramer/include/telegram/passport/encryptedpassportelement.py deleted file mode 100644 index 78b6972..0000000 --- a/telegramer/include/telegram/passport/encryptedpassportelement.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python -# flake8: noqa: E501 -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram EncryptedPassportElement.""" -from base64 import b64decode -from typing import TYPE_CHECKING, Any, List, Optional - -from telegram import ( - IdDocumentData, - PassportFile, - PersonalDetails, - ResidentialAddress, - TelegramObject, -) -from telegram.passport.credentials import decrypt_json -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot, Credentials - - -class EncryptedPassportElement(TelegramObject): - """ - Contains information about documents or other Telegram Passport elements shared with the bot - by the user. The data has been automatically decrypted by python-telegram-bot. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`type`, :attr:`data`, :attr:`phone_number`, :attr:`email`, - :attr:`files`, :attr:`front_side`, :attr:`reverse_side` and :attr:`selfie` are equal. - - Note: - This object is decrypted only when originating from - :obj:`telegram.PassportData.decrypted_data`. - - Args: - type (:obj:`str`): Element type. One of "personal_details", "passport", "driver_license", - "identity_card", "internal_passport", "address", "utility_bill", "bank_statement", - "rental_agreement", "passport_registration", "temporary_registration", "phone_number", - "email". - data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocument` | \ - :class:`telegram.ResidentialAddress` | :obj:`str`, optional): - Decrypted or encrypted data, available for "personal_details", "passport", - "driver_license", "identity_card", "identity_passport" and "address" types. - phone_number (:obj:`str`, optional): User's verified phone number, available only for - "phone_number" type. - email (:obj:`str`, optional): User's verified email address, available only for "email" - type. - files (List[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted files - with documents provided by the user, available for "utility_bill", "bank_statement", - "rental_agreement", "passport_registration" and "temporary_registration" types. - front_side (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the - front side of the document, provided by the user. Available for "passport", - "driver_license", "identity_card" and "internal_passport". - reverse_side (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the - reverse side of the document, provided by the user. Available for "driver_license" and - "identity_card". - selfie (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the - selfie of the user holding a document, provided by the user; available for "passport", - "driver_license", "identity_card" and "internal_passport". - translation (List[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted - files with translated versions of documents provided by the user. Available if - requested for "passport", "driver_license", "identity_card", "internal_passport", - "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and - "temporary_registration" types. - hash (:obj:`str`): Base64-encoded element hash for using in - :class:`telegram.PassportElementErrorUnspecified`. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): Element type. One of "personal_details", "passport", "driver_license", - "identity_card", "internal_passport", "address", "utility_bill", "bank_statement", - "rental_agreement", "passport_registration", "temporary_registration", "phone_number", - "email". - data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocument` | \ - :class:`telegram.ResidentialAddress` | :obj:`str`): - Optional. Decrypted or encrypted data, available for "personal_details", "passport", - "driver_license", "identity_card", "identity_passport" and "address" types. - phone_number (:obj:`str`): Optional. User's verified phone number, available only for - "phone_number" type. - email (:obj:`str`): Optional. User's verified email address, available only for "email" - type. - files (List[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted files - with documents provided by the user, available for "utility_bill", "bank_statement", - "rental_agreement", "passport_registration" and "temporary_registration" types. - front_side (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the - front side of the document, provided by the user. Available for "passport", - "driver_license", "identity_card" and "internal_passport". - reverse_side (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the - reverse side of the document, provided by the user. Available for "driver_license" and - "identity_card". - selfie (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the - selfie of the user holding a document, provided by the user; available for "passport", - "driver_license", "identity_card" and "internal_passport". - translation (List[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted - files with translated versions of documents provided by the user. Available if - requested for "passport", "driver_license", "identity_card", "internal_passport", - "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and - "temporary_registration" types. - hash (:obj:`str`): Base64-encoded element hash for using in - :class:`telegram.PassportElementErrorUnspecified`. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'selfie', - 'files', - 'type', - 'translation', - 'email', - 'hash', - 'phone_number', - 'bot', - 'reverse_side', - 'front_side', - 'data', - '_id_attrs', - ) - - def __init__( - self, - type: str, # pylint: disable=W0622 - data: PersonalDetails = None, - phone_number: str = None, - email: str = None, - files: List[PassportFile] = None, - front_side: PassportFile = None, - reverse_side: PassportFile = None, - selfie: PassportFile = None, - translation: List[PassportFile] = None, - hash: str = None, # pylint: disable=W0622 - bot: 'Bot' = None, - credentials: 'Credentials' = None, # pylint: disable=W0613 - **_kwargs: Any, - ): - # Required - self.type = type - # Optionals - self.data = data - self.phone_number = phone_number - self.email = email - self.files = files - self.front_side = front_side - self.reverse_side = reverse_side - self.selfie = selfie - self.translation = translation - self.hash = hash - - self._id_attrs = ( - self.type, - self.data, - self.phone_number, - self.email, - self.files, - self.front_side, - self.reverse_side, - self.selfie, - ) - - self.bot = bot - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['EncryptedPassportElement']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['files'] = PassportFile.de_list(data.get('files'), bot) or None - data['front_side'] = PassportFile.de_json(data.get('front_side'), bot) - data['reverse_side'] = PassportFile.de_json(data.get('reverse_side'), bot) - data['selfie'] = PassportFile.de_json(data.get('selfie'), bot) - data['translation'] = PassportFile.de_list(data.get('translation'), bot) or None - - return cls(bot=bot, **data) - - @classmethod - def de_json_decrypted( - cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'Credentials' - ) -> Optional['EncryptedPassportElement']: - """Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account - passport credentials. - - Args: - data (Dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with this object. - credentials (:class:`telegram.FileCredentials`): The credentials - - Returns: - :class:`telegram.EncryptedPassportElement`: - - """ - if not data: - return None - - if data['type'] not in ('phone_number', 'email'): - secure_data = getattr(credentials.secure_data, data['type']) - - if secure_data.data is not None: - # If not already decrypted - if not isinstance(data['data'], dict): - data['data'] = decrypt_json( - b64decode(secure_data.data.secret), - b64decode(secure_data.data.hash), - b64decode(data['data']), - ) - if data['type'] == 'personal_details': - data['data'] = PersonalDetails.de_json(data['data'], bot=bot) - elif data['type'] in ( - 'passport', - 'internal_passport', - 'driver_license', - 'identity_card', - ): - data['data'] = IdDocumentData.de_json(data['data'], bot=bot) - elif data['type'] == 'address': - data['data'] = ResidentialAddress.de_json(data['data'], bot=bot) - - data['files'] = ( - PassportFile.de_list_decrypted(data.get('files'), bot, secure_data.files) or None - ) - data['front_side'] = PassportFile.de_json_decrypted( - data.get('front_side'), bot, secure_data.front_side - ) - data['reverse_side'] = PassportFile.de_json_decrypted( - data.get('reverse_side'), bot, secure_data.reverse_side - ) - data['selfie'] = PassportFile.de_json_decrypted( - data.get('selfie'), bot, secure_data.selfie - ) - data['translation'] = ( - PassportFile.de_list_decrypted( - data.get('translation'), bot, secure_data.translation - ) - or None - ) - - return cls(bot=bot, **data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - if self.files: - data['files'] = [p.to_dict() for p in self.files] - if self.translation: - data['translation'] = [p.to_dict() for p in self.translation] - - return data diff --git a/telegramer/include/telegram/passport/passportdata.py b/telegramer/include/telegram/passport/passportdata.py deleted file mode 100644 index 40f7e72..0000000 --- a/telegramer/include/telegram/passport/passportdata.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""Contains information about Telegram Passport data shared with the bot by the user.""" - -from typing import TYPE_CHECKING, Any, List, Optional - -from telegram import EncryptedCredentials, EncryptedPassportElement, TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot, Credentials - - -class PassportData(TelegramObject): - """Contains information about Telegram Passport data shared with the bot by the user. - - Note: - To be able to decrypt this object, you must pass your ``private_key`` to either - :class:`telegram.Updater` or :class:`telegram.Bot`. Decrypted data is then found in - :attr:`decrypted_data` and the payload can be found in :attr:`decrypted_credentials`'s - attribute :attr:`telegram.Credentials.payload`. - - Args: - data (List[:class:`telegram.EncryptedPassportElement`]): Array with encrypted information - about documents and other Telegram Passport elements that was shared with the bot. - credentials (:class:`telegram.EncryptedCredentials`)): Encrypted credentials. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - data (List[:class:`telegram.EncryptedPassportElement`]): Array with encrypted information - about documents and other Telegram Passport elements that was shared with the bot. - credentials (:class:`telegram.EncryptedCredentials`): Encrypted credentials. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - - """ - - __slots__ = ('bot', 'credentials', 'data', '_decrypted_data', '_id_attrs') - - def __init__( - self, - data: List[EncryptedPassportElement], - credentials: EncryptedCredentials, - bot: 'Bot' = None, - **_kwargs: Any, - ): - self.data = data - self.credentials = credentials - - self.bot = bot - self._decrypted_data: Optional[List[EncryptedPassportElement]] = None - self._id_attrs = tuple([x.type for x in data] + [credentials.hash]) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PassportData']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['data'] = EncryptedPassportElement.de_list(data.get('data'), bot) - data['credentials'] = EncryptedCredentials.de_json(data.get('credentials'), bot) - - return cls(bot=bot, **data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['data'] = [e.to_dict() for e in self.data] - - return data - - @property - def decrypted_data(self) -> List[EncryptedPassportElement]: - """ - List[:class:`telegram.EncryptedPassportElement`]: Lazily decrypt and return information - about documents and other Telegram Passport elements which were shared with the bot. - - Raises: - telegram.TelegramDecryptionError: Decryption failed. Usually due to bad - private/public key but can also suggest malformed/tampered data. - """ - if self._decrypted_data is None: - self._decrypted_data = [ - EncryptedPassportElement.de_json_decrypted( - element.to_dict(), self.bot, self.decrypted_credentials - ) - for element in self.data - ] - return self._decrypted_data - - @property - def decrypted_credentials(self) -> 'Credentials': - """ - :class:`telegram.Credentials`: Lazily decrypt and return credentials that were used - to decrypt the data. This object also contains the user specified payload as - `decrypted_data.payload`. - - Raises: - telegram.TelegramDecryptionError: Decryption failed. Usually due to bad - private/public key but can also suggest malformed/tampered data. - """ - return self.credentials.decrypted_data diff --git a/telegramer/include/telegram/passport/passportelementerrors.py b/telegramer/include/telegram/passport/passportelementerrors.py deleted file mode 100644 index fd38ff3..0000000 --- a/telegramer/include/telegram/passport/passportelementerrors.py +++ /dev/null @@ -1,382 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=W0622 -"""This module contains the classes that represent Telegram PassportElementError.""" - -from typing import Any - -from telegram import TelegramObject - - -class PassportElementError(TelegramObject): - """Baseclass for the PassportElementError* classes. - - This object represents an error in the Telegram Passport element which was submitted that - should be resolved by the user. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source` and :attr:`type` are equal. - - Args: - source (:obj:`str`): Error source. - type (:obj:`str`): The section of the user's Telegram Passport which has the error. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - source (:obj:`str`): Error source. - type (:obj:`str`): The section of the user's Telegram Passport which has the error. - message (:obj:`str`): Error message. - - """ - - # All subclasses of this class won't have _id_attrs in slots since it's added here. - __slots__ = ('message', 'source', 'type', '_id_attrs') - - def __init__(self, source: str, type: str, message: str, **_kwargs: Any): - # Required - self.source = str(source) - self.type = str(type) - self.message = str(message) - - self._id_attrs = (self.source, self.type) - - -class PassportElementErrorDataField(PassportElementError): - """ - Represents an issue in one of the data fields that was provided by the user. The error is - considered resolved when the field's value changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`field_name`, :attr:`data_hash` - and :attr:`message` are equal. - - Args: - type (:obj:`str`): The section of the user's Telegram Passport which has the error, one of - ``"personal_details"``, ``"passport"``, ``"driver_license"``, ``"identity_card"``, - ``"internal_passport"``, ``"address"``. - field_name (:obj:`str`): Name of the data field which has the error. - data_hash (:obj:`str`): Base64-encoded data hash. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): The section of the user's Telegram Passport which has the error, one of - ``"personal_details"``, ``"passport"``, ``"driver_license"``, ``"identity_card"``, - ``"internal_passport"``, ``"address"``. - field_name (:obj:`str`): Name of the data field which has the error. - data_hash (:obj:`str`): Base64-encoded data hash. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('data_hash', 'field_name') - - def __init__(self, type: str, field_name: str, data_hash: str, message: str, **_kwargs: Any): - # Required - super().__init__('data', type, message) - self.field_name = field_name - self.data_hash = data_hash - - self._id_attrs = (self.source, self.type, self.field_name, self.data_hash, self.message) - - -class PassportElementErrorFile(PassportElementError): - """ - Represents an issue with a document scan. The error is considered resolved when the file with - the document scan changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, and - :attr:`message` are equal. - - Args: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"utility_bill"``, ``"bank_statement"``, ``"rental_agreement"``, - ``"passport_registration"``, ``"temporary_registration"``. - file_hash (:obj:`str`): Base64-encoded file hash. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"utility_bill"``, ``"bank_statement"``, ``"rental_agreement"``, - ``"passport_registration"``, ``"temporary_registration"``. - file_hash (:obj:`str`): Base64-encoded file hash. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('file_hash',) - - def __init__(self, type: str, file_hash: str, message: str, **_kwargs: Any): - # Required - super().__init__('file', type, message) - self.file_hash = file_hash - - self._id_attrs = (self.source, self.type, self.file_hash, self.message) - - -class PassportElementErrorFiles(PassportElementError): - """ - Represents an issue with a list of scans. The error is considered resolved when the list of - files with the document scans changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hashes`, and - :attr:`message` are equal. - - Args: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"utility_bill"``, ``"bank_statement"``, ``"rental_agreement"``, - ``"passport_registration"``, ``"temporary_registration"``. - file_hashes (List[:obj:`str`]): List of base64-encoded file hashes. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"utility_bill"``, ``"bank_statement"``, ``"rental_agreement"``, - ``"passport_registration"``, ``"temporary_registration"``. - file_hashes (List[:obj:`str`]): List of base64-encoded file hashes. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('file_hashes',) - - def __init__(self, type: str, file_hashes: str, message: str, **_kwargs: Any): - # Required - super().__init__('files', type, message) - self.file_hashes = file_hashes - - self._id_attrs = (self.source, self.type, self.message) + tuple(file_hashes) - - -class PassportElementErrorFrontSide(PassportElementError): - """ - Represents an issue with the front side of a document. The error is considered resolved when - the file with the front side of the document changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, and - :attr:`message` are equal. - - Args: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"passport"``, ``"driver_license"``, ``"identity_card"``, ``"internal_passport"``. - file_hash (:obj:`str`): Base64-encoded hash of the file with the front side of the - document. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"passport"``, ``"driver_license"``, ``"identity_card"``, ``"internal_passport"``. - file_hash (:obj:`str`): Base64-encoded hash of the file with the front side of the - document. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('file_hash',) - - def __init__(self, type: str, file_hash: str, message: str, **_kwargs: Any): - # Required - super().__init__('front_side', type, message) - self.file_hash = file_hash - - self._id_attrs = (self.source, self.type, self.file_hash, self.message) - - -class PassportElementErrorReverseSide(PassportElementError): - """ - Represents an issue with the reverse side of a document. The error is considered resolved when - the file with the reverse side of the document changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, and - :attr:`message` are equal. - - Args: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"driver_license"``, ``"identity_card"``. - file_hash (:obj:`str`): Base64-encoded hash of the file with the reverse side of the - document. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"driver_license"``, ``"identity_card"``. - file_hash (:obj:`str`): Base64-encoded hash of the file with the reverse side of the - document. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('file_hash',) - - def __init__(self, type: str, file_hash: str, message: str, **_kwargs: Any): - # Required - super().__init__('reverse_side', type, message) - self.file_hash = file_hash - - self._id_attrs = (self.source, self.type, self.file_hash, self.message) - - -class PassportElementErrorSelfie(PassportElementError): - """ - Represents an issue with the selfie with a document. The error is considered resolved when - the file with the selfie changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, and - :attr:`message` are equal. - - Args: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"passport"``, ``"driver_license"``, ``"identity_card"``, ``"internal_passport"``. - file_hash (:obj:`str`): Base64-encoded hash of the file with the selfie. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of - ``"passport"``, ``"driver_license"``, ``"identity_card"``, ``"internal_passport"``. - file_hash (:obj:`str`): Base64-encoded hash of the file with the selfie. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('file_hash',) - - def __init__(self, type: str, file_hash: str, message: str, **_kwargs: Any): - # Required - super().__init__('selfie', type, message) - self.file_hash = file_hash - - self._id_attrs = (self.source, self.type, self.file_hash, self.message) - - -class PassportElementErrorTranslationFile(PassportElementError): - """ - Represents an issue with one of the files that constitute the translation of a document. - The error is considered resolved when the file changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hash`, and - :attr:`message` are equal. - - Args: - type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue, - one of ``"passport"``, ``"driver_license"``, ``"identity_card"``, - ``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``, - ``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``. - file_hash (:obj:`str`): Base64-encoded hash of the file. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue, - one of ``"passport"``, ``"driver_license"``, ``"identity_card"``, - ``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``, - ``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``. - file_hash (:obj:`str`): Base64-encoded hash of the file. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('file_hash',) - - def __init__(self, type: str, file_hash: str, message: str, **_kwargs: Any): - # Required - super().__init__('translation_file', type, message) - self.file_hash = file_hash - - self._id_attrs = (self.source, self.type, self.file_hash, self.message) - - -class PassportElementErrorTranslationFiles(PassportElementError): - """ - Represents an issue with the translated version of a document. The error is considered - resolved when a file with the document translation changes. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`file_hashes`, and - :attr:`message` are equal. - - Args: - type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue, - one of ``"passport"``, ``"driver_license"``, ``"identity_card"``, - ``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``, - ``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``. - file_hashes (List[:obj:`str`]): List of base64-encoded file hashes. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue, - one of ``"passport"``, ``"driver_license"``, ``"identity_card"``, - ``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``, - ``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``. - file_hashes (List[:obj:`str`]): List of base64-encoded file hashes. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('file_hashes',) - - def __init__(self, type: str, file_hashes: str, message: str, **_kwargs: Any): - # Required - super().__init__('translation_files', type, message) - self.file_hashes = file_hashes - - self._id_attrs = (self.source, self.type, self.message) + tuple(file_hashes) - - -class PassportElementErrorUnspecified(PassportElementError): - """ - Represents an issue in an unspecified place. The error is considered resolved when new - data is added. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`source`, :attr:`type`, :attr:`element_hash`, - and :attr:`message` are equal. - - Args: - type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue. - element_hash (:obj:`str`): Base64-encoded element hash. - message (:obj:`str`): Error message. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue. - element_hash (:obj:`str`): Base64-encoded element hash. - message (:obj:`str`): Error message. - - """ - - __slots__ = ('element_hash',) - - def __init__(self, type: str, element_hash: str, message: str, **_kwargs: Any): - # Required - super().__init__('unspecified', type, message) - self.element_hash = element_hash - - self._id_attrs = (self.source, self.type, self.element_hash, self.message) diff --git a/telegramer/include/telegram/passport/passportfile.py b/telegramer/include/telegram/passport/passportfile.py deleted file mode 100644 index 59f60c8..0000000 --- a/telegramer/include/telegram/passport/passportfile.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Encrypted PassportFile.""" - -from typing import TYPE_CHECKING, Any, List, Optional - -from telegram import TelegramObject -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot, File, FileCredentials - - -class PassportFile(TelegramObject): - """ - This object represents a file uploaded to Telegram Passport. Currently all Telegram Passport - files are in JPEG format when decrypted and don't exceed 10MB. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`file_unique_id` is equal. - - Args: - file_id (:obj:`str`): Identifier for this file, which can be used to download - or reuse the file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - file_size (:obj:`int`): File size. - file_date (:obj:`int`): Unix time when the file was uploaded. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - file_id (:obj:`str`): Identifier for this file. - file_unique_id (:obj:`str`): Unique identifier for this file, which - is supposed to be the same over time and for different bots. - Can't be used to download or reuse the file. - file_size (:obj:`int`): File size. - file_date (:obj:`int`): Unix time when the file was uploaded. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'file_date', - 'bot', - 'file_id', - 'file_size', - '_credentials', - 'file_unique_id', - '_id_attrs', - ) - - def __init__( - self, - file_id: str, - file_unique_id: str, - file_date: int, - file_size: int = None, - bot: 'Bot' = None, - credentials: 'FileCredentials' = None, - **_kwargs: Any, - ): - # Required - self.file_id = file_id - self.file_unique_id = file_unique_id - self.file_size = file_size - self.file_date = file_date - # Optionals - self.bot = bot - self._credentials = credentials - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json_decrypted( - cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'FileCredentials' - ) -> Optional['PassportFile']: - """Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account - passport credentials. - - Args: - data (Dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with this object. - credentials (:class:`telegram.FileCredentials`): The credentials - - Returns: - :class:`telegram.PassportFile`: - - """ - data = cls._parse_data(data) - - if not data: - return None - - data['credentials'] = credentials - - return cls(bot=bot, **data) - - @classmethod - def de_list_decrypted( - cls, data: Optional[List[JSONDict]], bot: 'Bot', credentials: List['FileCredentials'] - ) -> List[Optional['PassportFile']]: - """Variant of :meth:`telegram.TelegramObject.de_list` that also takes into account - passport credentials. - - Args: - data (Dict[:obj:`str`, ...]): The JSON data. - bot (:class:`telegram.Bot`): The bot associated with these objects. - credentials (:class:`telegram.FileCredentials`): The credentials - - Returns: - List[:class:`telegram.PassportFile`]: - - """ - if not data: - return [] - - return [ - cls.de_json_decrypted(passport_file, bot, credentials[i]) - for i, passport_file in enumerate(data) - ] - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """ - Wrapper over :attr:`telegram.Bot.get_file`. Will automatically assign the correct - credentials to the returned :class:`telegram.File` if originating from - :obj:`telegram.PassportData.decrypted_data`. - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - file = self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) - file.set_credentials(self._credentials) - return file diff --git a/telegramer/include/telegram/payment/__init__.py b/telegramer/include/telegram/payment/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/payment/invoice.py b/telegramer/include/telegram/payment/invoice.py deleted file mode 100644 index 18fda43..0000000 --- a/telegramer/include/telegram/payment/invoice.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Invoice.""" - -from typing import Any - -from telegram import TelegramObject - - -class Invoice(TelegramObject): - """This object contains basic information about an invoice. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`title`, :attr:`description`, :attr:`start_parameter`, - :attr:`currency` and :attr:`total_amount` are equal. - - Args: - title (:obj:`str`): Product name. - description (:obj:`str`): Product description. - start_parameter (:obj:`str`): Unique bot deep-linking parameter that can be used to - generate this invoice. - currency (:obj:`str`): Three-letter ISO 4217 currency code. - total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not - float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. See the - :obj:`exp` parameter in - `currencies.json `_, - it shows the number of digits past the decimal point for each currency - (2 for the majority of currencies). - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - title (:obj:`str`): Product name. - description (:obj:`str`): Product description. - start_parameter (:obj:`str`): Unique bot deep-linking parameter. - currency (:obj:`str`): Three-letter ISO 4217 currency code. - total_amount (:obj:`int`): Total price in the smallest units of the currency. - - """ - - __slots__ = ( - 'currency', - 'start_parameter', - 'title', - 'description', - 'total_amount', - '_id_attrs', - ) - - def __init__( - self, - title: str, - description: str, - start_parameter: str, - currency: str, - total_amount: int, - **_kwargs: Any, - ): - self.title = title - self.description = description - self.start_parameter = start_parameter - self.currency = currency - self.total_amount = total_amount - - self._id_attrs = ( - self.title, - self.description, - self.start_parameter, - self.currency, - self.total_amount, - ) diff --git a/telegramer/include/telegram/payment/labeledprice.py b/telegramer/include/telegram/payment/labeledprice.py deleted file mode 100644 index 37b58e3..0000000 --- a/telegramer/include/telegram/payment/labeledprice.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram LabeledPrice.""" - -from typing import Any - -from telegram import TelegramObject - - -class LabeledPrice(TelegramObject): - """This object represents a portion of the price for goods or services. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`label` and :attr:`amount` are equal. - - Args: - label (:obj:`str`): Portion label. - amount (:obj:`int`): Price of the product in the smallest units of the currency (integer, - not float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. - See the :obj:`exp` parameter in - `currencies.json `_, - it shows the number of digits past the decimal point for each currency - (2 for the majority of currencies). - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - label (:obj:`str`): Portion label. - amount (:obj:`int`): Price of the product in the smallest units of the currency. - - """ - - __slots__ = ('label', '_id_attrs', 'amount') - - def __init__(self, label: str, amount: int, **_kwargs: Any): - self.label = label - self.amount = amount - - self._id_attrs = (self.label, self.amount) diff --git a/telegramer/include/telegram/payment/orderinfo.py b/telegramer/include/telegram/payment/orderinfo.py deleted file mode 100644 index 902b324..0000000 --- a/telegramer/include/telegram/payment/orderinfo.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram OrderInfo.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import ShippingAddress, TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class OrderInfo(TelegramObject): - """This object represents information about an order. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`name`, :attr:`phone_number`, :attr:`email` and - :attr:`shipping_address` are equal. - - Args: - name (:obj:`str`, optional): User name. - phone_number (:obj:`str`, optional): User's phone number. - email (:obj:`str`, optional): User email. - shipping_address (:class:`telegram.ShippingAddress`, optional): User shipping address. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - name (:obj:`str`): Optional. User name. - phone_number (:obj:`str`): Optional. User's phone number. - email (:obj:`str`): Optional. User email. - shipping_address (:class:`telegram.ShippingAddress`): Optional. User shipping address. - - """ - - __slots__ = ('email', 'shipping_address', 'phone_number', 'name', '_id_attrs') - - def __init__( - self, - name: str = None, - phone_number: str = None, - email: str = None, - shipping_address: str = None, - **_kwargs: Any, - ): - self.name = name - self.phone_number = phone_number - self.email = email - self.shipping_address = shipping_address - - self._id_attrs = (self.name, self.phone_number, self.email, self.shipping_address) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['OrderInfo']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return cls() - - data['shipping_address'] = ShippingAddress.de_json(data.get('shipping_address'), bot) - - return cls(**data) diff --git a/telegramer/include/telegram/payment/precheckoutquery.py b/telegramer/include/telegram/payment/precheckoutquery.py deleted file mode 100644 index 65b5e30..0000000 --- a/telegramer/include/telegram/payment/precheckoutquery.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram PreCheckoutQuery.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import OrderInfo, TelegramObject, User -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot - - -class PreCheckoutQuery(TelegramObject): - """This object contains information about an incoming pre-checkout query. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Note: - In Python ``from`` is a reserved word, use ``from_user`` instead. - - Args: - id (:obj:`str`): Unique query identifier. - from_user (:class:`telegram.User`): User who sent the query. - currency (:obj:`str`): Three-letter ISO 4217 currency code. - total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not - float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. - See the :obj:`exp` parameter in - `currencies.json `_, - it shows the number of digits past the decimal point for each currency - (2 for the majority of currencies). - invoice_payload (:obj:`str`): Bot specified invoice payload. - shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the - user. - order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - id (:obj:`str`): Unique query identifier. - from_user (:class:`telegram.User`): User who sent the query. - currency (:obj:`str`): Three-letter ISO 4217 currency code. - total_amount (:obj:`int`): Total price in the smallest units of the currency. - invoice_payload (:obj:`str`): Bot specified invoice payload. - shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the - user. - order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'bot', - 'invoice_payload', - 'shipping_option_id', - 'currency', - 'order_info', - 'total_amount', - 'id', - 'from_user', - '_id_attrs', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - from_user: User, - currency: str, - total_amount: int, - invoice_payload: str, - shipping_option_id: str = None, - order_info: OrderInfo = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - self.id = id # pylint: disable=C0103 - self.from_user = from_user - self.currency = currency - self.total_amount = total_amount - self.invoice_payload = invoice_payload - self.shipping_option_id = shipping_option_id - self.order_info = order_info - - self.bot = bot - - self._id_attrs = (self.id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PreCheckoutQuery']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['from_user'] = User.de_json(data.pop('from'), bot) - data['order_info'] = OrderInfo.de_json(data.get('order_info'), bot) - - return cls(bot=bot, **data) - - def answer( # pylint: disable=C0103 - self, - ok: bool, - error_message: str = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.answer_pre_checkout_query(update.pre_checkout_query.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.answer_pre_checkout_query`. - - """ - return self.bot.answer_pre_checkout_query( - pre_checkout_query_id=self.id, - ok=ok, - error_message=error_message, - timeout=timeout, - api_kwargs=api_kwargs, - ) diff --git a/telegramer/include/telegram/payment/shippingaddress.py b/telegramer/include/telegram/payment/shippingaddress.py deleted file mode 100644 index ae16d9f..0000000 --- a/telegramer/include/telegram/payment/shippingaddress.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ShippingAddress.""" - -from typing import Any - -from telegram import TelegramObject - - -class ShippingAddress(TelegramObject): - """This object represents a Telegram ShippingAddress. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`country_code`, :attr:`state`, :attr:`city`, - :attr:`street_line1`, :attr:`street_line2` and :attr:`post_cod` are equal. - - Args: - country_code (:obj:`str`): ISO 3166-1 alpha-2 country code. - state (:obj:`str`): State, if applicable. - city (:obj:`str`): City. - street_line1 (:obj:`str`): First line for the address. - street_line2 (:obj:`str`): Second line for the address. - post_code (:obj:`str`): Address post code. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - country_code (:obj:`str`): ISO 3166-1 alpha-2 country code. - state (:obj:`str`): State, if applicable. - city (:obj:`str`): City. - street_line1 (:obj:`str`): First line for the address. - street_line2 (:obj:`str`): Second line for the address. - post_code (:obj:`str`): Address post code. - - """ - - __slots__ = ( - 'post_code', - 'city', - '_id_attrs', - 'country_code', - 'street_line2', - 'street_line1', - 'state', - ) - - def __init__( - self, - country_code: str, - state: str, - city: str, - street_line1: str, - street_line2: str, - post_code: str, - **_kwargs: Any, - ): - self.country_code = country_code - self.state = state - self.city = city - self.street_line1 = street_line1 - self.street_line2 = street_line2 - self.post_code = post_code - - self._id_attrs = ( - self.country_code, - self.state, - self.city, - self.street_line1, - self.street_line2, - self.post_code, - ) diff --git a/telegramer/include/telegram/payment/shippingoption.py b/telegramer/include/telegram/payment/shippingoption.py deleted file mode 100644 index 5bca48a..0000000 --- a/telegramer/include/telegram/payment/shippingoption.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ShippingOption.""" - -from typing import TYPE_CHECKING, Any, List - -from telegram import TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import LabeledPrice # noqa - - -class ShippingOption(TelegramObject): - """This object represents one shipping option. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Args: - id (:obj:`str`): Shipping option identifier. - title (:obj:`str`): Option title. - prices (List[:class:`telegram.LabeledPrice`]): List of price portions. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - id (:obj:`str`): Shipping option identifier. - title (:obj:`str`): Option title. - prices (List[:class:`telegram.LabeledPrice`]): List of price portions. - - """ - - __slots__ = ('prices', 'title', 'id', '_id_attrs') - - def __init__( - self, - id: str, # pylint: disable=W0622 - title: str, - prices: List['LabeledPrice'], - **_kwargs: Any, - ): - self.id = id # pylint: disable=C0103 - self.title = title - self.prices = prices - - self._id_attrs = (self.id,) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['prices'] = [p.to_dict() for p in self.prices] - - return data diff --git a/telegramer/include/telegram/payment/shippingquery.py b/telegramer/include/telegram/payment/shippingquery.py deleted file mode 100644 index 2a0d140..0000000 --- a/telegramer/include/telegram/payment/shippingquery.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ShippingQuery.""" - -from typing import TYPE_CHECKING, Any, Optional, List - -from telegram import ShippingAddress, TelegramObject, User, ShippingOption -from telegram.utils.helpers import DEFAULT_NONE -from telegram.utils.types import JSONDict, ODVInput - -if TYPE_CHECKING: - from telegram import Bot - - -class ShippingQuery(TelegramObject): - """This object contains information about an incoming shipping query. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Note: - In Python ``from`` is a reserved word, use ``from_user`` instead. - - Args: - id (:obj:`str`): Unique query identifier. - from_user (:class:`telegram.User`): User who sent the query. - invoice_payload (:obj:`str`): Bot specified invoice payload. - shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - id (:obj:`str`): Unique query identifier. - from_user (:class:`telegram.User`): User who sent the query. - invoice_payload (:obj:`str`): Bot specified invoice payload. - shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ('bot', 'invoice_payload', 'shipping_address', 'id', 'from_user', '_id_attrs') - - def __init__( - self, - id: str, # pylint: disable=W0622 - from_user: User, - invoice_payload: str, - shipping_address: ShippingAddress, - bot: 'Bot' = None, - **_kwargs: Any, - ): - self.id = id # pylint: disable=C0103 - self.from_user = from_user - self.invoice_payload = invoice_payload - self.shipping_address = shipping_address - - self.bot = bot - - self._id_attrs = (self.id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ShippingQuery']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['from_user'] = User.de_json(data.pop('from'), bot) - data['shipping_address'] = ShippingAddress.de_json(data.get('shipping_address'), bot) - - return cls(bot=bot, **data) - - def answer( # pylint: disable=C0103 - self, - ok: bool, - shipping_options: List[ShippingOption] = None, - error_message: str = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.answer_shipping_query(update.shipping_query.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.answer_shipping_query`. - - """ - return self.bot.answer_shipping_query( - shipping_query_id=self.id, - ok=ok, - shipping_options=shipping_options, - error_message=error_message, - timeout=timeout, - api_kwargs=api_kwargs, - ) diff --git a/telegramer/include/telegram/payment/successfulpayment.py b/telegramer/include/telegram/payment/successfulpayment.py deleted file mode 100644 index 7240fb8..0000000 --- a/telegramer/include/telegram/payment/successfulpayment.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram SuccessfulPayment.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import OrderInfo, TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class SuccessfulPayment(TelegramObject): - """This object contains basic information about a successful payment. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`telegram_payment_charge_id` and - :attr:`provider_payment_charge_id` are equal. - - Args: - currency (:obj:`str`): Three-letter ISO 4217 currency code. - total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not - float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. - See the :obj:`exp` parameter in - `currencies.json `_, - it shows the number of digits past the decimal point for each currency - (2 for the majority of currencies). - invoice_payload (:obj:`str`): Bot specified invoice payload. - shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the - user. - order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user. - telegram_payment_charge_id (:obj:`str`): Telegram payment identifier. - provider_payment_charge_id (:obj:`str`): Provider payment identifier. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - currency (:obj:`str`): Three-letter ISO 4217 currency code. - total_amount (:obj:`int`): Total price in the smallest units of the currency. - invoice_payload (:obj:`str`): Bot specified invoice payload. - shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the - user. - order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user. - telegram_payment_charge_id (:obj:`str`): Telegram payment identifier. - provider_payment_charge_id (:obj:`str`): Provider payment identifier. - - """ - - __slots__ = ( - 'invoice_payload', - 'shipping_option_id', - 'currency', - 'order_info', - 'telegram_payment_charge_id', - 'provider_payment_charge_id', - 'total_amount', - '_id_attrs', - ) - - def __init__( - self, - currency: str, - total_amount: int, - invoice_payload: str, - telegram_payment_charge_id: str, - provider_payment_charge_id: str, - shipping_option_id: str = None, - order_info: OrderInfo = None, - **_kwargs: Any, - ): - self.currency = currency - self.total_amount = total_amount - self.invoice_payload = invoice_payload - self.shipping_option_id = shipping_option_id - self.order_info = order_info - self.telegram_payment_charge_id = telegram_payment_charge_id - self.provider_payment_charge_id = provider_payment_charge_id - - self._id_attrs = (self.telegram_payment_charge_id, self.provider_payment_charge_id) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SuccessfulPayment']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['order_info'] = OrderInfo.de_json(data.get('order_info'), bot) - - return cls(**data) diff --git a/telegramer/include/telegram/poll.py b/telegramer/include/telegram/poll.py deleted file mode 100644 index a7c51b2..0000000 --- a/telegramer/include/telegram/poll.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Poll.""" - -import datetime -import sys -from typing import TYPE_CHECKING, Any, Dict, List, Optional, ClassVar - -from telegram import MessageEntity, TelegramObject, User, constants -from telegram.utils.helpers import from_timestamp, to_timestamp -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class PollOption(TelegramObject): - """ - This object contains information about one answer option in a poll. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`text` and :attr:`voter_count` are equal. - - Args: - text (:obj:`str`): Option text, 1-100 characters. - voter_count (:obj:`int`): Number of users that voted for this option. - - Attributes: - text (:obj:`str`): Option text, 1-100 characters. - voter_count (:obj:`int`): Number of users that voted for this option. - - """ - - __slots__ = ('voter_count', 'text', '_id_attrs') - - def __init__(self, text: str, voter_count: int, **_kwargs: Any): - self.text = text - self.voter_count = voter_count - - self._id_attrs = (self.text, self.voter_count) - - MAX_LENGTH: ClassVar[int] = constants.MAX_POLL_OPTION_LENGTH - """:const:`telegram.constants.MAX_POLL_OPTION_LENGTH`""" - - -class PollAnswer(TelegramObject): - """ - This object represents an answer of a user in a non-anonymous poll. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`poll_id`, :attr:`user` and :attr:`options_ids` are equal. - - Attributes: - poll_id (:obj:`str`): Unique poll identifier. - user (:class:`telegram.User`): The user, who changed the answer to the poll. - option_ids (List[:obj:`int`]): Identifiers of answer options, chosen by the user. - - Args: - poll_id (:obj:`str`): Unique poll identifier. - user (:class:`telegram.User`): The user, who changed the answer to the poll. - option_ids (List[:obj:`int`]): 0-based identifiers of answer options, chosen by the user. - May be empty if the user retracted their vote. - - """ - - __slots__ = ('option_ids', 'user', 'poll_id', '_id_attrs') - - def __init__(self, poll_id: str, user: User, option_ids: List[int], **_kwargs: Any): - self.poll_id = poll_id - self.user = user - self.option_ids = option_ids - - self._id_attrs = (self.poll_id, self.user, tuple(self.option_ids)) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PollAnswer']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['user'] = User.de_json(data.get('user'), bot) - - return cls(**data) - - -class Poll(TelegramObject): - """ - This object contains information about a poll. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Attributes: - id (:obj:`str`): Unique poll identifier. - question (:obj:`str`): Poll question, 1-300 characters. - options (List[:class:`PollOption`]): List of poll options. - total_voter_count (:obj:`int`): Total number of users that voted in the poll. - is_closed (:obj:`bool`): :obj:`True`, if the poll is closed. - is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous. - type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`. - allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers. - correct_option_id (:obj:`int`): Optional. Identifier of the correct answer option. - explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect - answer or taps on the lamp icon in a quiz-style poll. - explanation_entities (List[:class:`telegram.MessageEntity`]): Optional. Special entities - like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`. - open_period (:obj:`int`): Optional. Amount of time in seconds the poll will be active - after creation. - close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be - automatically closed. - - Args: - id (:obj:`str`): Unique poll identifier. - question (:obj:`str`): Poll question, 1-300 characters. - options (List[:class:`PollOption`]): List of poll options. - is_closed (:obj:`bool`): :obj:`True`, if the poll is closed. - is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous. - type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`. - allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers. - correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer option. - Available only for polls in the quiz mode, which are closed, or was sent (not - forwarded) by the bot or to the private chat with the bot. - explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect - answer or taps on the lamp icon in a quiz-style poll, 0-200 characters. - explanation_entities (List[:class:`telegram.MessageEntity`], optional): Special entities - like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`. - open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active - after creation. - close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the - poll will be automatically closed. Converted to :obj:`datetime.datetime`. - - """ - - __slots__ = ( - 'total_voter_count', - 'allows_multiple_answers', - 'open_period', - 'options', - 'type', - 'explanation_entities', - 'is_anonymous', - 'close_date', - 'is_closed', - 'id', - 'explanation', - 'question', - 'correct_option_id', - '_id_attrs', - ) - - def __init__( - self, - id: str, # pylint: disable=W0622 - question: str, - options: List[PollOption], - total_voter_count: int, - is_closed: bool, - is_anonymous: bool, - type: str, # pylint: disable=W0622 - allows_multiple_answers: bool, - correct_option_id: int = None, - explanation: str = None, - explanation_entities: List[MessageEntity] = None, - open_period: int = None, - close_date: datetime.datetime = None, - **_kwargs: Any, - ): - self.id = id # pylint: disable=C0103 - self.question = question - self.options = options - self.total_voter_count = total_voter_count - self.is_closed = is_closed - self.is_anonymous = is_anonymous - self.type = type - self.allows_multiple_answers = allows_multiple_answers - self.correct_option_id = correct_option_id - self.explanation = explanation - self.explanation_entities = explanation_entities - self.open_period = open_period - self.close_date = close_date - - self._id_attrs = (self.id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Poll']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['options'] = [PollOption.de_json(option, bot) for option in data['options']] - data['explanation_entities'] = MessageEntity.de_list(data.get('explanation_entities'), bot) - data['close_date'] = from_timestamp(data.get('close_date')) - - return cls(**data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['options'] = [x.to_dict() for x in self.options] - if self.explanation_entities: - data['explanation_entities'] = [e.to_dict() for e in self.explanation_entities] - data['close_date'] = to_timestamp(data.get('close_date')) - - return data - - def parse_explanation_entity(self, entity: MessageEntity) -> str: - """Returns the text from a given :class:`telegram.MessageEntity`. - - Note: - This method is present because Telegram calculates the offset and length in - UTF-16 codepoint pairs, which some versions of Python don't handle automatically. - (That is, you can't just slice ``Message.text`` with the offset and length.) - - Args: - entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must - be an entity that belongs to this message. - - Returns: - :obj:`str`: The text of the given entity. - - Raises: - RuntimeError: If the poll has no explanation. - - """ - if not self.explanation: - raise RuntimeError("This Poll has no 'explanation'.") - - # Is it a narrow build, if so we don't need to convert - if sys.maxunicode == 0xFFFF: - return self.explanation[entity.offset : entity.offset + entity.length] - entity_text = self.explanation.encode('utf-16-le') - entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2] - - return entity_text.decode('utf-16-le') - - def parse_explanation_entities(self, types: List[str] = None) -> Dict[MessageEntity, str]: - """ - Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`. - It contains entities from this polls explanation filtered by their ``type`` attribute as - the key, and the text that each entity belongs to as the value of the :obj:`dict`. - - Note: - This method should always be used instead of the :attr:`explanation_entities` - attribute, since it calculates the correct substring from the message text based on - UTF-16 codepoints. See :attr:`parse_explanation_entity` for more info. - - Args: - types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the - ``type`` attribute of an entity is contained in this list, it will be returned. - Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`. - - Returns: - Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to - the text that belongs to them, calculated based on UTF-16 codepoints. - - """ - if types is None: - types = MessageEntity.ALL_TYPES - - return { - entity: self.parse_explanation_entity(entity) - for entity in (self.explanation_entities or []) - if entity.type in types - } - - REGULAR: ClassVar[str] = constants.POLL_REGULAR - """:const:`telegram.constants.POLL_REGULAR`""" - QUIZ: ClassVar[str] = constants.POLL_QUIZ - """:const:`telegram.constants.POLL_QUIZ`""" - MAX_QUESTION_LENGTH: ClassVar[int] = constants.MAX_POLL_QUESTION_LENGTH - """:const:`telegram.constants.MAX_POLL_QUESTION_LENGTH`""" - MAX_OPTION_LENGTH: ClassVar[int] = constants.MAX_POLL_OPTION_LENGTH - """:const:`telegram.constants.MAX_POLL_OPTION_LENGTH`""" diff --git a/telegramer/include/telegram/proximityalerttriggered.py b/telegramer/include/telegram/proximityalerttriggered.py deleted file mode 100644 index df4cb55..0000000 --- a/telegramer/include/telegram/proximityalerttriggered.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Proximity Alert.""" -from typing import Any, Optional, TYPE_CHECKING - -from telegram import TelegramObject, User -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class ProximityAlertTriggered(TelegramObject): - """ - This object represents the content of a service message, sent whenever a user in the chat - triggers a proximity alert set by another user. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`traveler`, :attr:`watcher` and :attr:`distance` are equal. - - Args: - traveler (:class:`telegram.User`): User that triggered the alert - watcher (:class:`telegram.User`): User that set the alert - distance (:obj:`int`): The distance between the users - - Attributes: - traveler (:class:`telegram.User`): User that triggered the alert - watcher (:class:`telegram.User`): User that set the alert - distance (:obj:`int`): The distance between the users - - """ - - __slots__ = ('traveler', 'distance', 'watcher', '_id_attrs') - - def __init__(self, traveler: User, watcher: User, distance: int, **_kwargs: Any): - self.traveler = traveler - self.watcher = watcher - self.distance = distance - - self._id_attrs = (self.traveler, self.watcher, self.distance) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ProximityAlertTriggered']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['traveler'] = User.de_json(data.get('traveler'), bot) - data['watcher'] = User.de_json(data.get('watcher'), bot) - - return cls(bot=bot, **data) diff --git a/telegramer/include/telegram/py.typed b/telegramer/include/telegram/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/replykeyboardmarkup.py b/telegramer/include/telegram/replykeyboardmarkup.py deleted file mode 100644 index cc6f985..0000000 --- a/telegramer/include/telegram/replykeyboardmarkup.py +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ReplyKeyboardMarkup.""" - -from typing import Any, List, Union, Sequence - -from telegram import KeyboardButton, ReplyMarkup -from telegram.utils.types import JSONDict - - -class ReplyKeyboardMarkup(ReplyMarkup): - """This object represents a custom keyboard with reply options. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their the size of :attr:`keyboard` and all the buttons are equal. - - Example: - A user requests to change the bot's language, bot replies to the request with a keyboard - to select the new language. Other users in the group don't see the keyboard. - - Args: - keyboard (List[List[:obj:`str` | :class:`telegram.KeyboardButton`]]): Array of button rows, - each represented by an Array of :class:`telegram.KeyboardButton` objects. - resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard vertically - for optimal fit (e.g., make the keyboard smaller if there are just two rows of - buttons). Defaults to :obj:`False`, in which case the custom keyboard is always of the - same height as the app's standard keyboard. - one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as soon as - it's been used. The keyboard will still be available, but clients will automatically - display the usual letter-keyboard in the chat - the user can press a special button in - the input field to see the custom keyboard again. Defaults to :obj:`False`. - selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard to - specific users only. Targets: - - 1) Users that are @mentioned in the :attr:`~telegram.Message.text` of the - :class:`telegram.Message` object. - 2) If the bot's message is a reply (has ``reply_to_message_id``), sender of the - original message. - - Defaults to :obj:`False`. - - input_field_placeholder (:obj:`str`, optional): The placeholder to be shown in the input - field when the keyboard is active; 1-64 characters. - - .. versionadded:: 13.7 - - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - keyboard (List[List[:class:`telegram.KeyboardButton` | :obj:`str`]]): Array of button rows. - resize_keyboard (:obj:`bool`): Optional. Requests clients to resize the keyboard. - one_time_keyboard (:obj:`bool`): Optional. Requests clients to hide the keyboard as soon as - it's been used. - selective (:obj:`bool`): Optional. Show the keyboard to specific users only. - input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input - field when the reply is active. - - .. versionadded:: 13.7 - - """ - - __slots__ = ( - 'selective', - 'keyboard', - 'resize_keyboard', - 'one_time_keyboard', - 'input_field_placeholder', - '_id_attrs', - ) - - def __init__( - self, - keyboard: Sequence[Sequence[Union[str, KeyboardButton]]], - resize_keyboard: bool = False, - one_time_keyboard: bool = False, - selective: bool = False, - input_field_placeholder: str = None, - **_kwargs: Any, - ): - # Required - self.keyboard = [] - for row in keyboard: - button_row = [] - for button in row: - if isinstance(button, KeyboardButton): - button_row.append(button) # telegram.KeyboardButton - else: - button_row.append(KeyboardButton(button)) # str - self.keyboard.append(button_row) - - # Optionals - self.resize_keyboard = bool(resize_keyboard) - self.one_time_keyboard = bool(one_time_keyboard) - self.selective = bool(selective) - self.input_field_placeholder = input_field_placeholder - - self._id_attrs = (self.keyboard,) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['keyboard'] = [] - for row in self.keyboard: - data['keyboard'].append([button.to_dict() for button in row]) - return data - - @classmethod - def from_button( - cls, - button: Union[KeyboardButton, str], - resize_keyboard: bool = False, - one_time_keyboard: bool = False, - selective: bool = False, - input_field_placeholder: str = None, - **kwargs: object, - ) -> 'ReplyKeyboardMarkup': - """Shortcut for:: - - ReplyKeyboardMarkup([[button]], **kwargs) - - Return a ReplyKeyboardMarkup from a single KeyboardButton. - - Args: - button (:class:`telegram.KeyboardButton` | :obj:`str`): The button to use in - the markup. - resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard - vertically for optimal fit (e.g., make the keyboard smaller if there are just two - rows of buttons). Defaults to :obj:`False`, in which case the custom keyboard is - always of the same height as the app's standard keyboard. - one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as - soon as it's been used. The keyboard will still be available, but clients will - automatically display the usual letter-keyboard in the chat - the user can press - a special button in the input field to see the custom keyboard again. - Defaults to :obj:`False`. - selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard - to specific users only. Targets: - - 1) Users that are @mentioned in the text of the Message object. - 2) If the bot's message is a reply (has ``reply_to_message_id``), sender of the - original message. - - Defaults to :obj:`False`. - - input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input - field when the reply is active. - - .. versionadded:: 13.7 - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - """ - return cls( - [[button]], - resize_keyboard=resize_keyboard, - one_time_keyboard=one_time_keyboard, - selective=selective, - input_field_placeholder=input_field_placeholder, - **kwargs, - ) - - @classmethod - def from_row( - cls, - button_row: List[Union[str, KeyboardButton]], - resize_keyboard: bool = False, - one_time_keyboard: bool = False, - selective: bool = False, - input_field_placeholder: str = None, - **kwargs: object, - ) -> 'ReplyKeyboardMarkup': - """Shortcut for:: - - ReplyKeyboardMarkup([button_row], **kwargs) - - Return a ReplyKeyboardMarkup from a single row of KeyboardButtons. - - Args: - button_row (List[:class:`telegram.KeyboardButton` | :obj:`str`]): The button to use in - the markup. - resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard - vertically for optimal fit (e.g., make the keyboard smaller if there are just two - rows of buttons). Defaults to :obj:`False`, in which case the custom keyboard is - always of the same height as the app's standard keyboard. - one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as - soon as it's been used. The keyboard will still be available, but clients will - automatically display the usual letter-keyboard in the chat - the user can press - a special button in the input field to see the custom keyboard again. - Defaults to :obj:`False`. - selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard - to specific users only. Targets: - - 1) Users that are @mentioned in the text of the Message object. - 2) If the bot's message is a reply (has ``reply_to_message_id``), sender of the - original message. - - Defaults to :obj:`False`. - - input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input - field when the reply is active. - - .. versionadded:: 13.7 - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - """ - return cls( - [button_row], - resize_keyboard=resize_keyboard, - one_time_keyboard=one_time_keyboard, - selective=selective, - input_field_placeholder=input_field_placeholder, - **kwargs, - ) - - @classmethod - def from_column( - cls, - button_column: List[Union[str, KeyboardButton]], - resize_keyboard: bool = False, - one_time_keyboard: bool = False, - selective: bool = False, - input_field_placeholder: str = None, - **kwargs: object, - ) -> 'ReplyKeyboardMarkup': - """Shortcut for:: - - ReplyKeyboardMarkup([[button] for button in button_column], **kwargs) - - Return a ReplyKeyboardMarkup from a single column of KeyboardButtons. - - Args: - button_column (List[:class:`telegram.KeyboardButton` | :obj:`str`]): The button to use - in the markup. - resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard - vertically for optimal fit (e.g., make the keyboard smaller if there are just two - rows of buttons). Defaults to :obj:`False`, in which case the custom keyboard is - always of the same height as the app's standard keyboard. - one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as - soon as it's been used. The keyboard will still be available, but clients will - automatically display the usual letter-keyboard in the chat - the user can press - a special button in the input field to see the custom keyboard again. - Defaults to :obj:`False`. - selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard - to specific users only. Targets: - - 1) Users that are @mentioned in the text of the Message object. - 2) If the bot's message is a reply (has ``reply_to_message_id``), sender of the - original message. - - Defaults to :obj:`False`. - - input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input - field when the reply is active. - - .. versionadded:: 13.7 - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - """ - button_grid = [[button] for button in button_column] - return cls( - button_grid, - resize_keyboard=resize_keyboard, - one_time_keyboard=one_time_keyboard, - selective=selective, - input_field_placeholder=input_field_placeholder, - **kwargs, - ) - - def __hash__(self) -> int: - return hash( - ( - tuple(tuple(button for button in row) for row in self.keyboard), - self.resize_keyboard, - self.one_time_keyboard, - self.selective, - ) - ) diff --git a/telegramer/include/telegram/replykeyboardremove.py b/telegramer/include/telegram/replykeyboardremove.py deleted file mode 100644 index 2762141..0000000 --- a/telegramer/include/telegram/replykeyboardremove.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram ReplyKeyboardRemove.""" -from typing import Any - -from telegram import ReplyMarkup - - -class ReplyKeyboardRemove(ReplyMarkup): - """ - Upon receiving a message with this object, Telegram clients will remove the current custom - keyboard and display the default letter-keyboard. By default, custom keyboards are displayed - until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are - hidden immediately after the user presses a button (see :class:`telegram.ReplyKeyboardMarkup`). - - Example: - A user votes in a poll, bot returns confirmation message in reply to the vote and removes - the keyboard for that user, while still showing the keyboard with poll options to users who - haven't voted yet. - - Note: - User will not be able to summon this keyboard; if you want to hide the keyboard from - sight but keep it accessible, use :attr:`telegram.ReplyKeyboardMarkup.one_time_keyboard`. - - Args: - selective (:obj:`bool`, optional): Use this parameter if you want to remove the keyboard - for specific users only. Targets: - - 1) Users that are @mentioned in the text of the :class:`telegram.Message` object. - 2) If the bot's message is a reply (has `reply_to_message_id`), sender of the original - message. - - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - remove_keyboard (:obj:`True`): Requests clients to remove the custom keyboard. - selective (:obj:`bool`): Optional. Use this parameter if you want to remove the keyboard - for specific users only. - - """ - - __slots__ = ('selective', 'remove_keyboard') - - def __init__(self, selective: bool = False, **_kwargs: Any): - # Required - self.remove_keyboard = True - # Optionals - self.selective = bool(selective) diff --git a/telegramer/include/telegram/replymarkup.py b/telegramer/include/telegram/replymarkup.py deleted file mode 100644 index 081a73a..0000000 --- a/telegramer/include/telegram/replymarkup.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""Base class for Telegram ReplyMarkup Objects.""" - -from telegram import TelegramObject - - -class ReplyMarkup(TelegramObject): - """Base class for Telegram ReplyMarkup Objects. - - See :class:`telegram.InlineKeyboardMarkup`, :class:`telegram.ReplyKeyboardMarkup`, - :class:`telegram.ReplyKeyboardRemove` and :class:`telegram.ForceReply` for - detailed use. - - """ - - __slots__ = () diff --git a/telegramer/include/telegram/update.py b/telegramer/include/telegram/update.py deleted file mode 100644 index 7c7b210..0000000 --- a/telegramer/include/telegram/update.py +++ /dev/null @@ -1,416 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram Update.""" - -from typing import TYPE_CHECKING, Any, Optional - -from telegram import ( - CallbackQuery, - ChosenInlineResult, - InlineQuery, - Message, - Poll, - PreCheckoutQuery, - ShippingQuery, - TelegramObject, - ChatMemberUpdated, - constants, - ChatJoinRequest, -) -from telegram.poll import PollAnswer -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot, Chat, User # noqa - - -class Update(TelegramObject): - """This object represents an incoming update. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`update_id` is equal. - - Note: - At most one of the optional parameters can be present in any given update. - - Args: - update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a - certain positive number and increase sequentially. This ID becomes especially handy if - you're using Webhooks, since it allows you to ignore repeated updates or to restore the - correct update sequence, should they get out of order. If there are no new updates for - at least a week, then identifier of the next update will be chosen randomly instead of - sequentially. - message (:class:`telegram.Message`, optional): New incoming message of any kind - text, - photo, sticker, etc. - edited_message (:class:`telegram.Message`, optional): New version of a message that is - known to the bot and was edited. - channel_post (:class:`telegram.Message`, optional): New incoming channel post of any kind - - text, photo, sticker, etc. - edited_channel_post (:class:`telegram.Message`, optional): New version of a channel post - that is known to the bot and was edited. - inline_query (:class:`telegram.InlineQuery`, optional): New incoming inline query. - chosen_inline_result (:class:`telegram.ChosenInlineResult`, optional): The result of an - inline query that was chosen by a user and sent to their chat partner. - callback_query (:class:`telegram.CallbackQuery`, optional): New incoming callback query. - shipping_query (:class:`telegram.ShippingQuery`, optional): New incoming shipping query. - Only for invoices with flexible price. - pre_checkout_query (:class:`telegram.PreCheckoutQuery`, optional): New incoming - pre-checkout query. Contains full information about checkout. - poll (:class:`telegram.Poll`, optional): New poll state. Bots receive only updates about - stopped polls and polls, which are sent by the bot. - poll_answer (:class:`telegram.PollAnswer`, optional): A user changed their answer - in a non-anonymous poll. Bots receive new votes only in polls that were sent - by the bot itself. - my_chat_member (:class:`telegram.ChatMemberUpdated`, optional): The bot's chat member - status was updated in a chat. For private chats, this update is received only when the - bot is blocked or unblocked by the user. - - .. versionadded:: 13.4 - chat_member (:class:`telegram.ChatMemberUpdated`, optional): A chat member's status was - updated in a chat. The bot must be an administrator in the chat and must explicitly - specify ``'chat_member'`` in the list of ``'allowed_updates'`` to receive these - updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`, - :meth:`telegram.ext.Updater.start_polling` and - :meth:`telegram.ext.Updater.start_webhook`). - - .. versionadded:: 13.4 - chat_join_request (:class:`telegram.ChatJoinRequest`, optional): A request to join the - chat has been sent. The bot must have the - :attr:`telegram.ChatPermissions.can_invite_users` administrator right in the chat to - receive these updates. - - .. versionadded:: 13.8 - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - update_id (:obj:`int`): The update's unique identifier. - message (:class:`telegram.Message`): Optional. New incoming message. - edited_message (:class:`telegram.Message`): Optional. New version of a message. - channel_post (:class:`telegram.Message`): Optional. New incoming channel post. - edited_channel_post (:class:`telegram.Message`): Optional. New version of a channel post. - inline_query (:class:`telegram.InlineQuery`): Optional. New incoming inline query. - chosen_inline_result (:class:`telegram.ChosenInlineResult`): Optional. The result of an - inline query that was chosen by a user. - callback_query (:class:`telegram.CallbackQuery`): Optional. New incoming callback query. - shipping_query (:class:`telegram.ShippingQuery`): Optional. New incoming shipping query. - pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming - pre-checkout query. - poll (:class:`telegram.Poll`): Optional. New poll state. Bots receive only updates - about stopped polls and polls, which are sent by the bot. - poll_answer (:class:`telegram.PollAnswer`): Optional. A user changed their answer - in a non-anonymous poll. Bots receive new votes only in polls that were sent - by the bot itself. - my_chat_member (:class:`telegram.ChatMemberUpdated`): Optional. The bot's chat member - status was updated in a chat. For private chats, this update is received only when the - bot is blocked or unblocked by the user. - - .. versionadded:: 13.4 - chat_member (:class:`telegram.ChatMemberUpdated`): Optional. A chat member's status was - updated in a chat. The bot must be an administrator in the chat and must explicitly - specify ``'chat_member'`` in the list of ``'allowed_updates'`` to receive these - updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`, - :meth:`telegram.ext.Updater.start_polling` and - :meth:`telegram.ext.Updater.start_webhook`). - - .. versionadded:: 13.4 - chat_join_request (:class:`telegram.ChatJoinRequest`): Optional. A request to join the - chat has been sent. The bot must have the ``'can_invite_users'`` administrator - right in the chat to receive these updates. - - .. versionadded:: 13.8 - - """ - - __slots__ = ( - 'callback_query', - 'chosen_inline_result', - 'pre_checkout_query', - 'inline_query', - 'update_id', - 'message', - 'shipping_query', - 'poll', - 'poll_answer', - 'channel_post', - 'edited_channel_post', - 'edited_message', - '_effective_user', - '_effective_chat', - '_effective_message', - 'my_chat_member', - 'chat_member', - 'chat_join_request', - '_id_attrs', - ) - - MESSAGE = constants.UPDATE_MESSAGE - """:const:`telegram.constants.UPDATE_MESSAGE` - - .. versionadded:: 13.5""" - EDITED_MESSAGE = constants.UPDATE_EDITED_MESSAGE - """:const:`telegram.constants.UPDATE_EDITED_MESSAGE` - - .. versionadded:: 13.5""" - CHANNEL_POST = constants.UPDATE_CHANNEL_POST - """:const:`telegram.constants.UPDATE_CHANNEL_POST` - - .. versionadded:: 13.5""" - EDITED_CHANNEL_POST = constants.UPDATE_EDITED_CHANNEL_POST - """:const:`telegram.constants.UPDATE_EDITED_CHANNEL_POST` - - .. versionadded:: 13.5""" - INLINE_QUERY = constants.UPDATE_INLINE_QUERY - """:const:`telegram.constants.UPDATE_INLINE_QUERY` - - .. versionadded:: 13.5""" - CHOSEN_INLINE_RESULT = constants.UPDATE_CHOSEN_INLINE_RESULT - """:const:`telegram.constants.UPDATE_CHOSEN_INLINE_RESULT` - - .. versionadded:: 13.5""" - CALLBACK_QUERY = constants.UPDATE_CALLBACK_QUERY - """:const:`telegram.constants.UPDATE_CALLBACK_QUERY` - - .. versionadded:: 13.5""" - SHIPPING_QUERY = constants.UPDATE_SHIPPING_QUERY - """:const:`telegram.constants.UPDATE_SHIPPING_QUERY` - - .. versionadded:: 13.5""" - PRE_CHECKOUT_QUERY = constants.UPDATE_PRE_CHECKOUT_QUERY - """:const:`telegram.constants.UPDATE_PRE_CHECKOUT_QUERY` - - .. versionadded:: 13.5""" - POLL = constants.UPDATE_POLL - """:const:`telegram.constants.UPDATE_POLL` - - .. versionadded:: 13.5""" - POLL_ANSWER = constants.UPDATE_POLL_ANSWER - """:const:`telegram.constants.UPDATE_POLL_ANSWER` - - .. versionadded:: 13.5""" - MY_CHAT_MEMBER = constants.UPDATE_MY_CHAT_MEMBER - """:const:`telegram.constants.UPDATE_MY_CHAT_MEMBER` - - .. versionadded:: 13.5""" - CHAT_MEMBER = constants.UPDATE_CHAT_MEMBER - """:const:`telegram.constants.UPDATE_CHAT_MEMBER` - - .. versionadded:: 13.5""" - CHAT_JOIN_REQUEST = constants.UPDATE_CHAT_JOIN_REQUEST - """:const:`telegram.constants.UPDATE_CHAT_JOIN_REQUEST` - - .. versionadded:: 13.8""" - ALL_TYPES = constants.UPDATE_ALL_TYPES - """:const:`telegram.constants.UPDATE_ALL_TYPES` - - .. versionadded:: 13.5""" - - def __init__( - self, - update_id: int, - message: Message = None, - edited_message: Message = None, - channel_post: Message = None, - edited_channel_post: Message = None, - inline_query: InlineQuery = None, - chosen_inline_result: ChosenInlineResult = None, - callback_query: CallbackQuery = None, - shipping_query: ShippingQuery = None, - pre_checkout_query: PreCheckoutQuery = None, - poll: Poll = None, - poll_answer: PollAnswer = None, - my_chat_member: ChatMemberUpdated = None, - chat_member: ChatMemberUpdated = None, - chat_join_request: ChatJoinRequest = None, - **_kwargs: Any, - ): - # Required - self.update_id = int(update_id) - # Optionals - self.message = message - self.edited_message = edited_message - self.inline_query = inline_query - self.chosen_inline_result = chosen_inline_result - self.callback_query = callback_query - self.shipping_query = shipping_query - self.pre_checkout_query = pre_checkout_query - self.channel_post = channel_post - self.edited_channel_post = edited_channel_post - self.poll = poll - self.poll_answer = poll_answer - self.my_chat_member = my_chat_member - self.chat_member = chat_member - self.chat_join_request = chat_join_request - - self._effective_user: Optional['User'] = None - self._effective_chat: Optional['Chat'] = None - self._effective_message: Optional[Message] = None - - self._id_attrs = (self.update_id,) - - @property - def effective_user(self) -> Optional['User']: - """ - :class:`telegram.User`: The user that sent this update, no matter what kind of update this - is. Will be :obj:`None` for :attr:`channel_post` and :attr:`poll`. - - """ - if self._effective_user: - return self._effective_user - - user = None - - if self.message: - user = self.message.from_user - - elif self.edited_message: - user = self.edited_message.from_user - - elif self.inline_query: - user = self.inline_query.from_user - - elif self.chosen_inline_result: - user = self.chosen_inline_result.from_user - - elif self.callback_query: - user = self.callback_query.from_user - - elif self.shipping_query: - user = self.shipping_query.from_user - - elif self.pre_checkout_query: - user = self.pre_checkout_query.from_user - - elif self.poll_answer: - user = self.poll_answer.user - - elif self.my_chat_member: - user = self.my_chat_member.from_user - - elif self.chat_member: - user = self.chat_member.from_user - - elif self.chat_join_request: - user = self.chat_join_request.from_user - - self._effective_user = user - return user - - @property - def effective_chat(self) -> Optional['Chat']: - """ - :class:`telegram.Chat`: The chat that this update was sent in, no matter what kind of - update this is. Will be :obj:`None` for :attr:`inline_query`, - :attr:`chosen_inline_result`, :attr:`callback_query` from inline messages, - :attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll` and - :attr:`poll_answer`. - - """ - if self._effective_chat: - return self._effective_chat - - chat = None - - if self.message: - chat = self.message.chat - - elif self.edited_message: - chat = self.edited_message.chat - - elif self.callback_query and self.callback_query.message: - chat = self.callback_query.message.chat - - elif self.channel_post: - chat = self.channel_post.chat - - elif self.edited_channel_post: - chat = self.edited_channel_post.chat - - elif self.my_chat_member: - chat = self.my_chat_member.chat - - elif self.chat_member: - chat = self.chat_member.chat - - elif self.chat_join_request: - chat = self.chat_join_request.chat - - self._effective_chat = chat - return chat - - @property - def effective_message(self) -> Optional[Message]: - """ - :class:`telegram.Message`: The message included in this update, no matter what kind of - update this is. Will be :obj:`None` for :attr:`inline_query`, - :attr:`chosen_inline_result`, :attr:`callback_query` from inline messages, - :attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll`, - :attr:`poll_answer`, :attr:`my_chat_member`, :attr:`chat_member` as well as - :attr:`chat_join_request` in case the bot is missing the - :attr:`telegram.ChatPermissions.can_invite_users` administrator right in the chat. - - """ - if self._effective_message: - return self._effective_message - - message = None - - if self.message: - message = self.message - - elif self.edited_message: - message = self.edited_message - - elif self.callback_query: - message = self.callback_query.message - - elif self.channel_post: - message = self.channel_post - - elif self.edited_channel_post: - message = self.edited_channel_post - - self._effective_message = message - return message - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Update']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['message'] = Message.de_json(data.get('message'), bot) - data['edited_message'] = Message.de_json(data.get('edited_message'), bot) - data['inline_query'] = InlineQuery.de_json(data.get('inline_query'), bot) - data['chosen_inline_result'] = ChosenInlineResult.de_json( - data.get('chosen_inline_result'), bot - ) - data['callback_query'] = CallbackQuery.de_json(data.get('callback_query'), bot) - data['shipping_query'] = ShippingQuery.de_json(data.get('shipping_query'), bot) - data['pre_checkout_query'] = PreCheckoutQuery.de_json(data.get('pre_checkout_query'), bot) - data['channel_post'] = Message.de_json(data.get('channel_post'), bot) - data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot) - data['poll'] = Poll.de_json(data.get('poll'), bot) - data['poll_answer'] = PollAnswer.de_json(data.get('poll_answer'), bot) - data['my_chat_member'] = ChatMemberUpdated.de_json(data.get('my_chat_member'), bot) - data['chat_member'] = ChatMemberUpdated.de_json(data.get('chat_member'), bot) - data['chat_join_request'] = ChatJoinRequest.de_json(data.get('chat_join_request'), bot) - - return cls(**data) diff --git a/telegramer/include/telegram/user.py b/telegramer/include/telegram/user.py deleted file mode 100644 index 0a50895..0000000 --- a/telegramer/include/telegram/user.py +++ /dev/null @@ -1,1243 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=W0622 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram User.""" -from datetime import datetime -from typing import TYPE_CHECKING, Any, List, Optional, Union, Tuple - -from telegram import TelegramObject, constants -from telegram.inline.inlinekeyboardbutton import InlineKeyboardButton -from telegram.utils.helpers import ( - mention_html as util_mention_html, - DEFAULT_NONE, - DEFAULT_20, -) -from telegram.utils.helpers import mention_markdown as util_mention_markdown -from telegram.utils.types import JSONDict, FileInput, ODVInput, DVInput - -if TYPE_CHECKING: - from telegram import ( - Bot, - Message, - UserProfilePhotos, - MessageId, - InputMediaAudio, - InputMediaDocument, - InputMediaPhoto, - InputMediaVideo, - MessageEntity, - ReplyMarkup, - PhotoSize, - Audio, - Contact, - Document, - InlineKeyboardMarkup, - LabeledPrice, - Location, - Animation, - Sticker, - Video, - Venue, - VideoNote, - Voice, - ) - - -class User(TelegramObject): - """This object represents a Telegram user or bot. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - Args: - id (:obj:`int`): Unique identifier for this user or bot. - is_bot (:obj:`bool`): :obj:`True`, if this user is a bot. - first_name (:obj:`str`): User's or bots first name. - last_name (:obj:`str`, optional): User's or bots last name. - username (:obj:`str`, optional): User's or bots username. - language_code (:obj:`str`, optional): IETF language tag of the user's language. - can_join_groups (:obj:`str`, optional): :obj:`True`, if the bot can be invited to groups. - Returned only in :attr:`telegram.Bot.get_me` requests. - can_read_all_group_messages (:obj:`str`, optional): :obj:`True`, if privacy mode is - disabled for the bot. Returned only in :attr:`telegram.Bot.get_me` requests. - supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline - queries. Returned only in :attr:`telegram.Bot.get_me` requests. - bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. - - Attributes: - id (:obj:`int`): Unique identifier for this user or bot. - is_bot (:obj:`bool`): :obj:`True`, if this user is a bot. - first_name (:obj:`str`): User's or bot's first name. - last_name (:obj:`str`): Optional. User's or bot's last name. - username (:obj:`str`): Optional. User's or bot's username. - language_code (:obj:`str`): Optional. IETF language tag of the user's language. - can_join_groups (:obj:`str`): Optional. :obj:`True`, if the bot can be invited to groups. - Returned only in :attr:`telegram.Bot.get_me` requests. - can_read_all_group_messages (:obj:`str`): Optional. :obj:`True`, if privacy mode is - disabled for the bot. Returned only in :attr:`telegram.Bot.get_me` requests. - supports_inline_queries (:obj:`str`): Optional. :obj:`True`, if the bot supports inline - queries. Returned only in :attr:`telegram.Bot.get_me` requests. - bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. - - """ - - __slots__ = ( - 'is_bot', - 'can_read_all_group_messages', - 'username', - 'first_name', - 'last_name', - 'can_join_groups', - 'supports_inline_queries', - 'id', - 'bot', - 'language_code', - '_id_attrs', - ) - - def __init__( - self, - id: int, - first_name: str, - is_bot: bool, - last_name: str = None, - username: str = None, - language_code: str = None, - can_join_groups: bool = None, - can_read_all_group_messages: bool = None, - supports_inline_queries: bool = None, - bot: 'Bot' = None, - **_kwargs: Any, - ): - # Required - self.id = int(id) # pylint: disable=C0103 - self.first_name = first_name - self.is_bot = is_bot - # Optionals - self.last_name = last_name - self.username = username - self.language_code = language_code - self.can_join_groups = can_join_groups - self.can_read_all_group_messages = can_read_all_group_messages - self.supports_inline_queries = supports_inline_queries - self.bot = bot - - self._id_attrs = (self.id,) - - @property - def name(self) -> str: - """:obj:`str`: Convenience property. If available, returns the user's :attr:`username` - prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`. - """ - if self.username: - return f'@{self.username}' - return self.full_name - - @property - def full_name(self) -> str: - """:obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if - available) :attr:`last_name`. - """ - if self.last_name: - return f'{self.first_name} {self.last_name}' - return self.first_name - - @property - def link(self) -> Optional[str]: - """:obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link - of the user. - """ - if self.username: - return f"https://t.me/{self.username}" - return None - - def get_profile_photos( - self, - offset: int = None, - limit: int = 100, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> Optional['UserProfilePhotos']: - """ - Shortcut for:: - - bot.get_user_profile_photos(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.get_user_profile_photos`. - - """ - return self.bot.get_user_profile_photos( - user_id=self.id, - offset=offset, - limit=limit, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def mention_markdown(self, name: str = None) -> str: - """ - Note: - :attr:`telegram.ParseMode.MARKDOWN` is a legacy mode, retained by Telegram for - backward compatibility. You should use :meth:`mention_markdown_v2` instead. - - Args: - name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`. - - Returns: - :obj:`str`: The inline mention for the user as markdown (version 1). - - """ - if name: - return util_mention_markdown(self.id, name) - return util_mention_markdown(self.id, self.full_name) - - def mention_markdown_v2(self, name: str = None) -> str: - """ - Args: - name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`. - - Returns: - :obj:`str`: The inline mention for the user as markdown (version 2). - - """ - if name: - return util_mention_markdown(self.id, name, version=2) - return util_mention_markdown(self.id, self.full_name, version=2) - - def mention_html(self, name: str = None) -> str: - """ - Args: - name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`. - - Returns: - :obj:`str`: The inline mention for the user as HTML. - - """ - if name: - return util_mention_html(self.id, name) - return util_mention_html(self.id, self.full_name) - - def mention_button(self, name: str = None) -> InlineKeyboardButton: - """ - Shortcut for:: - - InlineKeyboardButton(text=name, url=f"tg://user?id={update.effective_user.id}") - - .. versionadded:: 13.9 - - Args: - name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`. - - Returns: - :class:`telegram.InlineKeyboardButton`: InlineButton with url set to the user mention - """ - return InlineKeyboardButton(text=name or self.full_name, url=f"tg://user?id={self.id}") - - def pin_message( - self, - message_id: int, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.pin_chat_message(chat_id=update.effective_user.id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.pin_chat_message`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.pin_chat_message( - chat_id=self.id, - message_id=message_id, - disable_notification=disable_notification, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def unpin_message( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - message_id: int = None, - ) -> bool: - """Shortcut for:: - - bot.unpin_chat_message(chat_id=update.effective_user.id, - *args, - **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.unpin_chat_message`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unpin_chat_message( - chat_id=self.id, - timeout=timeout, - api_kwargs=api_kwargs, - message_id=message_id, - ) - - def unpin_all_messages( - self, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.unpin_all_chat_messages(chat_id=update.effective_user.id, - *args, - **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.unpin_all_chat_messages`. - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.unpin_all_chat_messages( - chat_id=self.id, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - def send_message( - self, - text: str, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_message(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_message`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_message( - chat_id=self.id, - text=text, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - entities=entities, - protect_content=protect_content, - ) - - def send_photo( - self, - photo: Union[FileInput, 'PhotoSize'], - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_photo(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_photo`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_photo( - chat_id=self.id, - photo=photo, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_media_group( - self, - media: List[ - Union['InputMediaAudio', 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo'] - ], - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> List['Message']: - """Shortcut for:: - - bot.send_media_group(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_media_group`. - - Returns: - List[:class:`telegram.Message`:] On success, instance representing the message posted. - - """ - return self.bot.send_media_group( - chat_id=self.id, - media=media, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_audio( - self, - audio: Union[FileInput, 'Audio'], - duration: int = None, - performer: str = None, - title: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_audio(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_audio`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_audio( - chat_id=self.id, - audio=audio, - duration=duration, - performer=performer, - title=title, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_chat_action( - self, - action: str, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.send_chat_action(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_chat_action`. - - Returns: - :obj:`True`: On success. - - """ - return self.bot.send_chat_action( - chat_id=self.id, - action=action, - timeout=timeout, - api_kwargs=api_kwargs, - ) - - send_action = send_chat_action - """Alias for :attr:`send_chat_action`""" - - def send_contact( - self, - phone_number: str = None, - first_name: str = None, - last_name: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - contact: 'Contact' = None, - vcard: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_contact(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_contact`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_contact( - chat_id=self.id, - phone_number=phone_number, - first_name=first_name, - last_name=last_name, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - contact=contact, - vcard=vcard, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_dice( - self, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - emoji: str = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_dice(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_dice`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_dice( - chat_id=self.id, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - emoji=emoji, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_document( - self, - document: Union[FileInput, 'Document'], - filename: str = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - disable_content_type_detection: bool = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_document(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_document`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_document( - chat_id=self.id, - document=document, - filename=filename, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - thumb=thumb, - api_kwargs=api_kwargs, - disable_content_type_detection=disable_content_type_detection, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - protect_content=protect_content, - ) - - def send_game( - self, - game_short_name: str, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'InlineKeyboardMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_game(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_game`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_game( - chat_id=self.id, - game_short_name=game_short_name, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_invoice( - self, - title: str, - description: str, - payload: str, - provider_token: str, - currency: str, - prices: List['LabeledPrice'], - start_parameter: str = None, - photo_url: str = None, - photo_size: int = None, - photo_width: int = None, - photo_height: int = None, - need_name: bool = None, - need_phone_number: bool = None, - need_email: bool = None, - need_shipping_address: bool = None, - is_flexible: bool = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'InlineKeyboardMarkup' = None, - provider_data: Union[str, object] = None, - send_phone_number_to_provider: bool = None, - send_email_to_provider: bool = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - max_tip_amount: int = None, - suggested_tip_amounts: List[int] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_invoice(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_invoice`. - - Warning: - As of API 5.2 :attr:`start_parameter` is an optional argument and therefore the order - of the arguments had to be changed. Use keyword arguments to make sure that the - arguments are passed correctly. - - .. versionchanged:: 13.5 - As of Bot API 5.2, the parameter :attr:`start_parameter` is optional. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_invoice( - chat_id=self.id, - title=title, - description=description, - payload=payload, - provider_token=provider_token, - currency=currency, - prices=prices, - start_parameter=start_parameter, - photo_url=photo_url, - photo_size=photo_size, - photo_width=photo_width, - photo_height=photo_height, - need_name=need_name, - need_phone_number=need_phone_number, - need_email=need_email, - need_shipping_address=need_shipping_address, - is_flexible=is_flexible, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - provider_data=provider_data, - send_phone_number_to_provider=send_phone_number_to_provider, - send_email_to_provider=send_email_to_provider, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - max_tip_amount=max_tip_amount, - suggested_tip_amounts=suggested_tip_amounts, - protect_content=protect_content, - ) - - def send_location( - self, - latitude: float = None, - longitude: float = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - location: 'Location' = None, - live_period: int = None, - api_kwargs: JSONDict = None, - horizontal_accuracy: float = None, - heading: int = None, - proximity_alert_radius: int = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_location(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_location`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_location( - chat_id=self.id, - latitude=latitude, - longitude=longitude, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - location=location, - live_period=live_period, - api_kwargs=api_kwargs, - horizontal_accuracy=horizontal_accuracy, - heading=heading, - proximity_alert_radius=proximity_alert_radius, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_animation( - self, - animation: Union[FileInput, 'Animation'], - duration: int = None, - width: int = None, - height: int = None, - thumb: FileInput = None, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_animation(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_animation`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_animation( - chat_id=self.id, - animation=animation, - duration=duration, - width=width, - height=height, - thumb=thumb, - caption=caption, - parse_mode=parse_mode, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_sticker( - self, - sticker: Union[FileInput, 'Sticker'], - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_sticker(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_sticker`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_sticker( - chat_id=self.id, - sticker=sticker, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_video( - self, - video: Union[FileInput, 'Video'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - width: int = None, - height: int = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - supports_streaming: bool = None, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_video(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_video`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_video( - chat_id=self.id, - video=video, - duration=duration, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - width=width, - height=height, - parse_mode=parse_mode, - supports_streaming=supports_streaming, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_venue( - self, - latitude: float = None, - longitude: float = None, - title: str = None, - address: str = None, - foursquare_id: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - venue: 'Venue' = None, - foursquare_type: str = None, - api_kwargs: JSONDict = None, - google_place_id: str = None, - google_place_type: str = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_venue(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_venue`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_venue( - chat_id=self.id, - latitude=latitude, - longitude=longitude, - title=title, - address=address, - foursquare_id=foursquare_id, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - venue=venue, - foursquare_type=foursquare_type, - api_kwargs=api_kwargs, - google_place_id=google_place_id, - google_place_type=google_place_type, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content, - ) - - def send_video_note( - self, - video_note: Union[FileInput, 'VideoNote'], - duration: int = None, - length: int = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - thumb: FileInput = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_video_note(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_video_note`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_video_note( - chat_id=self.id, - video_note=video_note, - duration=duration, - length=length, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - thumb=thumb, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - filename=filename, - protect_content=protect_content, - ) - - def send_voice( - self, - voice: Union[FileInput, 'Voice'], - duration: int = None, - caption: str = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: DVInput[float] = DEFAULT_20, - parse_mode: ODVInput[str] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - filename: str = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_voice(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_voice`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_voice( - chat_id=self.id, - voice=voice, - duration=duration, - caption=caption, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - parse_mode=parse_mode, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - caption_entities=caption_entities, - filename=filename, - protect_content=protect_content, - ) - - def send_poll( - self, - question: str, - options: List[str], - is_anonymous: bool = True, - # We use constant.POLL_REGULAR instead of Poll.REGULAR here to avoid circular imports - type: str = constants.POLL_REGULAR, # pylint: disable=W0622 - allows_multiple_answers: bool = False, - correct_option_id: int = None, - is_closed: bool = None, - disable_notification: ODVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - explanation: str = None, - explanation_parse_mode: ODVInput[str] = DEFAULT_NONE, - open_period: int = None, - close_date: Union[int, datetime] = None, - api_kwargs: JSONDict = None, - allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, - explanation_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None, - protect_content: bool = None, - ) -> 'Message': - """Shortcut for:: - - bot.send_poll(update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.send_poll`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.send_poll( - chat_id=self.id, - question=question, - options=options, - is_anonymous=is_anonymous, - type=type, # pylint=pylint, - allows_multiple_answers=allows_multiple_answers, - correct_option_id=correct_option_id, - is_closed=is_closed, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - reply_markup=reply_markup, - timeout=timeout, - explanation=explanation, - explanation_parse_mode=explanation_parse_mode, - open_period=open_period, - close_date=close_date, - api_kwargs=api_kwargs, - allow_sending_without_reply=allow_sending_without_reply, - explanation_entities=explanation_entities, - protect_content=protect_content, - ) - - def send_copy( - self, - from_chat_id: Union[str, int], - message_id: int, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> 'MessageId': - """Shortcut for:: - - bot.copy_message(chat_id=update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.copy_message`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.copy_message( - chat_id=self.id, - from_chat_id=from_chat_id, - message_id=message_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def copy_message( - self, - chat_id: Union[int, str], - message_id: int, - caption: str = None, - parse_mode: ODVInput[str] = DEFAULT_NONE, - caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None, - disable_notification: DVInput[bool] = DEFAULT_NONE, - reply_to_message_id: int = None, - allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE, - reply_markup: 'ReplyMarkup' = None, - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - protect_content: bool = None, - ) -> 'MessageId': - """Shortcut for:: - - bot.copy_message(from_chat_id=update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see :meth:`telegram.Bot.copy_message`. - - Returns: - :class:`telegram.Message`: On success, instance representing the message posted. - - """ - return self.bot.copy_message( - from_chat_id=self.id, - chat_id=chat_id, - message_id=message_id, - caption=caption, - parse_mode=parse_mode, - caption_entities=caption_entities, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - allow_sending_without_reply=allow_sending_without_reply, - reply_markup=reply_markup, - timeout=timeout, - api_kwargs=api_kwargs, - protect_content=protect_content, - ) - - def approve_join_request( - self, - chat_id: Union[int, str], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.approve_chat_join_request(user_id=update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.approve_chat_join_request`. - - .. versionadded:: 13.8 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.approve_chat_join_request( - user_id=self.id, chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs - ) - - def decline_join_request( - self, - chat_id: Union[int, str], - timeout: ODVInput[float] = DEFAULT_NONE, - api_kwargs: JSONDict = None, - ) -> bool: - """Shortcut for:: - - bot.decline_chat_join_request(user_id=update.effective_user.id, *args, **kwargs) - - For the documentation of the arguments, please see - :meth:`telegram.Bot.decline_chat_join_request`. - - .. versionadded:: 13.8 - - Returns: - :obj:`bool`: On success, :obj:`True` is returned. - - """ - return self.bot.decline_chat_join_request( - user_id=self.id, chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs - ) diff --git a/telegramer/include/telegram/userprofilephotos.py b/telegramer/include/telegram/userprofilephotos.py deleted file mode 100644 index aa3e6ec..0000000 --- a/telegramer/include/telegram/userprofilephotos.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram UserProfilePhotos.""" - -from typing import TYPE_CHECKING, Any, List, Optional - -from telegram import PhotoSize, TelegramObject -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class UserProfilePhotos(TelegramObject): - """This object represent a user's profile pictures. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`total_count` and :attr:`photos` are equal. - - Args: - total_count (:obj:`int`): Total number of profile pictures the target user has. - photos (List[List[:class:`telegram.PhotoSize`]]): Requested profile pictures (in up to 4 - sizes each). - - Attributes: - total_count (:obj:`int`): Total number of profile pictures. - photos (List[List[:class:`telegram.PhotoSize`]]): Requested profile pictures. - - """ - - __slots__ = ('photos', 'total_count', '_id_attrs') - - def __init__(self, total_count: int, photos: List[List[PhotoSize]], **_kwargs: Any): - # Required - self.total_count = int(total_count) - self.photos = photos - - self._id_attrs = (self.total_count, self.photos) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['UserProfilePhotos']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['photos'] = [PhotoSize.de_list(photo, bot) for photo in data['photos']] - - return cls(**data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data['photos'] = [] - for photo in self.photos: - data['photos'].append([x.to_dict() for x in photo]) - - return data - - def __hash__(self) -> int: - return hash(tuple(tuple(p for p in photo) for photo in self.photos)) diff --git a/telegramer/include/telegram/utils/__init__.py b/telegramer/include/telegram/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/utils/deprecate.py b/telegramer/include/telegram/utils/deprecate.py deleted file mode 100644 index bc9f519..0000000 --- a/telegramer/include/telegram/utils/deprecate.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module facilitates the deprecation of functions.""" - -import warnings - - -# We use our own DeprecationWarning since they are muted by default and "UserWarning" makes it -# seem like it's the user that issued the warning -# We name it something else so that you don't get confused when you attempt to suppress it -class TelegramDeprecationWarning(Warning): - """Custom warning class for deprecations in this library.""" - - __slots__ = () - - -# Function to warn users that setting custom attributes is deprecated (Use only in __setattr__!) -# Checks if a custom attribute is added by checking length of dictionary before & after -# assigning attribute. This is the fastest way to do it (I hope!). -def set_new_attribute_deprecated(self: object, key: str, value: object) -> None: - """Warns the user if they set custom attributes on PTB objects.""" - org = len(self.__dict__) - object.__setattr__(self, key, value) - new = len(self.__dict__) - if new > org: - warnings.warn( - f"Setting custom attributes such as {key!r} on objects such as " - f"{self.__class__.__name__!r} of the PTB library is deprecated.", - TelegramDeprecationWarning, - stacklevel=3, - ) diff --git a/telegramer/include/telegram/utils/helpers.py b/telegramer/include/telegram/utils/helpers.py deleted file mode 100644 index 194a461..0000000 --- a/telegramer/include/telegram/utils/helpers.py +++ /dev/null @@ -1,596 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains helper functions.""" - -import datetime as dtm # dtm = "DateTime Module" -import re -import signal -import time - -from collections import defaultdict -from html import escape -from pathlib import Path - -from typing import ( - TYPE_CHECKING, - Any, - DefaultDict, - Dict, - Optional, - Tuple, - Union, - Type, - cast, - IO, - TypeVar, - Generic, - overload, -) - -from telegram.utils.types import JSONDict, FileInput - -if TYPE_CHECKING: - from telegram import Message, Update, TelegramObject, InputFile - -# in PTB-Raw we don't have pytz, so we make a little workaround here -DTM_UTC = dtm.timezone.utc -try: - import pytz - - UTC = pytz.utc -except ImportError: - UTC = DTM_UTC # type: ignore[assignment] - -try: - import ujson as json -except ImportError: - import json # type: ignore[no-redef] - - -# From https://stackoverflow.com/questions/2549939/get-signal-names-from-numbers-in-python -_signames = { - v: k - for k, v in reversed(sorted(vars(signal).items())) - if k.startswith('SIG') and not k.startswith('SIG_') -} - - -def get_signal_name(signum: int) -> str: - """Returns the signal name of the given signal number.""" - return _signames[signum] - - -def is_local_file(obj: Optional[Union[str, Path]]) -> bool: - """ - Checks if a given string is a file on local system. - - Args: - obj (:obj:`str`): The string to check. - """ - if obj is None: - return False - - path = Path(obj) - try: - return path.is_file() - except Exception: - return False - - -def parse_file_input( - file_input: Union[FileInput, 'TelegramObject'], - tg_type: Type['TelegramObject'] = None, - attach: bool = None, - filename: str = None, -) -> Union[str, 'InputFile', Any]: - """ - Parses input for sending files: - - * For string input, if the input is an absolute path of a local file, - adds the ``file://`` prefix. If the input is a relative path of a local file, computes the - absolute path and adds the ``file://`` prefix. Returns the input unchanged, otherwise. - * :class:`pathlib.Path` objects are treated the same way as strings. - * For IO and bytes input, returns an :class:`telegram.InputFile`. - * If :attr:`tg_type` is specified and the input is of that type, returns the ``file_id`` - attribute. - - Args: - file_input (:obj:`str` | :obj:`bytes` | `filelike object` | Telegram media object): The - input to parse. - tg_type (:obj:`type`, optional): The Telegram media type the input can be. E.g. - :class:`telegram.Animation`. - attach (:obj:`bool`, optional): Whether this file should be send as one file or is part of - a collection of files. Only relevant in case an :class:`telegram.InputFile` is - returned. - filename (:obj:`str`, optional): The filename. Only relevant in case an - :class:`telegram.InputFile` is returned. - - Returns: - :obj:`str` | :class:`telegram.InputFile` | :obj:`object`: The parsed input or the untouched - :attr:`file_input`, in case it's no valid file input. - """ - # Importing on file-level yields cyclic Import Errors - from telegram import InputFile # pylint: disable=C0415 - - if isinstance(file_input, str) and file_input.startswith('file://'): - return file_input - if isinstance(file_input, (str, Path)): - if is_local_file(file_input): - out = Path(file_input).absolute().as_uri() - else: - out = file_input # type: ignore[assignment] - return out - if isinstance(file_input, bytes): - return InputFile(file_input, attach=attach, filename=filename) - if InputFile.is_file(file_input): - file_input = cast(IO, file_input) - return InputFile(file_input, attach=attach, filename=filename) - if tg_type and isinstance(file_input, tg_type): - return file_input.file_id # type: ignore[attr-defined] - return file_input - - -def escape_markdown(text: str, version: int = 1, entity_type: str = None) -> str: - """ - Helper function to escape telegram markup symbols. - - Args: - text (:obj:`str`): The text. - version (:obj:`int` | :obj:`str`): Use to specify the version of telegrams Markdown. - Either ``1`` or ``2``. Defaults to ``1``. - entity_type (:obj:`str`, optional): For the entity types ``PRE``, ``CODE`` and the link - part of ``TEXT_LINKS``, only certain characters need to be escaped in ``MarkdownV2``. - See the official API documentation for details. Only valid in combination with - ``version=2``, will be ignored else. - """ - if int(version) == 1: - escape_chars = r'_*`[' - elif int(version) == 2: - if entity_type in ['pre', 'code']: - escape_chars = r'\`' - elif entity_type == 'text_link': - escape_chars = r'\)' - else: - escape_chars = r'_*[]()~`>#+-=|{}.!' - else: - raise ValueError('Markdown version must be either 1 or 2!') - - return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', text) - - -# -------- date/time related helpers -------- -def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float: - """ - Converts a datetime object to a float timestamp (with sub-second precision). - If the datetime object is timezone-naive, it is assumed to be in UTC. - """ - if dt_obj.tzinfo is None: - dt_obj = dt_obj.replace(tzinfo=dtm.timezone.utc) - return dt_obj.timestamp() - - -def _localize(datetime: dtm.datetime, tzinfo: dtm.tzinfo) -> dtm.datetime: - """Localize the datetime, where UTC is handled depending on whether pytz is available or not""" - if tzinfo is DTM_UTC: - return datetime.replace(tzinfo=DTM_UTC) - return tzinfo.localize(datetime) # type: ignore[attr-defined] - - -def to_float_timestamp( - time_object: Union[int, float, dtm.timedelta, dtm.datetime, dtm.time], - reference_timestamp: float = None, - tzinfo: dtm.tzinfo = None, -) -> float: - """ - Converts a given time object to a float POSIX timestamp. - Used to convert different time specifications to a common format. The time object - can be relative (i.e. indicate a time increment, or a time of day) or absolute. - object objects from the :class:`datetime` module that are timezone-naive will be assumed - to be in UTC, if ``bot`` is not passed or ``bot.defaults`` is :obj:`None`. - - Args: - time_object (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \ - :obj:`datetime.datetime` | :obj:`datetime.time`): - Time value to convert. The semantics of this parameter will depend on its type: - - * :obj:`int` or :obj:`float` will be interpreted as "seconds from ``reference_t``" - * :obj:`datetime.timedelta` will be interpreted as - "time increment from ``reference_t``" - * :obj:`datetime.datetime` will be interpreted as an absolute date/time value - * :obj:`datetime.time` will be interpreted as a specific time of day - - reference_timestamp (:obj:`float`, optional): POSIX timestamp that indicates the absolute - time from which relative calculations are to be performed (e.g. when ``t`` is given as - an :obj:`int`, indicating "seconds from ``reference_t``"). Defaults to now (the time at - which this function is called). - - If ``t`` is given as an absolute representation of date & time (i.e. a - :obj:`datetime.datetime` object), ``reference_timestamp`` is not relevant and so its - value should be :obj:`None`. If this is not the case, a ``ValueError`` will be raised. - tzinfo (:obj:`pytz.BaseTzInfo`, optional): If ``t`` is a naive object from the - :class:`datetime` module, it will be interpreted as this timezone. Defaults to - ``pytz.utc``. - - Note: - Only to be used by ``telegram.ext``. - - - Returns: - :obj:`float` | :obj:`None`: - The return value depends on the type of argument ``t``. - If ``t`` is given as a time increment (i.e. as a :obj:`int`, :obj:`float` or - :obj:`datetime.timedelta`), then the return value will be ``reference_t`` + ``t``. - - Else if it is given as an absolute date/time value (i.e. a :obj:`datetime.datetime` - object), the equivalent value as a POSIX timestamp will be returned. - - Finally, if it is a time of the day without date (i.e. a :obj:`datetime.time` - object), the return value is the nearest future occurrence of that time of day. - - Raises: - TypeError: If ``t``'s type is not one of those described above. - ValueError: If ``t`` is a :obj:`datetime.datetime` and :obj:`reference_timestamp` is not - :obj:`None`. - """ - if reference_timestamp is None: - reference_timestamp = time.time() - elif isinstance(time_object, dtm.datetime): - raise ValueError('t is an (absolute) datetime while reference_timestamp is not None') - - if isinstance(time_object, dtm.timedelta): - return reference_timestamp + time_object.total_seconds() - if isinstance(time_object, (int, float)): - return reference_timestamp + time_object - - if tzinfo is None: - tzinfo = UTC - - if isinstance(time_object, dtm.time): - reference_dt = dtm.datetime.fromtimestamp( - reference_timestamp, tz=time_object.tzinfo or tzinfo - ) - reference_date = reference_dt.date() - reference_time = reference_dt.timetz() - - aware_datetime = dtm.datetime.combine(reference_date, time_object) - if aware_datetime.tzinfo is None: - aware_datetime = _localize(aware_datetime, tzinfo) - - # if the time of day has passed today, use tomorrow - if reference_time > aware_datetime.timetz(): - aware_datetime += dtm.timedelta(days=1) - return _datetime_to_float_timestamp(aware_datetime) - if isinstance(time_object, dtm.datetime): - if time_object.tzinfo is None: - time_object = _localize(time_object, tzinfo) - return _datetime_to_float_timestamp(time_object) - - raise TypeError(f'Unable to convert {type(time_object).__name__} object to timestamp') - - -def to_timestamp( - dt_obj: Union[int, float, dtm.timedelta, dtm.datetime, dtm.time, None], - reference_timestamp: float = None, - tzinfo: dtm.tzinfo = None, -) -> Optional[int]: - """ - Wrapper over :func:`to_float_timestamp` which returns an integer (the float value truncated - down to the nearest integer). - - See the documentation for :func:`to_float_timestamp` for more details. - """ - return ( - int(to_float_timestamp(dt_obj, reference_timestamp, tzinfo)) - if dt_obj is not None - else None - ) - - -def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optional[dtm.datetime]: - """ - Converts an (integer) unix timestamp to a timezone aware datetime object. - :obj:`None` s are left alone (i.e. ``from_timestamp(None)`` is :obj:`None`). - - Args: - unixtime (:obj:`int`): Integer POSIX timestamp. - tzinfo (:obj:`datetime.tzinfo`, optional): The timezone to which the timestamp is to be - converted to. Defaults to UTC. - - Returns: - Timezone aware equivalent :obj:`datetime.datetime` value if ``unixtime`` is not - :obj:`None`; else :obj:`None`. - """ - if unixtime is None: - return None - - if tzinfo is not None: - return dtm.datetime.fromtimestamp(unixtime, tz=tzinfo) - return dtm.datetime.utcfromtimestamp(unixtime) - - -# -------- end -------- - - -def mention_html(user_id: Union[int, str], name: str) -> str: - """ - Args: - user_id (:obj:`int`): The user's id which you want to mention. - name (:obj:`str`): The name the mention is showing. - - Returns: - :obj:`str`: The inline mention for the user as HTML. - """ - return f'{escape(name)}' - - -def mention_markdown(user_id: Union[int, str], name: str, version: int = 1) -> str: - """ - Args: - user_id (:obj:`int`): The user's id which you want to mention. - name (:obj:`str`): The name the mention is showing. - version (:obj:`int` | :obj:`str`): Use to specify the version of Telegram's Markdown. - Either ``1`` or ``2``. Defaults to ``1``. - - Returns: - :obj:`str`: The inline mention for the user as Markdown. - """ - return f'[{escape_markdown(name, version=version)}](tg://user?id={user_id})' - - -def effective_message_type(entity: Union['Message', 'Update']) -> Optional[str]: - """ - Extracts the type of message as a string identifier from a :class:`telegram.Message` or a - :class:`telegram.Update`. - - Args: - entity (:class:`telegram.Update` | :class:`telegram.Message`): The ``update`` or - ``message`` to extract from. - - Returns: - :obj:`str`: One of ``Message.MESSAGE_TYPES`` - - """ - # Importing on file-level yields cyclic Import Errors - from telegram import Message, Update # pylint: disable=C0415 - - if isinstance(entity, Message): - message = entity - elif isinstance(entity, Update): - message = entity.effective_message # type: ignore[assignment] - else: - raise TypeError(f"entity is not Message or Update (got: {type(entity)})") - - for i in Message.MESSAGE_TYPES: - if getattr(message, i, None): - return i - - return None - - -def create_deep_linked_url(bot_username: str, payload: str = None, group: bool = False) -> str: - """ - Creates a deep-linked URL for this ``bot_username`` with the specified ``payload``. - See https://core.telegram.org/bots#deep-linking to learn more. - - The ``payload`` may consist of the following characters: ``A-Z, a-z, 0-9, _, -`` - - Note: - Works well in conjunction with - ``CommandHandler("start", callback, filters = Filters.regex('payload'))`` - - Examples: - ``create_deep_linked_url(bot.get_me().username, "some-params")`` - - Args: - bot_username (:obj:`str`): The username to link to - payload (:obj:`str`, optional): Parameters to encode in the created URL - group (:obj:`bool`, optional): If :obj:`True` the user is prompted to select a group to - add the bot to. If :obj:`False`, opens a one-on-one conversation with the bot. - Defaults to :obj:`False`. - - Returns: - :obj:`str`: An URL to start the bot with specific parameters - """ - if bot_username is None or len(bot_username) <= 3: - raise ValueError("You must provide a valid bot_username.") - - base_url = f'https://t.me/{bot_username}' - if not payload: - return base_url - - if len(payload) > 64: - raise ValueError("The deep-linking payload must not exceed 64 characters.") - - if not re.match(r'^[A-Za-z0-9_-]+$', payload): - raise ValueError( - "Only the following characters are allowed for deep-linked " - "URLs: A-Z, a-z, 0-9, _ and -" - ) - - if group: - key = 'startgroup' - else: - key = 'start' - - return f'{base_url}?{key}={payload}' - - -def encode_conversations_to_json(conversations: Dict[str, Dict[Tuple, object]]) -> str: - """Helper method to encode a conversations dict (that uses tuples as keys) to a - JSON-serializable way. Use :meth:`decode_conversations_from_json` to decode. - - Args: - conversations (:obj:`dict`): The conversations dict to transform to JSON. - - Returns: - :obj:`str`: The JSON-serialized conversations dict - """ - tmp: Dict[str, JSONDict] = {} - for handler, states in conversations.items(): - tmp[handler] = {} - for key, state in states.items(): - tmp[handler][json.dumps(key)] = state - return json.dumps(tmp) - - -def decode_conversations_from_json(json_string: str) -> Dict[str, Dict[Tuple, object]]: - """Helper method to decode a conversations dict (that uses tuples as keys) from a - JSON-string created with :meth:`encode_conversations_to_json`. - - Args: - json_string (:obj:`str`): The conversations dict as JSON string. - - Returns: - :obj:`dict`: The conversations dict after decoding - """ - tmp = json.loads(json_string) - conversations: Dict[str, Dict[Tuple, object]] = {} - for handler, states in tmp.items(): - conversations[handler] = {} - for key, state in states.items(): - conversations[handler][tuple(json.loads(key))] = state - return conversations - - -def decode_user_chat_data_from_json(data: str) -> DefaultDict[int, Dict[object, object]]: - """Helper method to decode chat or user data (that uses ints as keys) from a - JSON-string. - - Args: - data (:obj:`str`): The user/chat_data dict as JSON string. - - Returns: - :obj:`dict`: The user/chat_data defaultdict after decoding - """ - tmp: DefaultDict[int, Dict[object, object]] = defaultdict(dict) - decoded_data = json.loads(data) - for user, user_data in decoded_data.items(): - user = int(user) - tmp[user] = {} - for key, value in user_data.items(): - try: - key = int(key) - except ValueError: - pass - tmp[user][key] = value - return tmp - - -DVType = TypeVar('DVType', bound=object) -OT = TypeVar('OT', bound=object) - - -class DefaultValue(Generic[DVType]): - """Wrapper for immutable default arguments that allows to check, if the default value was set - explicitly. Usage:: - - DefaultOne = DefaultValue(1) - def f(arg=DefaultOne): - if arg is DefaultOne: - print('`arg` is the default') - arg = arg.value - else: - print('`arg` was set explicitly') - print(f'`arg` = {str(arg)}') - - This yields:: - - >>> f() - `arg` is the default - `arg` = 1 - >>> f(1) - `arg` was set explicitly - `arg` = 1 - >>> f(2) - `arg` was set explicitly - `arg` = 2 - - Also allows to evaluate truthiness:: - - default = DefaultValue(value) - if default: - ... - - is equivalent to:: - - default = DefaultValue(value) - if value: - ... - - ``repr(DefaultValue(value))`` returns ``repr(value)`` and ``str(DefaultValue(value))`` returns - ``f'DefaultValue({value})'``. - - Args: - value (:obj:`obj`): The value of the default argument - - Attributes: - value (:obj:`obj`): The value of the default argument - - """ - - __slots__ = ('value', '__dict__') - - def __init__(self, value: DVType = None): - self.value = value - - def __bool__(self) -> bool: - return bool(self.value) - - @overload - @staticmethod - def get_value(obj: 'DefaultValue[OT]') -> OT: - ... - - @overload - @staticmethod - def get_value(obj: OT) -> OT: - ... - - @staticmethod - def get_value(obj: Union[OT, 'DefaultValue[OT]']) -> OT: - """ - Shortcut for:: - - return obj.value if isinstance(obj, DefaultValue) else obj - - Args: - obj (:obj:`object`): The object to process - - Returns: - Same type as input, or the value of the input: The value - """ - return obj.value if isinstance(obj, DefaultValue) else obj # type: ignore[return-value] - - # This is mostly here for readability during debugging - def __str__(self) -> str: - return f'DefaultValue({self.value})' - - # This is here to have the default instances nicely rendered in the docs - def __repr__(self) -> str: - return repr(self.value) - - -DEFAULT_NONE: DefaultValue = DefaultValue(None) -""":class:`DefaultValue`: Default :obj:`None`""" - -DEFAULT_FALSE: DefaultValue = DefaultValue(False) -""":class:`DefaultValue`: Default :obj:`False`""" - -DEFAULT_20: DefaultValue = DefaultValue(20) -""":class:`DefaultValue`: Default :obj:`20`""" diff --git a/telegramer/include/telegram/utils/promise.py b/telegramer/include/telegram/utils/promise.py deleted file mode 100644 index 01bef33..0000000 --- a/telegramer/include/telegram/utils/promise.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the :class:`telegram.ext.utils.promise.Promise` class for backwards -compatibility. -""" -import warnings - -import telegram.ext.utils.promise as promise -from telegram.utils.deprecate import TelegramDeprecationWarning - -warnings.warn( - 'telegram.utils.promise is deprecated. Please use telegram.ext.utils.promise instead.', - TelegramDeprecationWarning, -) - -Promise = promise.Promise -""" -:class:`telegram.ext.utils.promise.Promise` - -.. deprecated:: v13.2 - Use :class:`telegram.ext.utils.promise.Promise` instead. -""" diff --git a/telegramer/include/telegram/utils/request.py b/telegramer/include/telegram/utils/request.py deleted file mode 100644 index 4b8de7b..0000000 --- a/telegramer/include/telegram/utils/request.py +++ /dev/null @@ -1,400 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains methods to make POST and GET requests.""" -import logging -import os -import socket -import sys -import warnings - -try: - import ujson as json -except ImportError: - import json # type: ignore[no-redef] - -from typing import Any, Union - -import certifi - -try: - import telegram.vendor.ptb_urllib3.urllib3 as urllib3 - import telegram.vendor.ptb_urllib3.urllib3.contrib.appengine as appengine - from telegram.vendor.ptb_urllib3.urllib3.connection import HTTPConnection - from telegram.vendor.ptb_urllib3.urllib3.fields import RequestField - from telegram.vendor.ptb_urllib3.urllib3.util.timeout import Timeout -except ImportError: # pragma: no cover - try: - import urllib3 # type: ignore[no-redef] - import urllib3.contrib.appengine as appengine # type: ignore[no-redef] - from urllib3.connection import HTTPConnection # type: ignore[no-redef] - from urllib3.fields import RequestField # type: ignore[no-redef] - from urllib3.util.timeout import Timeout # type: ignore[no-redef] - - warnings.warn( - 'python-telegram-bot is using upstream urllib3. This is allowed but not ' - 'supported by python-telegram-bot maintainers.' - ) - except ImportError: - warnings.warn( - "python-telegram-bot wasn't properly installed. Please refer to README.rst on " - "how to properly install." - ) - raise - -# pylint: disable=C0412 -from telegram import InputFile, TelegramError -from telegram.error import ( - BadRequest, - ChatMigrated, - Conflict, - InvalidToken, - NetworkError, - RetryAfter, - TimedOut, - Unauthorized, -) -from telegram.utils.types import JSONDict -from telegram.utils.deprecate import set_new_attribute_deprecated - - -def _render_part(self: RequestField, name: str, value: str) -> str: # pylint: disable=W0613 - r""" - Monkey patch urllib3.urllib3.fields.RequestField to make it *not* support RFC2231 compliant - Content-Disposition headers since telegram servers don't understand it. Instead just escape - \\ and " and replace any \n and \r with a space. - """ - value = value.replace('\\', '\\\\').replace('"', '\\"') - value = value.replace('\r', ' ').replace('\n', ' ') - return f'{name}="{value}"' - - -RequestField._render_part = _render_part # type: ignore # pylint: disable=W0212 - -logging.getLogger('telegram.vendor.ptb_urllib3.urllib3').setLevel(logging.WARNING) - -USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)' - - -class Request: - """ - Helper class for python-telegram-bot which provides methods to perform POST & GET towards - Telegram servers. - - Args: - con_pool_size (:obj:`int`): Number of connections to keep in the connection pool. - proxy_url (:obj:`str`): The URL to the proxy server. For example: `http://127.0.0.1:3128`. - urllib3_proxy_kwargs (:obj:`dict`): Arbitrary arguments passed as-is to - :obj:`urllib3.ProxyManager`. This value will be ignored if :attr:`proxy_url` is not - set. - connect_timeout (:obj:`int` | :obj:`float`): The maximum amount of time (in seconds) to - wait for a connection attempt to a server to succeed. :obj:`None` will set an - infinite timeout for connection attempts. Defaults to ``5.0``. - read_timeout (:obj:`int` | :obj:`float`): The maximum amount of time (in seconds) to wait - between consecutive read operations for a response from the server. :obj:`None` will - set an infinite timeout. This value is usually overridden by the various - :class:`telegram.Bot` methods. Defaults to ``5.0``. - - """ - - __slots__ = ('_connect_timeout', '_con_pool_size', '_con_pool', '__dict__') - - def __init__( - self, - con_pool_size: int = 1, - proxy_url: str = None, - urllib3_proxy_kwargs: JSONDict = None, - connect_timeout: float = 5.0, - read_timeout: float = 5.0, - ): - if urllib3_proxy_kwargs is None: - urllib3_proxy_kwargs = {} - - self._connect_timeout = connect_timeout - - sockopts = HTTPConnection.default_socket_options + [ - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - ] - - # TODO: Support other platforms like mac and windows. - if 'linux' in sys.platform: - sockopts.append( - (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 120) # pylint: disable=no-member - ) - sockopts.append( - (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30) # pylint: disable=no-member - ) - sockopts.append( - (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 8) # pylint: disable=no-member - ) - - self._con_pool_size = con_pool_size - - kwargs = dict( - maxsize=con_pool_size, - cert_reqs='CERT_REQUIRED', - ca_certs=certifi.where(), - socket_options=sockopts, - timeout=urllib3.Timeout(connect=self._connect_timeout, read=read_timeout, total=None), - ) - - # Set a proxy according to the following order: - # * proxy defined in proxy_url (+ urllib3_proxy_kwargs) - # * proxy set in `HTTPS_PROXY` env. var. - # * proxy set in `https_proxy` env. var. - # * None (if no proxy is configured) - - if not proxy_url: - proxy_url = os.environ.get('HTTPS_PROXY') or os.environ.get('https_proxy') - - self._con_pool: Union[ - urllib3.PoolManager, - appengine.AppEngineManager, - 'SOCKSProxyManager', # noqa: F821 - urllib3.ProxyManager, - ] = None # type: ignore - if not proxy_url: - if appengine.is_appengine_sandbox(): - # Use URLFetch service if running in App Engine - self._con_pool = appengine.AppEngineManager() - else: - self._con_pool = urllib3.PoolManager(**kwargs) - else: - kwargs.update(urllib3_proxy_kwargs) - if proxy_url.startswith('socks'): - try: - # pylint: disable=C0415 - from telegram.vendor.ptb_urllib3.urllib3.contrib.socks import SOCKSProxyManager - except ImportError as exc: - raise RuntimeError('PySocks is missing') from exc - self._con_pool = SOCKSProxyManager(proxy_url, **kwargs) - else: - mgr = urllib3.proxy_from_url(proxy_url, **kwargs) - if mgr.proxy.auth: - # TODO: what about other auth types? - auth_hdrs = urllib3.make_headers(proxy_basic_auth=mgr.proxy.auth) - mgr.proxy_headers.update(auth_hdrs) - - self._con_pool = mgr - - def __setattr__(self, key: str, value: object) -> None: - set_new_attribute_deprecated(self, key, value) - - @property - def con_pool_size(self) -> int: - """The size of the connection pool used.""" - return self._con_pool_size - - def stop(self) -> None: - """Performs cleanup on shutdown.""" - self._con_pool.clear() # type: ignore - - @staticmethod - def _parse(json_data: bytes) -> Union[JSONDict, bool]: - """Try and parse the JSON returned from Telegram. - - Returns: - dict: A JSON parsed as Python dict with results - on error this dict will be empty. - - """ - decoded_s = json_data.decode('utf-8', 'replace') - try: - data = json.loads(decoded_s) - except ValueError as exc: - raise TelegramError('Invalid server response') from exc - - if not data.get('ok'): # pragma: no cover - description = data.get('description') - parameters = data.get('parameters') - if parameters: - migrate_to_chat_id = parameters.get('migrate_to_chat_id') - if migrate_to_chat_id: - raise ChatMigrated(migrate_to_chat_id) - retry_after = parameters.get('retry_after') - if retry_after: - raise RetryAfter(retry_after) - if description: - return description - - return data['result'] - - def _request_wrapper(self, *args: object, **kwargs: Any) -> bytes: - """Wraps urllib3 request for handling known exceptions. - - Args: - args: unnamed arguments, passed to urllib3 request. - kwargs: keyword arguments, passed to urllib3 request. - - Returns: - bytes: A non-parsed JSON text. - - Raises: - TelegramError - - """ - # Make sure to hint Telegram servers that we reuse connections by sending - # "Connection: keep-alive" in the HTTP headers. - if 'headers' not in kwargs: - kwargs['headers'] = {} - kwargs['headers']['connection'] = 'keep-alive' - # Also set our user agent - kwargs['headers']['user-agent'] = USER_AGENT - - try: - resp = self._con_pool.request(*args, **kwargs) - except urllib3.exceptions.TimeoutError as error: - raise TimedOut() from error - except urllib3.exceptions.HTTPError as error: - # HTTPError must come last as its the base urllib3 exception class - # TODO: do something smart here; for now just raise NetworkError - raise NetworkError(f'urllib3 HTTPError {error}') from error - - if 200 <= resp.status <= 299: - # 200-299 range are HTTP success statuses - return resp.data - - try: - message = str(self._parse(resp.data)) - except ValueError: - message = 'Unknown HTTPError' - - if resp.status in (401, 403): - raise Unauthorized(message) - if resp.status == 400: - raise BadRequest(message) - if resp.status == 404: - raise InvalidToken() - if resp.status == 409: - raise Conflict(message) - if resp.status == 413: - raise NetworkError( - 'File too large. Check telegram api limits ' - 'https://core.telegram.org/bots/api#senddocument' - ) - if resp.status == 502: - raise NetworkError('Bad Gateway') - raise NetworkError(f'{message} ({resp.status})') - - def post(self, url: str, data: JSONDict, timeout: float = None) -> Union[JSONDict, bool]: - """Request an URL. - - Args: - url (:obj:`str`): The web location we want to retrieve. - data (Dict[:obj:`str`, :obj:`str` | :obj:`int`], optional): A dict of key/value pairs. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - - Returns: - A JSON object. - - """ - urlopen_kwargs = {} - - if timeout is not None: - urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout) - - if data is None: - data = {} - - # Are we uploading files? - files = False - - # pylint: disable=R1702 - for key, val in data.copy().items(): - if isinstance(val, InputFile): - # Convert the InputFile to urllib3 field format - data[key] = val.field_tuple - files = True - elif isinstance(val, (float, int)): - # Urllib3 doesn't like floats it seems - data[key] = str(val) - elif key == 'media': - files = True - # List of media - if isinstance(val, list): - # Attach and set val to attached name for all - media = [] - for med in val: - media_dict = med.to_dict() - media.append(media_dict) - if isinstance(med.media, InputFile): - data[med.media.attach] = med.media.field_tuple - # if the file has a thumb, we also need to attach it to the data - if "thumb" in media_dict: - data[med.thumb.attach] = med.thumb.field_tuple - data[key] = json.dumps(media) - # Single media - else: - # Attach and set val to attached name - media_dict = val.to_dict() - if isinstance(val.media, InputFile): - data[val.media.attach] = val.media.field_tuple - # if the file has a thumb, we also need to attach it to the data - if "thumb" in media_dict: - data[val.thumb.attach] = val.thumb.field_tuple - data[key] = json.dumps(media_dict) - elif isinstance(val, list): - # In case we're sending files, we need to json-dump lists manually - # As we can't know if that's the case, we just json-dump here - data[key] = json.dumps(val) - - # Use multipart upload if we're uploading files, otherwise use JSON - if files: - result = self._request_wrapper('POST', url, fields=data, **urlopen_kwargs) - else: - result = self._request_wrapper( - 'POST', - url, - body=json.dumps(data).encode('utf-8'), - headers={'Content-Type': 'application/json'}, - **urlopen_kwargs, - ) - - return self._parse(result) - - def retrieve(self, url: str, timeout: float = None) -> bytes: - """Retrieve the contents of a file by its URL. - - Args: - url (:obj:`str`): The web location we want to retrieve. - timeout (:obj:`int` | :obj:`float`): If this value is specified, use it as the read - timeout from the server (instead of the one specified during creation of the - connection pool). - - """ - urlopen_kwargs = {} - if timeout is not None: - urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout) - - return self._request_wrapper('GET', url, **urlopen_kwargs) - - def download(self, url: str, filename: str, timeout: float = None) -> None: - """Download a file by its URL. - - Args: - url (:obj:`str`): The web location we want to retrieve. - timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as - the read timeout from the server (instead of the one specified during creation of - the connection pool). - filename (:obj:`str`): The filename within the path to download the file. - - """ - buf = self.retrieve(url, timeout=timeout) - with open(filename, 'wb') as fobj: - fobj.write(buf) diff --git a/telegramer/include/telegram/utils/types.py b/telegramer/include/telegram/utils/types.py deleted file mode 100644 index a3e1f42..0000000 --- a/telegramer/include/telegram/utils/types.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains custom typing aliases.""" -from pathlib import Path -from typing import ( - IO, - TYPE_CHECKING, - Any, - Dict, - List, - Optional, - Tuple, - TypeVar, - Union, -) - -if TYPE_CHECKING: - from telegram import InputFile # noqa: F401 - from telegram.utils.helpers import DefaultValue # noqa: F401 - -FileLike = Union[IO, 'InputFile'] -"""Either an open file handler or a :class:`telegram.InputFile`.""" - -FileInput = Union[str, bytes, FileLike, Path] -"""Valid input for passing files to Telegram. Either a file id as string, a file like object, -a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes`.""" - -JSONDict = Dict[str, Any] -"""Dictionary containing response from Telegram or data to send to the API.""" - -DVType = TypeVar('DVType') -ODVInput = Optional[Union['DefaultValue[DVType]', DVType]] -"""Generic type for bot method parameters which can have defaults. ``ODVInput[type]`` is the same -as ``Optional[Union[DefaultValue, type]]``.""" -DVInput = Union['DefaultValue[DVType]', DVType] -"""Generic type for bot method parameters which can have defaults. ``DVInput[type]`` is the same -as ``Union[DefaultValue, type]``.""" - -RT = TypeVar("RT") -SLT = Union[RT, List[RT], Tuple[RT, ...]] -"""Single instance or list/tuple of instances.""" diff --git a/telegramer/include/telegram/utils/webhookhandler.py b/telegramer/include/telegram/utils/webhookhandler.py deleted file mode 100644 index 88e3c5a..0000000 --- a/telegramer/include/telegram/utils/webhookhandler.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the :class:`telegram.ext.utils.webhookhandler.WebhookHandler` class for -backwards compatibility. -""" -import warnings - -import telegram.ext.utils.webhookhandler as webhook_handler -from telegram.utils.deprecate import TelegramDeprecationWarning - -warnings.warn( - 'telegram.utils.webhookhandler is deprecated. Please use telegram.ext.utils.webhookhandler ' - 'instead.', - TelegramDeprecationWarning, -) - -WebhookHandler = webhook_handler.WebhookHandler -WebhookServer = webhook_handler.WebhookServer -WebhookAppClass = webhook_handler.WebhookAppClass diff --git a/telegramer/include/telegram/vendor/__init__.py b/telegramer/include/telegram/vendor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/__init__.py b/telegramer/include/telegram/vendor/ptb_urllib3/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/__init__.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/__init__.py deleted file mode 100644 index 0cd5e34..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/__init__.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -urllib3 - Thread-safe connection pooling and re-using. -""" -from __future__ import absolute_import -import warnings - -from .connectionpool import ( - HTTPConnectionPool, - HTTPSConnectionPool, - connection_from_url -) - -from . import exceptions -from .filepost import encode_multipart_formdata -from .poolmanager import PoolManager, ProxyManager, proxy_from_url -from .response import HTTPResponse -from .util.request import make_headers -from .util.url import get_host -from .util.timeout import Timeout -from .util.retry import Retry - - -# Set default logging handler to avoid "No handler found" warnings. -import logging -try: # Python 2.7+ - from logging import NullHandler -except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): - pass - -__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' -__license__ = 'MIT' -__version__ = 'dev' - -__all__ = ( - 'HTTPConnectionPool', - 'HTTPSConnectionPool', - 'PoolManager', - 'ProxyManager', - 'HTTPResponse', - 'Retry', - 'Timeout', - 'add_stderr_logger', - 'connection_from_url', - 'disable_warnings', - 'encode_multipart_formdata', - 'get_host', - 'make_headers', - 'proxy_from_url', -) - -logging.getLogger(__name__).addHandler(NullHandler()) - - -def add_stderr_logger(level=logging.DEBUG): - """ - Helper for quickly adding a StreamHandler to the logger. Useful for - debugging. - - Returns the handler after adding it. - """ - # This method needs to be in this __init__.py to get the __name__ correct - # even if urllib3 is vendored within another package. - logger = logging.getLogger(__name__) - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) - logger.addHandler(handler) - logger.setLevel(level) - logger.debug('Added a stderr logging handler to logger: %s', __name__) - return handler - - -# ... Clean up. -del NullHandler - - -# All warning filters *must* be appended unless you're really certain that they -# shouldn't be: otherwise, it's very hard for users to use most Python -# mechanisms to silence them. -# SecurityWarning's always go off by default. -warnings.simplefilter('always', exceptions.SecurityWarning, append=True) -# SubjectAltNameWarning's should go off once per host -warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True) -# InsecurePlatformWarning's don't vary between requests, so we keep it default. -warnings.simplefilter('default', exceptions.InsecurePlatformWarning, - append=True) -# SNIMissingWarnings should go off only once. -warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True) - - -def disable_warnings(category=exceptions.HTTPWarning): - """ - Helper for quickly disabling all urllib3 warnings. - """ - warnings.simplefilter('ignore', category) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/_collections.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/_collections.py deleted file mode 100644 index 4fcd2ab..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/_collections.py +++ /dev/null @@ -1,327 +0,0 @@ -from __future__ import absolute_import -try: - from collections.abc import Mapping, MutableMapping -except ImportError: - from collections import Mapping, MutableMapping -try: - from threading import RLock -except ImportError: # Platform-specific: No threads available - class RLock: - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_value, traceback): - pass - - -try: # Python 2.7+ - from collections import OrderedDict -except ImportError: - from .packages.ordered_dict import OrderedDict -from .packages.six import iterkeys, itervalues, PY3 - - -__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] - - -_Null = object() - - -class RecentlyUsedContainer(MutableMapping): - """ - Provides a thread-safe dict-like container which maintains up to - ``maxsize`` keys while throwing away the least-recently-used keys beyond - ``maxsize``. - - :param maxsize: - Maximum number of recent elements to retain. - - :param dispose_func: - Every time an item is evicted from the container, - ``dispose_func(value)`` is called. Callback which will get called - """ - - ContainerCls = OrderedDict - - def __init__(self, maxsize=10, dispose_func=None): - self._maxsize = maxsize - self.dispose_func = dispose_func - - self._container = self.ContainerCls() - self.lock = RLock() - - def __getitem__(self, key): - # Re-insert the item, moving it to the end of the eviction line. - with self.lock: - item = self._container.pop(key) - self._container[key] = item - return item - - def __setitem__(self, key, value): - evicted_value = _Null - with self.lock: - # Possibly evict the existing value of 'key' - evicted_value = self._container.get(key, _Null) - self._container[key] = value - - # If we didn't evict an existing value, we might have to evict the - # least recently used item from the beginning of the container. - if len(self._container) > self._maxsize: - _key, evicted_value = self._container.popitem(last=False) - - if self.dispose_func and evicted_value is not _Null: - self.dispose_func(evicted_value) - - def __delitem__(self, key): - with self.lock: - value = self._container.pop(key) - - if self.dispose_func: - self.dispose_func(value) - - def __len__(self): - with self.lock: - return len(self._container) - - def __iter__(self): - raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') - - def clear(self): - with self.lock: - # Copy pointers to all values, then wipe the mapping - values = list(itervalues(self._container)) - self._container.clear() - - if self.dispose_func: - for value in values: - self.dispose_func(value) - - def keys(self): - with self.lock: - return list(iterkeys(self._container)) - - -class HTTPHeaderDict(MutableMapping): - """ - :param headers: - An iterable of field-value pairs. Must not contain multiple field names - when compared case-insensitively. - - :param kwargs: - Additional field-value pairs to pass in to ``dict.update``. - - A ``dict`` like container for storing HTTP Headers. - - Field names are stored and compared case-insensitively in compliance with - RFC 7230. Iteration provides the first case-sensitive key seen for each - case-insensitive pair. - - Using ``__setitem__`` syntax overwrites fields that compare equal - case-insensitively in order to maintain ``dict``'s api. For fields that - compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` - in a loop. - - If multiple fields that are equal case-insensitively are passed to the - constructor or ``.update``, the behavior is undefined and some will be - lost. - - >>> headers = HTTPHeaderDict() - >>> headers.add('Set-Cookie', 'foo=bar') - >>> headers.add('set-cookie', 'baz=quxx') - >>> headers['content-length'] = '7' - >>> headers['SET-cookie'] - 'foo=bar, baz=quxx' - >>> headers['Content-Length'] - '7' - """ - - def __init__(self, headers=None, **kwargs): - super(HTTPHeaderDict, self).__init__() - self._container = OrderedDict() - if headers is not None: - if isinstance(headers, HTTPHeaderDict): - self._copy_from(headers) - else: - self.extend(headers) - if kwargs: - self.extend(kwargs) - - def __setitem__(self, key, val): - self._container[key.lower()] = (key, val) - return self._container[key.lower()] - - def __getitem__(self, key): - val = self._container[key.lower()] - return ', '.join(val[1:]) - - def __delitem__(self, key): - del self._container[key.lower()] - - def __contains__(self, key): - return key.lower() in self._container - - def __eq__(self, other): - if not isinstance(other, Mapping) and not hasattr(other, 'keys'): - return False - if not isinstance(other, type(self)): - other = type(self)(other) - return (dict((k.lower(), v) for k, v in self.itermerged()) == - dict((k.lower(), v) for k, v in other.itermerged())) - - def __ne__(self, other): - return not self.__eq__(other) - - if not PY3: # Python 2 - iterkeys = MutableMapping.iterkeys - itervalues = MutableMapping.itervalues - - __marker = object() - - def __len__(self): - return len(self._container) - - def __iter__(self): - # Only provide the originally cased names - for vals in self._container.values(): - yield vals[0] - - def pop(self, key, default=__marker): - '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - ''' - # Using the MutableMapping function directly fails due to the private marker. - # Using ordinary dict.pop would expose the internal structures. - # So let's reinvent the wheel. - try: - value = self[key] - except KeyError: - if default is self.__marker: - raise - return default - else: - del self[key] - return value - - def discard(self, key): - try: - del self[key] - except KeyError: - pass - - def add(self, key, val): - """Adds a (name, value) pair, doesn't overwrite the value if it already - exists. - - >>> headers = HTTPHeaderDict(foo='bar') - >>> headers.add('Foo', 'baz') - >>> headers['foo'] - 'bar, baz' - """ - key_lower = key.lower() - new_vals = key, val - # Keep the common case aka no item present as fast as possible - vals = self._container.setdefault(key_lower, new_vals) - if new_vals is not vals: - # new_vals was not inserted, as there was a previous one - if isinstance(vals, list): - # If already several items got inserted, we have a list - vals.append(val) - else: - # vals should be a tuple then, i.e. only one item so far - # Need to convert the tuple to list for further extension - self._container[key_lower] = [vals[0], vals[1], val] - - def extend(self, *args, **kwargs): - """Generic import function for any type of header-like object. - Adapted version of MutableMapping.update in order to insert items - with self.add instead of self.__setitem__ - """ - if len(args) > 1: - raise TypeError("extend() takes at most 1 positional " - "arguments ({0} given)".format(len(args))) - other = args[0] if len(args) >= 1 else () - - if isinstance(other, HTTPHeaderDict): - for key, val in other.iteritems(): - self.add(key, val) - elif isinstance(other, Mapping): - for key in other: - self.add(key, other[key]) - elif hasattr(other, "keys"): - for key in other.keys(): - self.add(key, other[key]) - else: - for key, value in other: - self.add(key, value) - - for key, value in kwargs.items(): - self.add(key, value) - - def getlist(self, key): - """Returns a list of all the values for the named field. Returns an - empty list if the key doesn't exist.""" - try: - vals = self._container[key.lower()] - except KeyError: - return [] - else: - if isinstance(vals, tuple): - return [vals[1]] - else: - return vals[1:] - - # Backwards compatibility for httplib - getheaders = getlist - getallmatchingheaders = getlist - iget = getlist - - def __repr__(self): - return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) - - def _copy_from(self, other): - for key in other: - val = other.getlist(key) - if isinstance(val, list): - # Don't need to convert tuples - val = list(val) - self._container[key.lower()] = [key] + val - - def copy(self): - clone = type(self)() - clone._copy_from(self) - return clone - - def iteritems(self): - """Iterate over all header lines, including duplicate ones.""" - for key in self: - vals = self._container[key.lower()] - for val in vals[1:]: - yield vals[0], val - - def itermerged(self): - """Iterate over all headers, merging duplicate ones together.""" - for key in self: - val = self._container[key.lower()] - yield val[0], ', '.join(val[1:]) - - def items(self): - return list(self.iteritems()) - - @classmethod - def from_httplib(cls, message): # Python 2 - """Read headers from a Python 2 httplib message object.""" - # python2.7 does not expose a proper API for exporting multiheaders - # efficiently. This function re-reads raw lines from the message - # object and extracts the multiheaders properly. - headers = [] - - for line in message.headers: - if line.startswith((' ', '\t')): - key, value = headers[-1] - headers[-1] = (key, value + '\r\n' + line.rstrip()) - continue - - key, value = line.split(':', 1) - headers.append((key, value.strip())) - - return cls(headers) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/connection.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/connection.py deleted file mode 100644 index 9f06c39..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/connection.py +++ /dev/null @@ -1,369 +0,0 @@ -from __future__ import absolute_import -import datetime -import logging -import os -import sys -import socket -from socket import error as SocketError, timeout as SocketTimeout -import warnings -from .packages import six -from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection -from .packages.six.moves.http_client import HTTPException # noqa: F401 - -try: # Compiled with SSL? - import ssl - BaseSSLError = ssl.SSLError -except (ImportError, AttributeError): # Platform-specific: No SSL. - ssl = None - - class BaseSSLError(BaseException): - pass - - -try: # Python 3: - # Not a no-op, we're adding this to the namespace so it can be imported. - ConnectionError = ConnectionError -except NameError: # Python 2: - class ConnectionError(Exception): - pass - - -from .exceptions import ( - NewConnectionError, - ConnectTimeoutError, - SubjectAltNameWarning, - SystemTimeWarning, -) -from .packages.ssl_match_hostname import match_hostname, CertificateError - -from .util.ssl_ import ( - resolve_cert_reqs, - resolve_ssl_version, - assert_fingerprint, - create_urllib3_context, - ssl_wrap_socket -) - - -from .util import connection - -from ._collections import HTTPHeaderDict - -log = logging.getLogger(__name__) - -port_by_scheme = { - 'http': 80, - 'https': 443, -} - -# When updating RECENT_DATE, move it to -# within two years of the current date, and no -# earlier than 6 months ago. -RECENT_DATE = datetime.date(2016, 1, 1) - - -class DummyConnection(object): - """Used to detect a failed ConnectionCls import.""" - pass - - -class HTTPConnection(_HTTPConnection, object): - """ - Based on httplib.HTTPConnection but provides an extra constructor - backwards-compatibility layer between older and newer Pythons. - - Additional keyword parameters are used to configure attributes of the connection. - Accepted parameters include: - - - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` - - ``source_address``: Set the source address for the current connection. - - .. note:: This is ignored for Python 2.6. It is only applied for 2.7 and 3.x - - - ``socket_options``: Set specific options on the underlying socket. If not specified, then - defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling - Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. - - For example, if you wish to enable TCP Keep Alive in addition to the defaults, - you might pass:: - - HTTPConnection.default_socket_options + [ - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - ] - - Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). - """ - - default_port = port_by_scheme['http'] - - #: Disable Nagle's algorithm by default. - #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` - default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] - - #: Whether this connection verifies the host's certificate. - is_verified = False - - def __init__(self, *args, **kw): - if six.PY3: # Python 3 - kw.pop('strict', None) - - # Pre-set source_address in case we have an older Python like 2.6. - self.source_address = kw.get('source_address') - - if sys.version_info < (2, 7): # Python 2.6 - # _HTTPConnection on Python 2.6 will balk at this keyword arg, but - # not newer versions. We can still use it when creating a - # connection though, so we pop it *after* we have saved it as - # self.source_address. - kw.pop('source_address', None) - - #: The socket options provided by the user. If no options are - #: provided, we use the default options. - self.socket_options = kw.pop('socket_options', self.default_socket_options) - - # Superclass also sets self.source_address in Python 2.7+. - _HTTPConnection.__init__(self, *args, **kw) - - def _new_conn(self): - """ Establish a socket connection and set nodelay settings on it. - - :return: New socket connection. - """ - extra_kw = {} - if self.source_address: - extra_kw['source_address'] = self.source_address - - if self.socket_options: - extra_kw['socket_options'] = self.socket_options - - try: - conn = connection.create_connection( - (self.host, self.port), self.timeout, **extra_kw) - - except SocketTimeout as e: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) - - except SocketError as e: - raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) - - return conn - - def _prepare_conn(self, conn): - self.sock = conn - # the _tunnel_host attribute was added in python 2.6.3 (via - # http://hg.python.org/cpython/rev/0f57b30a152f) so pythons 2.6(0-2) do - # not have them. - if getattr(self, '_tunnel_host', None): - # TODO: Fix tunnel so it doesn't depend on self.sock state. - self._tunnel() - # Mark this connection as not reusable - self.auto_open = 0 - - def connect(self): - conn = self._new_conn() - self._prepare_conn(conn) - - def request_chunked(self, method, url, body=None, headers=None): - """ - Alternative to the common request method, which sends the - body with chunked encoding and not as one block - """ - headers = HTTPHeaderDict(headers if headers is not None else {}) - skip_accept_encoding = 'accept-encoding' in headers - skip_host = 'host' in headers - self.putrequest( - method, - url, - skip_accept_encoding=skip_accept_encoding, - skip_host=skip_host - ) - for header, value in headers.items(): - self.putheader(header, value) - if 'transfer-encoding' not in headers: - self.putheader('Transfer-Encoding', 'chunked') - self.endheaders() - - if body is not None: - stringish_types = six.string_types + (six.binary_type,) - if isinstance(body, stringish_types): - body = (body,) - for chunk in body: - if not chunk: - continue - if not isinstance(chunk, six.binary_type): - chunk = chunk.encode('utf8') - len_str = hex(len(chunk))[2:] - self.send(len_str.encode('utf-8')) - self.send(b'\r\n') - self.send(chunk) - self.send(b'\r\n') - - # After the if clause, to always have a closed body - self.send(b'0\r\n\r\n') - - -class HTTPSConnection(HTTPConnection): - default_port = port_by_scheme['https'] - - ssl_version = None - - def __init__(self, host, port=None, key_file=None, cert_file=None, - strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - ssl_context=None, **kw): - - HTTPConnection.__init__(self, host, port, strict=strict, - timeout=timeout, **kw) - - self.key_file = key_file - self.cert_file = cert_file - self.ssl_context = ssl_context - - # Required property for Google AppEngine 1.9.0 which otherwise causes - # HTTPS requests to go out as HTTP. (See Issue #356) - self._protocol = 'https' - - def connect(self): - conn = self._new_conn() - self._prepare_conn(conn) - - if self.ssl_context is None: - self.ssl_context = create_urllib3_context( - ssl_version=resolve_ssl_version(None), - cert_reqs=resolve_cert_reqs(None), - ) - - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, - ssl_context=self.ssl_context, - ) - - -class VerifiedHTTPSConnection(HTTPSConnection): - """ - Based on httplib.HTTPSConnection but wraps the socket with - SSL certification. - """ - cert_reqs = None - ca_certs = None - ca_cert_dir = None - ssl_version = None - assert_fingerprint = None - - def set_cert(self, key_file=None, cert_file=None, - cert_reqs=None, ca_certs=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None): - """ - This method should only be called once, before the connection is used. - """ - # If cert_reqs is not provided, we can try to guess. If the user gave - # us a cert database, we assume they want to use it: otherwise, if - # they gave us an SSL Context object we should use whatever is set for - # it. - if cert_reqs is None: - if ca_certs or ca_cert_dir: - cert_reqs = 'CERT_REQUIRED' - elif self.ssl_context is not None: - cert_reqs = self.ssl_context.verify_mode - - self.key_file = key_file - self.cert_file = cert_file - self.cert_reqs = cert_reqs - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - self.ca_certs = ca_certs and os.path.expanduser(ca_certs) - self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) - - def connect(self): - # Add certificate verification - conn = self._new_conn() - - hostname = self.host - if getattr(self, '_tunnel_host', None): - # _tunnel_host was added in Python 2.6.3 - # (See: http://hg.python.org/cpython/rev/0f57b30a152f) - - self.sock = conn - # Calls self._set_hostport(), so self.host is - # self._tunnel_host below. - self._tunnel() - # Mark this connection as not reusable - self.auto_open = 0 - - # Override the host with the one we're requesting data from. - hostname = self._tunnel_host - - is_time_off = datetime.date.today() < RECENT_DATE - if is_time_off: - warnings.warn(( - 'System time is way off (before {0}). This will probably ' - 'lead to SSL verification errors').format(RECENT_DATE), - SystemTimeWarning - ) - - # Wrap socket using verification with the root certs in - # trusted_root_certs - if self.ssl_context is None: - self.ssl_context = create_urllib3_context( - ssl_version=resolve_ssl_version(self.ssl_version), - cert_reqs=resolve_cert_reqs(self.cert_reqs), - ) - - context = self.ssl_context - context.verify_mode = resolve_cert_reqs(self.cert_reqs) - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - server_hostname=hostname, - ssl_context=context) - - if self.assert_fingerprint: - assert_fingerprint(self.sock.getpeercert(binary_form=True), - self.assert_fingerprint) - elif context.verify_mode != ssl.CERT_NONE \ - and self.assert_hostname is not False: - cert = self.sock.getpeercert() - if not cert.get('subjectAltName', ()): - warnings.warn(( - 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' - '`commonName` for now. This feature is being removed by major browsers and ' - 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' - 'for details.)'.format(hostname)), - SubjectAltNameWarning - ) - _match_hostname(cert, self.assert_hostname or hostname) - - self.is_verified = ( - context.verify_mode == ssl.CERT_REQUIRED or - self.assert_fingerprint is not None - ) - - -def _match_hostname(cert, asserted_hostname): - try: - match_hostname(cert, asserted_hostname) - except CertificateError as e: - log.error( - 'Certificate did not match expected hostname: %s. ' - 'Certificate: %s', asserted_hostname, cert - ) - # Add cert to exception and reraise so client code can inspect - # the cert when catching the exception, if they want to - e._peer_cert = cert - raise - - -if ssl: - # Make a copy for testing. - UnverifiedHTTPSConnection = HTTPSConnection - HTTPSConnection = VerifiedHTTPSConnection -else: - HTTPSConnection = DummyConnection diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/connectionpool.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/connectionpool.py deleted file mode 100644 index a958f99..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/connectionpool.py +++ /dev/null @@ -1,912 +0,0 @@ -from __future__ import absolute_import -import errno -import logging -import sys -import warnings - -from socket import error as SocketError, timeout as SocketTimeout -import socket - - -from .exceptions import ( - ClosedPoolError, - ProtocolError, - EmptyPoolError, - HeaderParsingError, - HostChangedError, - LocationValueError, - MaxRetryError, - ProxyError, - ReadTimeoutError, - SSLError, - TimeoutError, - InsecureRequestWarning, - NewConnectionError, - ConnectTimeoutError, -) -from .packages.ssl_match_hostname import CertificateError -from .packages import six -from .packages.six.moves import queue -from .connection import ( - port_by_scheme, - DummyConnection, - HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, - HTTPException, BaseSSLError, -) -from .request import RequestMethods -from .response import HTTPResponse - -from .util.connection import is_connection_dropped -from .util.request import set_file_position -from .util.response import assert_header_parsing -from .util.retry import Retry -from .util.timeout import Timeout -from .util.url import get_host, Url - - -if six.PY2: - # Queue is imported for side effects on MS Windows - import Queue as _unused_module_Queue # noqa: F401 - -xrange = six.moves.xrange - -log = logging.getLogger(__name__) - -_Default = object() - - -# Pool objects -class ConnectionPool(object): - """ - Base class for all connection pools, such as - :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. - """ - - scheme = None - QueueCls = queue.LifoQueue - - def __init__(self, host, port=None): - if not host: - raise LocationValueError("No host specified.") - - self.host = _ipv6_host(host).lower() - self.port = port - - def __str__(self): - return '%s(host=%r, port=%r)' % (type(self).__name__, - self.host, self.port) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - # Return False to re-raise any potential exceptions - return False - - def close(self): - """ - Close all pooled connections and disable the pool. - """ - pass - - -# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 -_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK]) - - -class HTTPConnectionPool(ConnectionPool, RequestMethods): - """ - Thread-safe connection pool for one host. - - :param host: - Host used for this HTTP Connection (e.g. "localhost"), passed into - :class:`httplib.HTTPConnection`. - - :param port: - Port used for this HTTP Connection (None is equivalent to 80), passed - into :class:`httplib.HTTPConnection`. - - :param strict: - Causes BadStatusLine to be raised if the status line can't be parsed - as a valid HTTP/1.0 or 1.1 status line, passed into - :class:`httplib.HTTPConnection`. - - .. note:: - Only works in Python 2. This parameter is ignored in Python 3. - - :param timeout: - Socket timeout in seconds for each individual connection. This can - be a float or integer, which sets the timeout for the HTTP request, - or an instance of :class:`urllib3.util.Timeout` which gives you more - fine-grained control over request timeouts. After the constructor has - been parsed, this is always a `urllib3.util.Timeout` object. - - :param maxsize: - Number of connections to save that can be reused. More than 1 is useful - in multithreaded situations. If ``block`` is set to False, more - connections will be created but they will not be saved once they've - been used. - - :param block: - If set to True, no more than ``maxsize`` connections will be used at - a time. When no free connections are available, the call will block - until a connection has been released. This is a useful side effect for - particular multithreaded situations where one does not want to use more - than maxsize connections per host to prevent flooding. - - :param headers: - Headers to include with all requests, unless other headers are given - explicitly. - - :param retries: - Retry configuration to use by default with requests in this pool. - - :param _proxy: - Parsed proxy URL, should not be used directly, instead, see - :class:`urllib3.connectionpool.ProxyManager`" - - :param _proxy_headers: - A dictionary with proxy headers, should not be used directly, - instead, see :class:`urllib3.connectionpool.ProxyManager`" - - :param \\**conn_kw: - Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, - :class:`urllib3.connection.HTTPSConnection` instances. - """ - - scheme = 'http' - ConnectionCls = HTTPConnection - ResponseCls = HTTPResponse - - def __init__(self, host, port=None, strict=False, - timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, - headers=None, retries=None, - _proxy=None, _proxy_headers=None, - **conn_kw): - ConnectionPool.__init__(self, host, port) - RequestMethods.__init__(self, headers) - - self.strict = strict - - if not isinstance(timeout, Timeout): - timeout = Timeout.from_float(timeout) - - if retries is None: - retries = Retry.DEFAULT - - self.timeout = timeout - self.retries = retries - - self.pool = self.QueueCls(maxsize) - self.block = block - - self.proxy = _proxy - self.proxy_headers = _proxy_headers or {} - - # Fill the queue up so that doing get() on it will block properly - for _ in xrange(maxsize): - self.pool.put(None) - - # These are mostly for testing and debugging purposes. - self.num_connections = 0 - self.num_requests = 0 - self.conn_kw = conn_kw - - if self.proxy: - # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. - # We cannot know if the user has added default socket options, so we cannot replace the - # list. - self.conn_kw.setdefault('socket_options', []) - - def _new_conn(self): - """ - Return a fresh :class:`HTTPConnection`. - """ - self.num_connections += 1 - log.debug("Starting new HTTP connection (%d): %s", - self.num_connections, self.host) - - conn = self.ConnectionCls(host=self.host, port=self.port, - timeout=self.timeout.connect_timeout, - strict=self.strict, **self.conn_kw) - return conn - - def _get_conn(self, timeout=None): - """ - Get a connection. Will return a pooled connection if one is available. - - If no connections are available and :prop:`.block` is ``False``, then a - fresh connection is returned. - - :param timeout: - Seconds to wait before giving up and raising - :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and - :prop:`.block` is ``True``. - """ - conn = None - try: - conn = self.pool.get(block=self.block, timeout=timeout) - - except AttributeError: # self.pool is None - raise ClosedPoolError(self, "Pool is closed.") - - except queue.Empty: - if self.block: - raise EmptyPoolError(self, - "Pool reached maximum size and no more " - "connections are allowed.") - pass # Oh well, we'll create a new connection then - - # If this is a persistent connection, check if it got disconnected - if conn and is_connection_dropped(conn): - log.debug("Resetting dropped connection: %s", self.host) - conn.close() - if getattr(conn, 'auto_open', 1) == 0: - # This is a proxied connection that has been mutated by - # httplib._tunnel() and cannot be reused (since it would - # attempt to bypass the proxy) - conn = None - - return conn or self._new_conn() - - def _put_conn(self, conn): - """ - Put a connection back into the pool. - - :param conn: - Connection object for the current host and port as returned by - :meth:`._new_conn` or :meth:`._get_conn`. - - If the pool is already full, the connection is closed and discarded - because we exceeded maxsize. If connections are discarded frequently, - then maxsize should be increased. - - If the pool is closed, then the connection will be closed and discarded. - """ - try: - self.pool.put(conn, block=False) - return # Everything is dandy, done. - except AttributeError: - # self.pool is None. - pass - except queue.Full: - # This should never happen if self.block == True - log.warning( - "Connection pool is full, discarding connection: %s", - self.host) - - # Connection never got put back into the pool, close it. - if conn: - conn.close() - - def _validate_conn(self, conn): - """ - Called right before a request is made, after the socket is created. - """ - # Force connect early to allow us to set read timeout in time - if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` - conn.connect() - - def _prepare_proxy(self, conn): - # Nothing to do for HTTP connections. - pass - - def _get_timeout(self, timeout): - """ Helper that always returns a :class:`urllib3.util.Timeout` """ - if timeout is _Default: - return self.timeout.clone() - - if isinstance(timeout, Timeout): - return timeout.clone() - else: - # User passed us an int/float. This is for backwards compatibility, - # can be removed later - return Timeout.from_float(timeout) - - def _raise_timeout(self, err, url, timeout_value, exc_cls): - """Is the error actually a timeout? Will raise a ReadTimeout or pass""" - - # exc_cls is either ReadTimeoutError or ConnectTimeoutError - # Only ReadTimeoutError requires the url (preserving old behaviour) - args = [self] - if exc_cls is ReadTimeoutError: - args.append(url) - desc = 'Read' - else: - desc = 'Connect' - - if isinstance(err, SocketTimeout): - args.append("%s timed out. (%s timeout=%s)" % (desc, desc.lower(), timeout_value)) - raise exc_cls(*args) - - # See the above comment about EAGAIN in Python 3. In Python 2 we have - # to specifically catch it and throw the timeout error - elif hasattr(err, 'errno') and err.errno in _blocking_errnos: - args.append("%s timed out. (%s timeout=%s)" % (desc, desc.lower(), timeout_value)) - raise exc_cls(*args) - - # Catch possible read timeouts thrown as SSL errors. If not the - # case, rethrow the original. We need to do this because of: - # http://bugs.python.org/issue10272 - elif 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python 2.6 - args.append("%s timed out. (%s timeout=%s)" % (desc, desc.lower(), timeout_value)) - raise exc_cls(*args) - - def _make_request(self, conn, method, url, timeout=_Default, chunked=False, - **httplib_request_kw): - """ - Perform a request on a given urllib connection object taken from our - pool. - - :param conn: - a connection from one of our connection pools - - :param timeout: - Socket timeout in seconds for the request. This can be a - float or integer, which will set the same timeout value for - the socket connect and the socket read, or an instance of - :class:`urllib3.util.Timeout`, which gives you more fine-grained - control over your timeouts. - """ - self.num_requests += 1 - - timeout_obj = self._get_timeout(timeout) - timeout_obj.start_connect() - conn.timeout = timeout_obj.connect_timeout - - # Trigger any extra validation we need to do. - try: - self._validate_conn(conn) - except (SocketTimeout, BaseSSLError) as e: - # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. - self._raise_timeout(err=e, url=url, timeout_value=conn.timeout, - exc_cls=ConnectTimeoutError) - raise - - # Reset the timeout for the recv() on the socket - read_timeout = timeout_obj.read_timeout - - # App Engine doesn't have a sock attr - if getattr(conn, 'sock', None): - # In Python 3 socket.py will catch EAGAIN and return None when you - # try and read into the file pointer created by http.client, which - # instead raises a BadStatusLine exception. Instead of catching - # the exception and assuming all BadStatusLine exceptions are read - # timeouts, check for a zero timeout before making the request. - if read_timeout == 0: - raise ReadTimeoutError( - self, url, "Read timed out. (read timeout=%s)" % read_timeout) - if read_timeout is Timeout.DEFAULT_TIMEOUT: - conn.sock.settimeout(socket.getdefaulttimeout()) - else: # None or a value - conn.sock.settimeout(read_timeout) - - # conn.request() calls httplib.*.request, not the method in - # urllib3.request. It also calls makefile (recv) on the socket. - if chunked: - conn.request_chunked(method, url, **httplib_request_kw) - else: - conn.request(method, url, **httplib_request_kw) - - # Receive the response from the server - try: - try: # Python 2.7, use buffering of HTTP responses - httplib_response = conn.getresponse(buffering=True) - except TypeError: # Python 2.6 and older, Python 3 - try: - httplib_response = conn.getresponse() - except Exception as e: - # Remove the TypeError from the exception chain in Python 3; - # otherwise it looks like a programming error was the cause. - six.raise_from(e, None) - except (SocketTimeout, BaseSSLError, SocketError) as e: - self._raise_timeout(err=e, url=url, timeout_value=read_timeout, - exc_cls=ReadTimeoutError) - raise - - # AppEngine doesn't have a version attr. - http_version = getattr(conn, '_http_vsn_str', 'HTTP/?') - log.debug("%s://%s:%s \"%s %s %s\" %s %s", self.scheme, self.host, self.port, - method, url, http_version, httplib_response.status, - httplib_response.length) - - try: - assert_header_parsing(httplib_response.msg) - except HeaderParsingError as hpe: # Platform-specific: Python 3 - log.warning( - 'Failed to parse headers (url=%s): %s', - self._absolute_url(url), hpe, exc_info=True) - - return httplib_response - - def _absolute_url(self, path): - return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url - - def close(self): - """ - Close all pooled connections and disable the pool. - """ - # Disable access to the pool - old_pool, self.pool = self.pool, None - - try: - while True: - conn = old_pool.get(block=False) - if conn: - conn.close() - - except queue.Empty: - pass # Done. - - def is_same_host(self, url): - """ - Check if the given ``url`` is a member of the same host as this - connection pool. - """ - if url.startswith('/'): - return True - - # TODO: Add optional support for socket.gethostbyname checking. - scheme, host, port = get_host(url) - - host = _ipv6_host(host).lower() - - # Use explicit default port for comparison when none is given - if self.port and not port: - port = port_by_scheme.get(scheme) - elif not self.port and port == port_by_scheme.get(scheme): - port = None - - return (scheme, host, port) == (self.scheme, self.host, self.port) - - def urlopen(self, method, url, body=None, headers=None, retries=None, - redirect=True, assert_same_host=True, timeout=_Default, - pool_timeout=None, release_conn=None, chunked=False, - body_pos=None, **response_kw): - """ - Get a connection from the pool and perform an HTTP request. This is the - lowest level call for making a request, so you'll need to specify all - the raw details. - - .. note:: - - More commonly, it's appropriate to use a convenience method provided - by :class:`.RequestMethods`, such as :meth:`request`. - - .. note:: - - `release_conn` will only behave as expected if - `preload_content=False` because we want to make - `preload_content=False` the default behaviour someday soon without - breaking backwards compatibility. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param body: - Data to send in the request body (useful for creating - POST requests, see HTTPConnectionPool.post_url for - more convenience). - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param retries: - Configure the number of retries to allow before raising a - :class:`~urllib3.exceptions.MaxRetryError` exception. - - Pass ``None`` to retry until you receive a response. Pass a - :class:`~urllib3.util.retry.Retry` object for fine-grained control - over different types of retries. - Pass an integer number to retry connection errors that many times, - but no other types of errors. Pass zero to never retry. - - If ``False``, then retries are disabled and any exception is raised - immediately. Also, instead of raising a MaxRetryError on redirects, - the redirect response will be returned. - - :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. - - :param redirect: - If True, automatically handle redirects (status codes 301, 302, - 303, 307, 308). Each redirect counts as a retry. Disabling retries - will disable redirect, too. - - :param assert_same_host: - If ``True``, will make sure that the host of the pool requests is - consistent else will raise HostChangedError. When False, you can - use the pool on an HTTP proxy and request foreign hosts. - - :param timeout: - If specified, overrides the default timeout for this one - request. It may be a float (in seconds) or an instance of - :class:`urllib3.util.Timeout`. - - :param pool_timeout: - If set and the pool is set to block=True, then this method will - block for ``pool_timeout`` seconds and raise EmptyPoolError if no - connection is available within the time period. - - :param release_conn: - If False, then the urlopen call will not release the connection - back into the pool once a response is received (but will release if - you read the entire contents of the response such as when - `preload_content=True`). This is useful if you're not preloading - the response's content immediately. You will need to call - ``r.release_conn()`` on the response ``r`` to return the connection - back into the pool. If None, it takes the value of - ``response_kw.get('preload_content', True)``. - - :param chunked: - If True, urllib3 will send the body using chunked transfer - encoding. Otherwise, urllib3 will send the body using the standard - content-length form. Defaults to False. - - :param int body_pos: - Position to seek to in file-like body in the event of a retry or - redirect. Typically this won't need to be set because urllib3 will - auto-populate the value when needed. - - :param \\**response_kw: - Additional parameters are passed to - :meth:`urllib3.response.HTTPResponse.from_httplib` - """ - if headers is None: - headers = self.headers - - if not isinstance(retries, Retry): - retries = Retry.from_int(retries, redirect=redirect, default=self.retries) - - if release_conn is None: - release_conn = response_kw.get('preload_content', True) - - # Check host - if assert_same_host and not self.is_same_host(url): - raise HostChangedError(self, url, retries) - - conn = None - - # Track whether `conn` needs to be released before - # returning/raising/recursing. Update this variable if necessary, and - # leave `release_conn` constant throughout the function. That way, if - # the function recurses, the original value of `release_conn` will be - # passed down into the recursive call, and its value will be respected. - # - # See issue #651 [1] for details. - # - # [1] - release_this_conn = release_conn - - # Merge the proxy headers. Only do this in HTTP. We have to copy the - # headers dict so we can safely change it without those changes being - # reflected in anyone else's copy. - if self.scheme == 'http': - headers = headers.copy() - headers.update(self.proxy_headers) - - # Must keep the exception bound to a separate variable or else Python 3 - # complains about UnboundLocalError. - err = None - - # Keep track of whether we cleanly exited the except block. This - # ensures we do proper cleanup in finally. - clean_exit = False - - # Rewind body position, if needed. Record current position - # for future rewinds in the event of a redirect/retry. - body_pos = set_file_position(body, body_pos) - - try: - # Request a connection from the queue. - timeout_obj = self._get_timeout(timeout) - conn = self._get_conn(timeout=pool_timeout) - - conn.timeout = timeout_obj.connect_timeout - - is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None) - if is_new_proxy_conn: - self._prepare_proxy(conn) - - # Make the request on the httplib connection object. - httplib_response = self._make_request(conn, method, url, - timeout=timeout_obj, - body=body, headers=headers, - chunked=chunked) - - # If we're going to release the connection in ``finally:``, then - # the response doesn't need to know about the connection. Otherwise - # it will also try to release it and we'll have a double-release - # mess. - response_conn = conn if not release_conn else None - - # Pass method to Response for length checking - response_kw['request_method'] = method - - # Import httplib's response into our own wrapper object - response = self.ResponseCls.from_httplib(httplib_response, - pool=self, - connection=response_conn, - retries=retries, - **response_kw) - - # Everything went great! - clean_exit = True - - except queue.Empty: - # Timed out by queue. - raise EmptyPoolError(self, "No pool connections are available.") - - except (BaseSSLError, CertificateError) as e: - # Close the connection. If a connection is reused on which there - # was a Certificate error, the next request will certainly raise - # another Certificate error. - clean_exit = False - raise SSLError(e) - - except SSLError: - # Treat SSLError separately from BaseSSLError to preserve - # traceback. - clean_exit = False - raise - - except (TimeoutError, HTTPException, SocketError, ProtocolError) as e: - # Discard the connection for these exceptions. It will be - # be replaced during the next _get_conn() call. - clean_exit = False - - if isinstance(e, (SocketError, NewConnectionError)) and self.proxy: - e = ProxyError('Cannot connect to proxy.', e) - elif isinstance(e, (SocketError, HTTPException)): - e = ProtocolError('Connection aborted.', e) - - retries = retries.increment(method, url, error=e, _pool=self, - _stacktrace=sys.exc_info()[2]) - retries.sleep() - - # Keep track of the error for the retry warning. - err = e - - finally: - if not clean_exit: - # We hit some kind of exception, handled or otherwise. We need - # to throw the connection away unless explicitly told not to. - # Close the connection, set the variable to None, and make sure - # we put the None back in the pool to avoid leaking it. - conn = conn and conn.close() - release_this_conn = True - - if release_this_conn: - # Put the connection back to be reused. If the connection is - # expired then it will be None, which will get replaced with a - # fresh connection during _get_conn. - self._put_conn(conn) - - if not conn: - # Try again - log.warning("Retrying (%r) after connection " - "broken by '%r': %s", retries, err, url) - return self.urlopen(method, url, body, headers, retries, - redirect, assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) - - # Handle redirect? - redirect_location = redirect and response.get_redirect_location() - if redirect_location: - if response.status == 303: - method = 'GET' - - try: - retries = retries.increment(method, url, response=response, _pool=self) - except MaxRetryError: - if retries.raise_on_redirect: - # Release the connection for this response, since we're not - # returning it to be released manually. - response.release_conn() - raise - return response - - retries.sleep_for_retry(response) - log.debug("Redirecting %s -> %s", url, redirect_location) - return self.urlopen( - method, redirect_location, body, headers, - retries=retries, redirect=redirect, - assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) - - # Check if we should retry the HTTP response. - has_retry_after = bool(response.getheader('Retry-After')) - if retries.is_retry(method, response.status, has_retry_after): - try: - retries = retries.increment(method, url, response=response, _pool=self) - except MaxRetryError: - if retries.raise_on_status: - # Release the connection for this response, since we're not - # returning it to be released manually. - response.release_conn() - raise - return response - retries.sleep(response) - log.debug("Retry: %s", url) - return self.urlopen( - method, url, body, headers, - retries=retries, redirect=redirect, - assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, - body_pos=body_pos, **response_kw) - - return response - - -class HTTPSConnectionPool(HTTPConnectionPool): - """ - Same as :class:`.HTTPConnectionPool`, but HTTPS. - - When Python is compiled with the :mod:`ssl` module, then - :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, - instead of :class:`.HTTPSConnection`. - - :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, - ``assert_hostname`` and ``host`` in this order to verify connections. - If ``assert_hostname`` is False, no verification is done. - - The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, - ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is - available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade - the connection socket into an SSL socket. - """ - - scheme = 'https' - ConnectionCls = HTTPSConnection - - def __init__(self, host, port=None, - strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, - block=False, headers=None, retries=None, - _proxy=None, _proxy_headers=None, - key_file=None, cert_file=None, cert_reqs=None, - ca_certs=None, ssl_version=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None, **conn_kw): - - HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, - block, headers, retries, _proxy, _proxy_headers, - **conn_kw) - - if ca_certs and cert_reqs is None: - cert_reqs = 'CERT_REQUIRED' - - self.key_file = key_file - self.cert_file = cert_file - self.cert_reqs = cert_reqs - self.ca_certs = ca_certs - self.ca_cert_dir = ca_cert_dir - self.ssl_version = ssl_version - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - - def _prepare_conn(self, conn): - """ - Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` - and establish the tunnel if proxy is used. - """ - - if isinstance(conn, VerifiedHTTPSConnection): - conn.set_cert(key_file=self.key_file, - cert_file=self.cert_file, - cert_reqs=self.cert_reqs, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint) - conn.ssl_version = self.ssl_version - return conn - - def _prepare_proxy(self, conn): - """ - Establish tunnel connection early, because otherwise httplib - would improperly set Host: header to proxy's IP:port. - """ - # Python 2.7+ - try: - set_tunnel = conn.set_tunnel - except AttributeError: # Platform-specific: Python 2.6 - set_tunnel = conn._set_tunnel - - if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older - set_tunnel(self.host, self.port) - else: - set_tunnel(self.host, self.port, self.proxy_headers) - - conn.connect() - - def _new_conn(self): - """ - Return a fresh :class:`httplib.HTTPSConnection`. - """ - self.num_connections += 1 - log.debug("Starting new HTTPS connection (%d): %s", - self.num_connections, self.host) - - if not self.ConnectionCls or self.ConnectionCls is DummyConnection: - raise SSLError("Can't connect to HTTPS URL because the SSL " - "module is not available.") - - actual_host = self.host - actual_port = self.port - if self.proxy is not None: - actual_host = self.proxy.host - actual_port = self.proxy.port - - conn = self.ConnectionCls(host=actual_host, port=actual_port, - timeout=self.timeout.connect_timeout, - strict=self.strict, **self.conn_kw) - - return self._prepare_conn(conn) - - def _validate_conn(self, conn): - """ - Called right before a request is made, after the socket is created. - """ - super(HTTPSConnectionPool, self)._validate_conn(conn) - - if not conn.is_verified: - warnings.warn(( - 'Unverified HTTPS request is being made. ' - 'Adding certificate verification is strongly advised. See: ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings'), - InsecureRequestWarning) - - -def connection_from_url(url, **kw): - """ - Given a url, return an :class:`.ConnectionPool` instance of its host. - - This is a shortcut for not having to parse out the scheme, host, and port - of the url before creating an :class:`.ConnectionPool` instance. - - :param url: - Absolute URL string that must include the scheme. Port is optional. - - :param \\**kw: - Passes additional parameters to the constructor of the appropriate - :class:`.ConnectionPool`. Useful for specifying things like - timeout, maxsize, headers, etc. - - Example:: - - >>> conn = connection_from_url('http://google.com/') - >>> r = conn.request('GET', '/') - """ - scheme, host, port = get_host(url) - port = port or port_by_scheme.get(scheme, 80) - if scheme == 'https': - return HTTPSConnectionPool(host, port=port, **kw) - else: - return HTTPConnectionPool(host, port=port, **kw) - - -def _ipv6_host(host): - """ - Process IPv6 address literals - """ - - # httplib doesn't like it when we include brackets in IPv6 addresses - # Specifically, if we include brackets but also pass the port then - # httplib crazily doubles up the square brackets on the Host header. - # Instead, we need to make sure we never pass ``None`` as the port. - # However, for backward compatibility reasons we can't actually - # *assert* that. See http://bugs.python.org/issue28539 - # - # Also if an IPv6 address literal has a zone identifier, the - # percent sign might be URIencoded, convert it back into ASCII - if host.startswith('[') and host.endswith(']'): - host = host.replace('%25', '%').strip('[]') - return host diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/__init__.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/appengine.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/appengine.py deleted file mode 100644 index 814b022..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/appengine.py +++ /dev/null @@ -1,296 +0,0 @@ -""" -This module provides a pool manager that uses Google App Engine's -`URLFetch Service `_. - -Example usage:: - - from urllib3 import PoolManager - from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox - - if is_appengine_sandbox(): - # AppEngineManager uses AppEngine's URLFetch API behind the scenes - http = AppEngineManager() - else: - # PoolManager uses a socket-level API behind the scenes - http = PoolManager() - - r = http.request('GET', 'https://google.com/') - -There are `limitations `_ to the URLFetch service and it may not be -the best choice for your application. There are three options for using -urllib3 on Google App Engine: - -1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is - cost-effective in many circumstances as long as your usage is within the - limitations. -2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. - Sockets also have `limitations and restrictions - `_ and have a lower free quota than URLFetch. - To use sockets, be sure to specify the following in your ``app.yaml``:: - - env_variables: - GAE_USE_SOCKETS_HTTPLIB : 'true' - -3. If you are using `App Engine Flexible -`_, you can use the standard -:class:`PoolManager` without any configuration or special environment variables. -""" - -from __future__ import absolute_import -import logging -import os -import warnings -from ..packages.six.moves.urllib.parse import urljoin - -from ..exceptions import ( - HTTPError, - HTTPWarning, - MaxRetryError, - ProtocolError, - TimeoutError, - SSLError -) - -from ..packages.six import BytesIO -from ..request import RequestMethods -from ..response import HTTPResponse -from ..util.timeout import Timeout -from ..util.retry import Retry - -try: - from google.appengine.api import urlfetch -except ImportError: - urlfetch = None - - -log = logging.getLogger(__name__) - - -class AppEnginePlatformWarning(HTTPWarning): - pass - - -class AppEnginePlatformError(HTTPError): - pass - - -class AppEngineManager(RequestMethods): - """ - Connection manager for Google App Engine sandbox applications. - - This manager uses the URLFetch service directly instead of using the - emulated httplib, and is subject to URLFetch limitations as described in - the App Engine documentation `here - `_. - - Notably it will raise an :class:`AppEnginePlatformError` if: - * URLFetch is not available. - * If you attempt to use this on App Engine Flexible, as full socket - support is available. - * If a request size is more than 10 megabytes. - * If a response size is more than 32 megabtyes. - * If you use an unsupported request method such as OPTIONS. - - Beyond those cases, it will raise normal urllib3 errors. - """ - - def __init__(self, headers=None, retries=None, validate_certificate=True, - urlfetch_retries=True): - if not urlfetch: - raise AppEnginePlatformError( - "URLFetch is not available in this environment.") - - if is_prod_appengine_mvms(): - raise AppEnginePlatformError( - "Use normal urllib3.PoolManager instead of AppEngineManager" - "on Managed VMs, as using URLFetch is not necessary in " - "this environment.") - - warnings.warn( - "urllib3 is using URLFetch on Google App Engine sandbox instead " - "of sockets. To use sockets directly instead of URLFetch see " - "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", - AppEnginePlatformWarning) - - RequestMethods.__init__(self, headers) - self.validate_certificate = validate_certificate - self.urlfetch_retries = urlfetch_retries - - self.retries = retries or Retry.DEFAULT - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - # Return False to re-raise any potential exceptions - return False - - def urlopen(self, method, url, body=None, headers=None, - retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, - **response_kw): - - retries = self._get_retries(retries, redirect) - - try: - follow_redirects = ( - redirect and - retries.redirect != 0 and - retries.total) - response = urlfetch.fetch( - url, - payload=body, - method=method, - headers=headers or {}, - allow_truncated=False, - follow_redirects=self.urlfetch_retries and follow_redirects, - deadline=self._get_absolute_timeout(timeout), - validate_certificate=self.validate_certificate, - ) - except urlfetch.DeadlineExceededError as e: - raise TimeoutError(self, e) - - except urlfetch.InvalidURLError as e: - if 'too large' in str(e): - raise AppEnginePlatformError( - "URLFetch request too large, URLFetch only " - "supports requests up to 10mb in size.", e) - raise ProtocolError(e) - - except urlfetch.DownloadError as e: - if 'Too many redirects' in str(e): - raise MaxRetryError(self, url, reason=e) - raise ProtocolError(e) - - except urlfetch.ResponseTooLargeError as e: - raise AppEnginePlatformError( - "URLFetch response too large, URLFetch only supports" - "responses up to 32mb in size.", e) - - except urlfetch.SSLCertificateError as e: - raise SSLError(e) - - except urlfetch.InvalidMethodError as e: - raise AppEnginePlatformError( - "URLFetch does not support method: %s" % method, e) - - http_response = self._urlfetch_response_to_http_response( - response, retries=retries, **response_kw) - - # Handle redirect? - redirect_location = redirect and http_response.get_redirect_location() - if redirect_location: - # Check for redirect response - if (self.urlfetch_retries and retries.raise_on_redirect): - raise MaxRetryError(self, url, "too many redirects") - else: - if http_response.status == 303: - method = 'GET' - - try: - retries = retries.increment(method, url, response=http_response, _pool=self) - except MaxRetryError: - if retries.raise_on_redirect: - raise MaxRetryError(self, url, "too many redirects") - return http_response - - retries.sleep_for_retry(http_response) - log.debug("Redirecting %s -> %s", url, redirect_location) - redirect_url = urljoin(url, redirect_location) - return self.urlopen( - method, redirect_url, body, headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) - - # Check if we should retry the HTTP response. - has_retry_after = bool(http_response.getheader('Retry-After')) - if retries.is_retry(method, http_response.status, has_retry_after): - retries = retries.increment( - method, url, response=http_response, _pool=self) - log.debug("Retry: %s", url) - retries.sleep(http_response) - return self.urlopen( - method, url, - body=body, headers=headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) - - return http_response - - def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): - - if is_prod_appengine(): - # Production GAE handles deflate encoding automatically, but does - # not remove the encoding header. - content_encoding = urlfetch_resp.headers.get('content-encoding') - - if content_encoding == 'deflate': - del urlfetch_resp.headers['content-encoding'] - - transfer_encoding = urlfetch_resp.headers.get('transfer-encoding') - # We have a full response's content, - # so let's make sure we don't report ourselves as chunked data. - if transfer_encoding == 'chunked': - encodings = transfer_encoding.split(",") - encodings.remove('chunked') - urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings) - - return HTTPResponse( - # In order for decoding to work, we must present the content as - # a file-like object. - body=BytesIO(urlfetch_resp.content), - headers=urlfetch_resp.headers, - status=urlfetch_resp.status_code, - **response_kw - ) - - def _get_absolute_timeout(self, timeout): - if timeout is Timeout.DEFAULT_TIMEOUT: - return None # Defer to URLFetch's default. - if isinstance(timeout, Timeout): - if timeout._read is not None or timeout._connect is not None: - warnings.warn( - "URLFetch does not support granular timeout settings, " - "reverting to total or default URLFetch timeout.", - AppEnginePlatformWarning) - return timeout.total - return timeout - - def _get_retries(self, retries, redirect): - if not isinstance(retries, Retry): - retries = Retry.from_int( - retries, redirect=redirect, default=self.retries) - - if retries.connect or retries.read or retries.redirect: - warnings.warn( - "URLFetch only supports total retries and does not " - "recognize connect, read, or redirect retry parameters.", - AppEnginePlatformWarning) - - return retries - - -def is_appengine(): - return (is_local_appengine() or - is_prod_appengine() or - is_prod_appengine_mvms()) - - -def is_appengine_sandbox(): - return is_appengine() and not is_prod_appengine_mvms() - - -def is_local_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Development/' in os.environ['SERVER_SOFTWARE']) - - -def is_prod_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and - not is_prod_appengine_mvms()) - - -def is_prod_appengine_mvms(): - return os.environ.get('GAE_VM', False) == 'true' diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/ntlmpool.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/ntlmpool.py deleted file mode 100644 index 642e99e..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/ntlmpool.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -NTLM authenticating pool, contributed by erikcederstran - -Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 -""" -from __future__ import absolute_import - -from logging import getLogger -from ntlm import ntlm - -from .. import HTTPSConnectionPool -from ..packages.six.moves.http_client import HTTPSConnection - - -log = getLogger(__name__) - - -class NTLMConnectionPool(HTTPSConnectionPool): - """ - Implements an NTLM authentication version of an urllib3 connection pool - """ - - scheme = 'https' - - def __init__(self, user, pw, authurl, *args, **kwargs): - """ - authurl is a random URL on the server that is protected by NTLM. - user is the Windows user, probably in the DOMAIN\\username format. - pw is the password for the user. - """ - super(NTLMConnectionPool, self).__init__(*args, **kwargs) - self.authurl = authurl - self.rawuser = user - user_parts = user.split('\\', 1) - self.domain = user_parts[0].upper() - self.user = user_parts[1] - self.pw = pw - - def _new_conn(self): - # Performs the NTLM handshake that secures the connection. The socket - # must be kept open while requests are performed. - self.num_connections += 1 - log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s', - self.num_connections, self.host, self.authurl) - - headers = {} - headers['Connection'] = 'Keep-Alive' - req_header = 'Authorization' - resp_header = 'www-authenticate' - - conn = HTTPSConnection(host=self.host, port=self.port) - - # Send negotiation message - headers[req_header] = ( - 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser)) - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) - res = conn.getresponse() - reshdr = dict(res.getheaders()) - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', reshdr) - log.debug('Response data: %s [...]', res.read(100)) - - # Remove the reference to the socket, so that it can not be closed by - # the response object (we want to keep the socket open) - res.fp = None - - # Server should respond with a challenge message - auth_header_values = reshdr[resp_header].split(', ') - auth_header_value = None - for s in auth_header_values: - if s[:5] == 'NTLM ': - auth_header_value = s[5:] - if auth_header_value is None: - raise Exception('Unexpected %s response header: %s' % - (resp_header, reshdr[resp_header])) - - # Send authentication message - ServerChallenge, NegotiateFlags = \ - ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value) - auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, - self.user, - self.domain, - self.pw, - NegotiateFlags) - headers[req_header] = 'NTLM %s' % auth_msg - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) - res = conn.getresponse() - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', dict(res.getheaders())) - log.debug('Response data: %s [...]', res.read()[:100]) - if res.status != 200: - if res.status == 401: - raise Exception('Server rejected request: wrong ' - 'username or password') - raise Exception('Wrong server response: %s %s' % - (res.status, res.reason)) - - res.fp = None - log.debug('Connection established') - return conn - - def urlopen(self, method, url, body=None, headers=None, retries=3, - redirect=True, assert_same_host=True): - if headers is None: - headers = {} - headers['Connection'] = 'Keep-Alive' - return super(NTLMConnectionPool, self).urlopen(method, url, body, - headers, retries, - redirect, - assert_same_host) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/pyopenssl.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/pyopenssl.py deleted file mode 100644 index eb4d476..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/pyopenssl.py +++ /dev/null @@ -1,450 +0,0 @@ -""" -SSL with SNI_-support for Python 2. Follow these instructions if you would -like to verify SSL certificates in Python 2. Note, the default libraries do -*not* do certificate checking; you need to do additional work to validate -certificates yourself. - -This needs the following packages installed: - -* pyOpenSSL (tested with 16.0.0) -* cryptography (minimum 1.3.4, from pyopenssl) -* idna (minimum 2.0, from cryptography) - -However, pyopenssl depends on cryptography, which depends on idna, so while we -use all three directly here we end up having relatively few packages required. - -You can install them with the following command: - - pip install pyopenssl cryptography idna - -To activate certificate checking, call -:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code -before you begin making HTTP requests. This can be done in a ``sitecustomize`` -module, or at any other time before your application begins using ``urllib3``, -like this:: - - try: - import urllib3.contrib.pyopenssl - urllib3.contrib.pyopenssl.inject_into_urllib3() - except ImportError: - pass - -Now you can use :mod:`urllib3` as you normally would, and it will support SNI -when the required modules are installed. - -Activating this module also has the positive side effect of disabling SSL/TLS -compression in Python 2 (see `CRIME attack`_). - -If you want to configure the default list of supported cipher suites, you can -set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. - -.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication -.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) -""" -from __future__ import absolute_import - -import OpenSSL.SSL -from cryptography import x509 -from cryptography.hazmat.backends.openssl import backend as openssl_backend -from cryptography.hazmat.backends.openssl.x509 import _Certificate - -from socket import timeout, error as SocketError -from io import BytesIO - -try: # Platform-specific: Python 2 - from socket import _fileobject -except ImportError: # Platform-specific: Python 3 - _fileobject = None - from ..packages.backports.makefile import backport_makefile - -import logging -import ssl -import six -import sys - -from .. import util - -__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] - -# SNI always works. -HAS_SNI = True - -# Map from urllib3 to PyOpenSSL compatible parameter-values. -_openssl_versions = { - ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, - ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, -} - -if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): - _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD - -if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): - _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD - -try: - _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) -except AttributeError: - pass - -_stdlib_to_openssl_verify = { - ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, - ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, - ssl.CERT_REQUIRED: - OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, -} -_openssl_to_stdlib_verify = dict( - (v, k) for k, v in _stdlib_to_openssl_verify.items() -) - -# OpenSSL will only write 16K at a time -SSL_WRITE_BLOCKSIZE = 16384 - -orig_util_HAS_SNI = util.HAS_SNI -orig_util_SSLContext = util.ssl_.SSLContext - - -log = logging.getLogger(__name__) - - -def inject_into_urllib3(): - 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' - - _validate_dependencies_met() - - util.ssl_.SSLContext = PyOpenSSLContext - util.HAS_SNI = HAS_SNI - util.ssl_.HAS_SNI = HAS_SNI - util.IS_PYOPENSSL = True - util.ssl_.IS_PYOPENSSL = True - - -def extract_from_urllib3(): - 'Undo monkey-patching by :func:`inject_into_urllib3`.' - - util.ssl_.SSLContext = orig_util_SSLContext - util.HAS_SNI = orig_util_HAS_SNI - util.ssl_.HAS_SNI = orig_util_HAS_SNI - util.IS_PYOPENSSL = False - util.ssl_.IS_PYOPENSSL = False - - -def _validate_dependencies_met(): - """ - Verifies that PyOpenSSL's package-level dependencies have been met. - Throws `ImportError` if they are not met. - """ - # Method added in `cryptography==1.1`; not available in older versions - from cryptography.x509.extensions import Extensions - if getattr(Extensions, "get_extension_for_class", None) is None: - raise ImportError("'cryptography' module missing required functionality. " - "Try upgrading to v1.3.4 or newer.") - - # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 - # attribute is only present on those versions. - from OpenSSL.crypto import X509 - x509 = X509() - if getattr(x509, "_x509", None) is None: - raise ImportError("'pyOpenSSL' module missing required functionality. " - "Try upgrading to v0.14 or newer.") - - -def _dnsname_to_stdlib(name): - """ - Converts a dNSName SubjectAlternativeName field to the form used by the - standard library on the given Python version. - - Cryptography produces a dNSName as a unicode string that was idna-decoded - from ASCII bytes. We need to idna-encode that string to get it back, and - then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib - uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). - """ - def idna_encode(name): - """ - Borrowed wholesale from the Python Cryptography Project. It turns out - that we can't just safely call `idna.encode`: it can explode for - wildcard names. This avoids that problem. - """ - import idna - - for prefix in [u'*.', u'.']: - if name.startswith(prefix): - name = name[len(prefix):] - return prefix.encode('ascii') + idna.encode(name) - return idna.encode(name) - - name = idna_encode(name) - if sys.version_info >= (3, 0): - name = name.decode('utf-8') - return name - - -def get_subj_alt_name(peer_cert): - """ - Given an PyOpenSSL certificate, provides all the subject alternative names. - """ - # Pass the cert to cryptography, which has much better APIs for this. - # This is technically using private APIs, but should work across all - # relevant versions until PyOpenSSL gets something proper for this. - cert = _Certificate(openssl_backend, peer_cert._x509) - - # We want to find the SAN extension. Ask Cryptography to locate it (it's - # faster than looping in Python) - try: - ext = cert.extensions.get_extension_for_class( - x509.SubjectAlternativeName - ).value - except x509.ExtensionNotFound: - # No such extension, return the empty list. - return [] - except (x509.DuplicateExtension, x509.UnsupportedExtension, - x509.UnsupportedGeneralNameType, UnicodeError) as e: - # A problem has been found with the quality of the certificate. Assume - # no SAN field is present. - log.warning( - "A problem was encountered with the certificate that prevented " - "urllib3 from finding the SubjectAlternativeName field. This can " - "affect certificate validation. The error was %s", - e, - ) - return [] - - # We want to return dNSName and iPAddress fields. We need to cast the IPs - # back to strings because the match_hostname function wants them as - # strings. - # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 - # decoded. This is pretty frustrating, but that's what the standard library - # does with certificates, and so we need to attempt to do the same. - names = [ - ('DNS', _dnsname_to_stdlib(name)) - for name in ext.get_values_for_type(x509.DNSName) - ] - names.extend( - ('IP Address', str(name)) - for name in ext.get_values_for_type(x509.IPAddress) - ) - - return names - - -class WrappedSocket(object): - '''API-compatibility wrapper for Python OpenSSL's Connection-class. - - Note: _makefile_refs, _drop() and _reuse() are needed for the garbage - collector of pypy. - ''' - - def __init__(self, connection, socket, suppress_ragged_eofs=True): - self.connection = connection - self.socket = socket - self.suppress_ragged_eofs = suppress_ragged_eofs - self._makefile_refs = 0 - self._closed = False - - def fileno(self): - return self.socket.fileno() - - # Copy-pasted from Python 3.5 source code - def _decref_socketios(self): - if self._makefile_refs > 0: - self._makefile_refs -= 1 - if self._closed: - self.close() - - def recv(self, *args, **kwargs): - try: - data = self.connection.recv(*args, **kwargs) - except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): - return b'' - else: - raise SocketError(str(e)) - except OpenSSL.SSL.ZeroReturnError as e: - if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return b'' - else: - raise - except OpenSSL.SSL.WantReadError: - rd = util.wait_for_read(self.socket, self.socket.gettimeout()) - if not rd: - raise timeout('The read operation timed out') - else: - return self.recv(*args, **kwargs) - else: - return data - - def recv_into(self, *args, **kwargs): - try: - return self.connection.recv_into(*args, **kwargs) - except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): - return 0 - else: - raise SocketError(str(e)) - except OpenSSL.SSL.ZeroReturnError as e: - if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return 0 - else: - raise - except OpenSSL.SSL.WantReadError: - rd = util.wait_for_read(self.socket, self.socket.gettimeout()) - if not rd: - raise timeout('The read operation timed out') - else: - return self.recv_into(*args, **kwargs) - - def settimeout(self, timeout): - return self.socket.settimeout(timeout) - - def _send_until_done(self, data): - while True: - try: - return self.connection.send(data) - except OpenSSL.SSL.WantWriteError: - wr = util.wait_for_write(self.socket, self.socket.gettimeout()) - if not wr: - raise timeout() - continue - - def sendall(self, data): - total_sent = 0 - while total_sent < len(data): - sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) - total_sent += sent - - def shutdown(self): - # FIXME rethrow compatible exceptions should we ever use this - self.connection.shutdown() - - def close(self): - if self._makefile_refs < 1: - try: - self._closed = True - return self.connection.close() - except OpenSSL.SSL.Error: - return - else: - self._makefile_refs -= 1 - - def getpeercert(self, binary_form=False): - x509 = self.connection.get_peer_certificate() - - if not x509: - return x509 - - if binary_form: - return OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, - x509) - - return { - 'subject': ( - (('commonName', x509.get_subject().CN),), - ), - 'subjectAltName': get_subj_alt_name(x509) - } - - def _reuse(self): - self._makefile_refs += 1 - - def _drop(self): - if self._makefile_refs < 1: - self.close() - else: - self._makefile_refs -= 1 - - -if _fileobject: # Platform-specific: Python 2 - def makefile(self, mode, bufsize=-1): - self._makefile_refs += 1 - return _fileobject(self, mode, bufsize, close=True) -else: # Platform-specific: Python 3 - makefile = backport_makefile - -WrappedSocket.makefile = makefile - - -class PyOpenSSLContext(object): - """ - I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible - for translating the interface of the standard library ``SSLContext`` object - to calls into PyOpenSSL. - """ - def __init__(self, protocol): - self.protocol = _openssl_versions[protocol] - self._ctx = OpenSSL.SSL.Context(self.protocol) - self._options = 0 - self.check_hostname = False - - @property - def options(self): - return self._options - - @options.setter - def options(self, value): - self._options = value - self._ctx.set_options(value) - - @property - def verify_mode(self): - return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] - - @verify_mode.setter - def verify_mode(self, value): - self._ctx.set_verify( - _stdlib_to_openssl_verify[value], - _verify_callback - ) - - def set_default_verify_paths(self): - self._ctx.set_default_verify_paths() - - def set_ciphers(self, ciphers): - if isinstance(ciphers, six.text_type): - ciphers = ciphers.encode('utf-8') - self._ctx.set_cipher_list(ciphers) - - def load_verify_locations(self, cafile=None, capath=None, cadata=None): - if cafile is not None: - cafile = cafile.encode('utf-8') - if capath is not None: - capath = capath.encode('utf-8') - self._ctx.load_verify_locations(cafile, capath) - if cadata is not None: - self._ctx.load_verify_locations(BytesIO(cadata)) - - def load_cert_chain(self, certfile, keyfile=None, password=None): - self._ctx.use_certificate_file(certfile) - if password is not None: - self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password) - self._ctx.use_privatekey_file(keyfile or certfile) - - def wrap_socket(self, sock, server_side=False, - do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): - cnx = OpenSSL.SSL.Connection(self._ctx, sock) - - if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 - server_hostname = server_hostname.encode('utf-8') - - if server_hostname is not None: - cnx.set_tlsext_host_name(server_hostname) - - cnx.set_connect_state() - - while True: - try: - cnx.do_handshake() - except OpenSSL.SSL.WantReadError: - rd = util.wait_for_read(sock, sock.gettimeout()) - if not rd: - raise timeout('select timed out') - continue - except OpenSSL.SSL.Error as e: - raise ssl.SSLError('bad handshake: %r' % e) - break - - return WrappedSocket(cnx, sock) - - -def _verify_callback(cnx, x509, err_no, err_depth, return_code): - return err_no == 0 diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/socks.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/socks.py deleted file mode 100644 index 811e312..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/contrib/socks.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module contains provisional support for SOCKS proxies from within -urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and -SOCKS5. To enable its functionality, either install PySocks or install this -module with the ``socks`` extra. - -The SOCKS implementation supports the full range of urllib3 features. It also -supports the following SOCKS features: - -- SOCKS4 -- SOCKS4a -- SOCKS5 -- Usernames and passwords for the SOCKS proxy - -Known Limitations: - -- Currently PySocks does not support contacting remote websites via literal - IPv6 addresses. Any such connection attempt will fail. You must use a domain - name. -- Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any - such connection attempt will fail. -""" -from __future__ import absolute_import - -try: - import socks -except ImportError: - import warnings - from ..exceptions import DependencyWarning - - warnings.warn(( - 'SOCKS support in urllib3 requires the installation of optional ' - 'dependencies: specifically, PySocks. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies' - ), - DependencyWarning - ) - raise - -from socket import error as SocketError, timeout as SocketTimeout - -from ..connection import ( - HTTPConnection, HTTPSConnection -) -from ..connectionpool import ( - HTTPConnectionPool, HTTPSConnectionPool -) -from ..exceptions import ConnectTimeoutError, NewConnectionError -from ..poolmanager import PoolManager -from ..util.url import parse_url - -try: - import ssl -except ImportError: - ssl = None - - -class SOCKSConnection(HTTPConnection): - """ - A plain-text HTTP connection that connects via a SOCKS proxy. - """ - def __init__(self, *args, **kwargs): - self._socks_options = kwargs.pop('_socks_options') - super(SOCKSConnection, self).__init__(*args, **kwargs) - - def _new_conn(self): - """ - Establish a new connection via the SOCKS proxy. - """ - extra_kw = {} - if self.source_address: - extra_kw['source_address'] = self.source_address - - if self.socket_options: - extra_kw['socket_options'] = self.socket_options - - try: - conn = socks.create_connection( - (self.host, self.port), - proxy_type=self._socks_options['socks_version'], - proxy_addr=self._socks_options['proxy_host'], - proxy_port=self._socks_options['proxy_port'], - proxy_username=self._socks_options['username'], - proxy_password=self._socks_options['password'], - proxy_rdns=self._socks_options['rdns'], - timeout=self.timeout, - **extra_kw - ) - - except SocketTimeout as e: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) - - except socks.ProxyError as e: - # This is fragile as hell, but it seems to be the only way to raise - # useful errors here. - if e.socket_err: - error = e.socket_err - if isinstance(error, SocketTimeout): - raise ConnectTimeoutError( - self, - "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout) - ) - else: - raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % error - ) - else: - raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % e - ) - - except SocketError as e: # Defensive: PySocks should catch all these. - raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) - - return conn - - -# We don't need to duplicate the Verified/Unverified distinction from -# urllib3/connection.py here because the HTTPSConnection will already have been -# correctly set to either the Verified or Unverified form by that module. This -# means the SOCKSHTTPSConnection will automatically be the correct type. -class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): - pass - - -class SOCKSHTTPConnectionPool(HTTPConnectionPool): - ConnectionCls = SOCKSConnection - - -class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): - ConnectionCls = SOCKSHTTPSConnection - - -class SOCKSProxyManager(PoolManager): - """ - A version of the urllib3 ProxyManager that routes connections via the - defined SOCKS proxy. - """ - pool_classes_by_scheme = { - 'http': SOCKSHTTPConnectionPool, - 'https': SOCKSHTTPSConnectionPool, - } - - def __init__(self, proxy_url, username=None, password=None, - num_pools=10, headers=None, **connection_pool_kw): - parsed = parse_url(proxy_url) - - if username is None and password is None and parsed.auth is not None: - split = parsed.auth.split(':') - if len(split) == 2: - username, password = split - if parsed.scheme == 'socks5': - socks_version = socks.PROXY_TYPE_SOCKS5 - rdns = False - elif parsed.scheme == 'socks5h': - socks_version = socks.PROXY_TYPE_SOCKS5 - rdns = True - elif parsed.scheme == 'socks4': - socks_version = socks.PROXY_TYPE_SOCKS4 - rdns = False - elif parsed.scheme == 'socks4a': - socks_version = socks.PROXY_TYPE_SOCKS4 - rdns = True - else: - raise ValueError( - "Unable to determine SOCKS version from %s" % proxy_url - ) - - self.proxy_url = proxy_url - - socks_options = { - 'socks_version': socks_version, - 'proxy_host': parsed.host, - 'proxy_port': parsed.port, - 'username': username, - 'password': password, - 'rdns': rdns - } - connection_pool_kw['_socks_options'] = socks_options - - super(SOCKSProxyManager, self).__init__( - num_pools, headers, **connection_pool_kw - ) - - self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/exceptions.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/exceptions.py deleted file mode 100644 index 6c4be58..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/exceptions.py +++ /dev/null @@ -1,246 +0,0 @@ -from __future__ import absolute_import -from .packages.six.moves.http_client import ( - IncompleteRead as httplib_IncompleteRead -) -# Base Exceptions - - -class HTTPError(Exception): - "Base exception used by this module." - pass - - -class HTTPWarning(Warning): - "Base warning used by this module." - pass - - -class PoolError(HTTPError): - "Base exception for errors caused within a pool." - def __init__(self, pool, message): - self.pool = pool - HTTPError.__init__(self, "%s: %s" % (pool, message)) - - def __reduce__(self): - # For pickling purposes. - return self.__class__, (None, None) - - -class RequestError(PoolError): - "Base exception for PoolErrors that have associated URLs." - def __init__(self, pool, url, message): - self.url = url - PoolError.__init__(self, pool, message) - - def __reduce__(self): - # For pickling purposes. - return self.__class__, (None, self.url, None) - - -class SSLError(HTTPError): - "Raised when SSL certificate fails in an HTTPS connection." - pass - - -class ProxyError(HTTPError): - "Raised when the connection to a proxy fails." - pass - - -class DecodeError(HTTPError): - "Raised when automatic decoding based on Content-Type fails." - pass - - -class ProtocolError(HTTPError): - "Raised when something unexpected happens mid-request/response." - pass - - -#: Renamed to ProtocolError but aliased for backwards compatibility. -ConnectionError = ProtocolError - - -# Leaf Exceptions - -class MaxRetryError(RequestError): - """Raised when the maximum number of retries is exceeded. - - :param pool: The connection pool - :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` - :param string url: The requested Url - :param exceptions.Exception reason: The underlying error - - """ - - def __init__(self, pool, url, reason=None): - self.reason = reason - - message = "Max retries exceeded with url: %s (Caused by %r)" % ( - url, reason) - - RequestError.__init__(self, pool, url, message) - - -class HostChangedError(RequestError): - "Raised when an existing pool gets a request for a foreign host." - - def __init__(self, pool, url, retries=3): - message = "Tried to open a foreign host with url: %s" % url - RequestError.__init__(self, pool, url, message) - self.retries = retries - - -class TimeoutStateError(HTTPError): - """ Raised when passing an invalid state to a timeout """ - pass - - -class TimeoutError(HTTPError): - """ Raised when a socket timeout error occurs. - - Catching this error will catch both :exc:`ReadTimeoutErrors - ` and :exc:`ConnectTimeoutErrors `. - """ - pass - - -class ReadTimeoutError(TimeoutError, RequestError): - "Raised when a socket timeout occurs while receiving data from a server" - pass - - -# This timeout error does not have a URL attached and needs to inherit from the -# base HTTPError -class ConnectTimeoutError(TimeoutError): - "Raised when a socket timeout occurs while connecting to a server" - pass - - -class NewConnectionError(ConnectTimeoutError, PoolError): - "Raised when we fail to establish a new connection. Usually ECONNREFUSED." - pass - - -class EmptyPoolError(PoolError): - "Raised when a pool runs out of connections and no more are allowed." - pass - - -class ClosedPoolError(PoolError): - "Raised when a request enters a pool after the pool has been closed." - pass - - -class LocationValueError(ValueError, HTTPError): - "Raised when there is something wrong with a given URL input." - pass - - -class LocationParseError(LocationValueError): - "Raised when get_host or similar fails to parse the URL input." - - def __init__(self, location): - message = "Failed to parse: %s" % location - HTTPError.__init__(self, message) - - self.location = location - - -class ResponseError(HTTPError): - "Used as a container for an error reason supplied in a MaxRetryError." - GENERIC_ERROR = 'too many error responses' - SPECIFIC_ERROR = 'too many {status_code} error responses' - - -class SecurityWarning(HTTPWarning): - "Warned when perfoming security reducing actions" - pass - - -class SubjectAltNameWarning(SecurityWarning): - "Warned when connecting to a host with a certificate missing a SAN." - pass - - -class InsecureRequestWarning(SecurityWarning): - "Warned when making an unverified HTTPS request." - pass - - -class SystemTimeWarning(SecurityWarning): - "Warned when system time is suspected to be wrong" - pass - - -class InsecurePlatformWarning(SecurityWarning): - "Warned when certain SSL configuration is not available on a platform." - pass - - -class SNIMissingWarning(HTTPWarning): - "Warned when making a HTTPS request without SNI available." - pass - - -class DependencyWarning(HTTPWarning): - """ - Warned when an attempt is made to import a module with missing optional - dependencies. - """ - pass - - -class ResponseNotChunked(ProtocolError, ValueError): - "Response needs to be chunked in order to read it as chunks." - pass - - -class BodyNotHttplibCompatible(HTTPError): - """ - Body should be httplib.HTTPResponse like (have an fp attribute which - returns raw chunks) for read_chunked(). - """ - pass - - -class IncompleteRead(HTTPError, httplib_IncompleteRead): - """ - Response length doesn't match expected Content-Length - - Subclass of http_client.IncompleteRead to allow int value - for `partial` to avoid creating large objects on streamed - reads. - """ - def __init__(self, partial, expected): - super(IncompleteRead, self).__init__(partial, expected) - - def __repr__(self): - return ('IncompleteRead(%i bytes read, ' - '%i more expected)' % (self.partial, self.expected)) - - -class InvalidHeader(HTTPError): - "The header provided was somehow invalid." - pass - - -class ProxySchemeUnknown(AssertionError, ValueError): - "ProxyManager does not support the supplied scheme" - # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. - - def __init__(self, scheme): - message = "Not supported proxy scheme %s" % scheme - super(ProxySchemeUnknown, self).__init__(message) - - -class HeaderParsingError(HTTPError): - "Raised by assert_header_parsing, but we convert it to a log.warning statement." - def __init__(self, defects, unparsed_data): - message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data) - super(HeaderParsingError, self).__init__(message) - - -class UnrewindableBodyError(HTTPError): - "urllib3 encountered an error when trying to rewind a body" - pass diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/fields.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/fields.py deleted file mode 100644 index 19b0ae0..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/fields.py +++ /dev/null @@ -1,178 +0,0 @@ -from __future__ import absolute_import -import email.utils -import mimetypes - -from .packages import six - - -def guess_content_type(filename, default='application/octet-stream'): - """ - Guess the "Content-Type" of a file. - - :param filename: - The filename to guess the "Content-Type" of using :mod:`mimetypes`. - :param default: - If no "Content-Type" can be guessed, default to `default`. - """ - if filename: - return mimetypes.guess_type(filename)[0] or default - return default - - -def format_header_param(name, value): - """ - Helper function to format and quote a single header parameter. - - Particularly useful for header parameters which might contain - non-ASCII values, like file names. This follows RFC 2231, as - suggested by RFC 2388 Section 4.4. - - :param name: - The name of the parameter, a string expected to be ASCII only. - :param value: - The value of the parameter, provided as a unicode string. - """ - if not any(ch in value for ch in '"\\\r\n'): - result = '%s="%s"' % (name, value) - try: - result.encode('ascii') - except (UnicodeEncodeError, UnicodeDecodeError): - pass - else: - return result - if not six.PY3 and isinstance(value, six.text_type): # Python 2: - value = value.encode('utf-8') - value = email.utils.encode_rfc2231(value, 'utf-8') - value = '%s*=%s' % (name, value) - return value - - -class RequestField(object): - """ - A data container for request body parameters. - - :param name: - The name of this request field. - :param data: - The data/value body. - :param filename: - An optional filename of the request field. - :param headers: - An optional dict-like object of headers to initially use for the field. - """ - def __init__(self, name, data, filename=None, headers=None): - self._name = name - self._filename = filename - self.data = data - self.headers = {} - if headers: - self.headers = dict(headers) - - @classmethod - def from_tuples(cls, fieldname, value): - """ - A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. - - Supports constructing :class:`~urllib3.fields.RequestField` from - parameter of key/value strings AND key/filetuple. A filetuple is a - (filename, data, MIME type) tuple where the MIME type is optional. - For example:: - - 'foo': 'bar', - 'fakefile': ('foofile.txt', 'contents of foofile'), - 'realfile': ('barfile.txt', open('realfile').read()), - 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), - 'nonamefile': 'contents of nonamefile field', - - Field names and filenames must be unicode. - """ - if isinstance(value, tuple): - if len(value) == 3: - filename, data, content_type = value - else: - filename, data = value - content_type = guess_content_type(filename) - else: - filename = None - content_type = None - data = value - - request_param = cls(fieldname, data, filename=filename) - request_param.make_multipart(content_type=content_type) - - return request_param - - def _render_part(self, name, value): - """ - Overridable helper function to format a single header parameter. - - :param name: - The name of the parameter, a string expected to be ASCII only. - :param value: - The value of the parameter, provided as a unicode string. - """ - return format_header_param(name, value) - - def _render_parts(self, header_parts): - """ - Helper function to format and quote a single header. - - Useful for single headers that are composed of multiple items. E.g., - 'Content-Disposition' fields. - - :param header_parts: - A sequence of (k, v) typles or a :class:`dict` of (k, v) to format - as `k1="v1"; k2="v2"; ...`. - """ - parts = [] - iterable = header_parts - if isinstance(header_parts, dict): - iterable = header_parts.items() - - for name, value in iterable: - if value is not None: - parts.append(self._render_part(name, value)) - - return '; '.join(parts) - - def render_headers(self): - """ - Renders the headers for this request field. - """ - lines = [] - - sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] - for sort_key in sort_keys: - if self.headers.get(sort_key, False): - lines.append('%s: %s' % (sort_key, self.headers[sort_key])) - - for header_name, header_value in self.headers.items(): - if header_name not in sort_keys: - if header_value: - lines.append('%s: %s' % (header_name, header_value)) - - lines.append('\r\n') - return '\r\n'.join(lines) - - def make_multipart(self, content_disposition=None, content_type=None, - content_location=None): - """ - Makes this request field into a multipart request field. - - This method overrides "Content-Disposition", "Content-Type" and - "Content-Location" headers to the request parameter. - - :param content_type: - The 'Content-Type' of the request body. - :param content_location: - The 'Content-Location' of the request body. - - """ - self.headers['Content-Disposition'] = content_disposition or 'form-data' - self.headers['Content-Disposition'] += '; '.join([ - '', self._render_parts( - (('name', self._name), ('filename', self._filename)) - ) - ]) - self.headers['Content-Type'] = content_type - self.headers['Content-Location'] = content_location diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/filepost.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/filepost.py deleted file mode 100644 index cd11cee..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/filepost.py +++ /dev/null @@ -1,94 +0,0 @@ -from __future__ import absolute_import -import codecs - -from uuid import uuid4 -from io import BytesIO - -from .packages import six -from .packages.six import b -from .fields import RequestField - -writer = codecs.lookup('utf-8')[3] - - -def choose_boundary(): - """ - Our embarrassingly-simple replacement for mimetools.choose_boundary. - """ - return uuid4().hex - - -def iter_field_objects(fields): - """ - Iterate over fields. - - Supports list of (k, v) tuples and dicts, and lists of - :class:`~urllib3.fields.RequestField`. - - """ - if isinstance(fields, dict): - i = six.iteritems(fields) - else: - i = iter(fields) - - for field in i: - if isinstance(field, RequestField): - yield field - else: - yield RequestField.from_tuples(*field) - - -def iter_fields(fields): - """ - .. deprecated:: 1.6 - - Iterate over fields. - - The addition of :class:`~urllib3.fields.RequestField` makes this function - obsolete. Instead, use :func:`iter_field_objects`, which returns - :class:`~urllib3.fields.RequestField` objects. - - Supports list of (k, v) tuples and dicts. - """ - if isinstance(fields, dict): - return ((k, v) for k, v in six.iteritems(fields)) - - return ((k, v) for k, v in fields) - - -def encode_multipart_formdata(fields, boundary=None): - """ - Encode a dictionary of ``fields`` using the multipart/form-data MIME format. - - :param fields: - Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). - - :param boundary: - If not specified, then a random boundary will be generated using - :func:`mimetools.choose_boundary`. - """ - body = BytesIO() - if boundary is None: - boundary = choose_boundary() - - for field in iter_field_objects(fields): - body.write(b('--%s\r\n' % (boundary))) - - writer(body).write(field.render_headers()) - data = field.data - - if isinstance(data, int): - data = str(data) # Backwards compatibility - - if isinstance(data, six.text_type): - writer(body).write(data) - else: - body.write(data) - - body.write(b'\r\n') - - body.write(b('--%s--\r\n' % (boundary))) - - content_type = str('multipart/form-data; boundary=%s' % boundary) - - return body.getvalue(), content_type diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/__init__.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/__init__.py deleted file mode 100644 index 170e974..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import absolute_import - -from . import ssl_match_hostname - -__all__ = ('ssl_match_hostname', ) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/backports/__init__.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/backports/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/backports/makefile.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/backports/makefile.py deleted file mode 100644 index 75b80dc..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/backports/makefile.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -""" -backports.makefile -~~~~~~~~~~~~~~~~~~ - -Backports the Python 3 ``socket.makefile`` method for use with anything that -wants to create a "fake" socket object. -""" -import io - -from socket import SocketIO - - -def backport_makefile(self, mode="r", buffering=None, encoding=None, - errors=None, newline=None): - """ - Backport of ``socket.makefile`` from Python 3.5. - """ - if not set(mode) <= set(["r", "w", "b"]): - raise ValueError( - "invalid mode %r (only r, w, b allowed)" % (mode,) - ) - writing = "w" in mode - reading = "r" in mode or not writing - assert reading or writing - binary = "b" in mode - rawmode = "" - if reading: - rawmode += "r" - if writing: - rawmode += "w" - raw = SocketIO(self, rawmode) - self._makefile_refs += 1 - if buffering is None: - buffering = -1 - if buffering < 0: - buffering = io.DEFAULT_BUFFER_SIZE - if buffering == 0: - if not binary: - raise ValueError("unbuffered streams must be binary") - return raw - if reading and writing: - buffer = io.BufferedRWPair(raw, raw, buffering) - elif reading: - buffer = io.BufferedReader(raw, buffering) - else: - assert writing - buffer = io.BufferedWriter(raw, buffering) - if binary: - return buffer - text = io.TextIOWrapper(buffer, encoding, errors, newline) - text.mode = mode - return text diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ordered_dict.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ordered_dict.py deleted file mode 100644 index 4479363..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ordered_dict.py +++ /dev/null @@ -1,259 +0,0 @@ -# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. -# Passes Python2.7's test suite and incorporates all the latest updates. -# Copyright 2009 Raymond Hettinger, released under the MIT License. -# http://code.activestate.com/recipes/576693/ -try: - from thread import get_ident as _get_ident -except ImportError: - from dummy_thread import get_ident as _get_ident - -try: - from _abcoll import KeysView, ValuesView, ItemsView -except ImportError: - pass - - -class OrderedDict(dict): - 'Dictionary that remembers insertion order' - # An inherited dict maps keys to values. - # The inherited dict provides __getitem__, __len__, __contains__, and get. - # The remaining methods are order-aware. - # Big-O running times for all methods are the same as for regular dictionaries. - - # The internal self.__map dictionary maps keys to links in a doubly linked list. - # The circular doubly linked list starts and ends with a sentinel element. - # The sentinel element never gets deleted (this simplifies the algorithm). - # Each link is stored as a list of length three: [PREV, NEXT, KEY]. - - def __init__(self, *args, **kwds): - '''Initialize an ordered dictionary. Signature is the same as for - regular dictionaries, but keyword arguments are not recommended - because their insertion order is arbitrary. - - ''' - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__root - except AttributeError: - self.__root = root = [] # sentinel node - root[:] = [root, root, None] - self.__map = {} - self.__update(*args, **kwds) - - def __setitem__(self, key, value, dict_setitem=dict.__setitem__): - 'od.__setitem__(i, y) <==> od[i]=y' - # Setting a new item creates a new link which goes at the end of the linked - # list, and the inherited dictionary is updated with the new key/value pair. - if key not in self: - root = self.__root - last = root[0] - last[1] = root[0] = self.__map[key] = [last, root, key] - dict_setitem(self, key, value) - - def __delitem__(self, key, dict_delitem=dict.__delitem__): - 'od.__delitem__(y) <==> del od[y]' - # Deleting an existing item uses self.__map to find the link which is - # then removed by updating the links in the predecessor and successor nodes. - dict_delitem(self, key) - link_prev, link_next, key = self.__map.pop(key) - link_prev[1] = link_next - link_next[0] = link_prev - - def __iter__(self): - 'od.__iter__() <==> iter(od)' - root = self.__root - curr = root[1] - while curr is not root: - yield curr[2] - curr = curr[1] - - def __reversed__(self): - 'od.__reversed__() <==> reversed(od)' - root = self.__root - curr = root[0] - while curr is not root: - yield curr[2] - curr = curr[0] - - def clear(self): - 'od.clear() -> None. Remove all items from od.' - try: - for node in self.__map.itervalues(): - del node[:] - root = self.__root - root[:] = [root, root, None] - self.__map.clear() - except AttributeError: - pass - dict.clear(self) - - def popitem(self, last=True): - '''od.popitem() -> (k, v), return and remove a (key, value) pair. - Pairs are returned in LIFO order if last is true or FIFO order if false. - - ''' - if not self: - raise KeyError('dictionary is empty') - root = self.__root - if last: - link = root[0] - link_prev = link[0] - link_prev[1] = root - root[0] = link_prev - else: - link = root[1] - link_next = link[1] - root[1] = link_next - link_next[0] = root - key = link[2] - del self.__map[key] - value = dict.pop(self, key) - return key, value - - # -- the following methods do not depend on the internal structure -- - - def keys(self): - 'od.keys() -> list of keys in od' - return list(self) - - def values(self): - 'od.values() -> list of values in od' - return [self[key] for key in self] - - def items(self): - 'od.items() -> list of (key, value) pairs in od' - return [(key, self[key]) for key in self] - - def iterkeys(self): - 'od.iterkeys() -> an iterator over the keys in od' - return iter(self) - - def itervalues(self): - 'od.itervalues -> an iterator over the values in od' - for k in self: - yield self[k] - - def iteritems(self): - 'od.iteritems -> an iterator over the (key, value) items in od' - for k in self: - yield (k, self[k]) - - def update(*args, **kwds): - '''od.update(E, **F) -> None. Update od from dict/iterable E and F. - - If E is a dict instance, does: for k in E: od[k] = E[k] - If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] - Or if E is an iterable of items, does: for k, v in E: od[k] = v - In either case, this is followed by: for k, v in F.items(): od[k] = v - - ''' - if len(args) > 2: - raise TypeError('update() takes at most 2 positional ' - 'arguments (%d given)' % (len(args),)) - elif not args: - raise TypeError('update() takes at least 1 argument (0 given)') - self = args[0] - # Make progressively weaker assumptions about "other" - other = () - if len(args) == 2: - other = args[1] - if isinstance(other, dict): - for key in other: - self[key] = other[key] - elif hasattr(other, 'keys'): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value - for key, value in kwds.items(): - self[key] = value - - __update = update # let subclasses override update without breaking __init__ - - __marker = object() - - def pop(self, key, default=__marker): - '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - - ''' - if key in self: - result = self[key] - del self[key] - return result - if default is self.__marker: - raise KeyError(key) - return default - - def setdefault(self, key, default=None): - 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' - if key in self: - return self[key] - self[key] = default - return default - - def __repr__(self, _repr_running={}): - 'od.__repr__() <==> repr(od)' - call_key = id(self), _get_ident() - if call_key in _repr_running: - return '...' - _repr_running[call_key] = 1 - try: - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - finally: - del _repr_running[call_key] - - def __reduce__(self): - 'Return state information for pickling' - items = [[k, self[k]] for k in self] - inst_dict = vars(self).copy() - for k in vars(OrderedDict()): - inst_dict.pop(k, None) - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def copy(self): - 'od.copy() -> a shallow copy of od' - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S - and values equal to v (which defaults to None). - - ''' - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive - while comparison to a regular mapping is order-insensitive. - - ''' - if isinstance(other, OrderedDict): - return len(self)==len(other) and self.items() == other.items() - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other - - # -- the following methods are only used in Python 2.7 -- - - def viewkeys(self): - "od.viewkeys() -> a set-like object providing a view on od's keys" - return KeysView(self) - - def viewvalues(self): - "od.viewvalues() -> an object providing a view on od's values" - return ValuesView(self) - - def viewitems(self): - "od.viewitems() -> a set-like object providing a view on od's items" - return ItemsView(self) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/six.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/six.py deleted file mode 100644 index 190c023..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/six.py +++ /dev/null @@ -1,868 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -# Copyright (c) 2010-2015 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson " -__version__ = "1.10.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - if from_value is None: - raise value - raise value from from_value -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - raise value from from_value -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ssl_match_hostname/__init__.p b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ssl_match_hostname/__init__.p deleted file mode 100644 index d6594eb..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ssl_match_hostname/__init__.p +++ /dev/null @@ -1,19 +0,0 @@ -import sys - -try: - # Our match_hostname function is the same as 3.5's, so we only want to - # import the match_hostname function if it's at least that good. - if sys.version_info < (3, 5): - raise ImportError("Fallback to vendored code") - - from ssl import CertificateError, match_hostname -except ImportError: - try: - # Backport of the function from a pypi module - from backports.ssl_match_hostname import CertificateError, match_hostname - except ImportError: - # Our vendored copy - from ._implementation import CertificateError, match_hostname - -# Not needed, but documenting what we provide. -__all__ = ('CertificateError', 'match_hostname') diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ssl_match_hostname/_implement b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ssl_match_hostname/_implement deleted file mode 100644 index 1fd42f3..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/packages/ssl_match_hostname/_implement +++ /dev/null @@ -1,157 +0,0 @@ -"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" - -# Note: This file is under the PSF license as the code comes from the python -# stdlib. http://docs.python.org/3/license.html - -import re -import sys - -# ipaddress has been backported to 2.6+ in pypi. If it is installed on the -# system, use it to handle IPAddress ServerAltnames (this was added in -# python-3.5) otherwise only do DNS matching. This allows -# backports.ssl_match_hostname to continue to be used all the way back to -# python-2.4. -try: - import ipaddress -except ImportError: - ipaddress = None - -__version__ = '3.5.0.1' - - -class CertificateError(ValueError): - pass - - -def _dnsname_match(dn, hostname, max_wildcards=1): - """Matching according to RFC 6125, section 6.4.3 - - http://tools.ietf.org/html/rfc6125#section-6.4.3 - """ - pats = [] - if not dn: - return False - - # Ported from python3-syntax: - # leftmost, *remainder = dn.split(r'.') - parts = dn.split(r'.') - leftmost = parts[0] - remainder = parts[1:] - - wildcards = leftmost.count('*') - if wildcards > max_wildcards: - # Issue #17980: avoid denials of service by refusing more - # than one wildcard per fragment. A survey of established - # policy among SSL implementations showed it to be a - # reasonable choice. - raise CertificateError( - "too many wildcards in certificate DNS name: " + repr(dn)) - - # speed up common case w/o wildcards - if not wildcards: - return dn.lower() == hostname.lower() - - # RFC 6125, section 6.4.3, subitem 1. - # The client SHOULD NOT attempt to match a presented identifier in which - # the wildcard character comprises a label other than the left-most label. - if leftmost == '*': - # When '*' is a fragment by itself, it matches a non-empty dotless - # fragment. - pats.append('[^.]+') - elif leftmost.startswith('xn--') or hostname.startswith('xn--'): - # RFC 6125, section 6.4.3, subitem 3. - # The client SHOULD NOT attempt to match a presented identifier - # where the wildcard character is embedded within an A-label or - # U-label of an internationalized domain name. - pats.append(re.escape(leftmost)) - else: - # Otherwise, '*' matches any dotless string, e.g. www* - pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) - - # add the remaining fragments, ignore any wildcards - for frag in remainder: - pats.append(re.escape(frag)) - - pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) - return pat.match(hostname) - - -def _to_unicode(obj): - if isinstance(obj, str) and sys.version_info < (3,): - obj = unicode(obj, encoding='ascii', errors='strict') - return obj - -def _ipaddress_match(ipname, host_ip): - """Exact matching of IP addresses. - - RFC 6125 explicitly doesn't define an algorithm for this - (section 1.7.2 - "Out of Scope"). - """ - # OpenSSL may add a trailing newline to a subjectAltName's IP address - # Divergence from upstream: ipaddress can't handle byte str - ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) - return ip == host_ip - - -def match_hostname(cert, hostname): - """Verify that *cert* (in decoded format as returned by - SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 - rules are followed, but IP addresses are not accepted for *hostname*. - - CertificateError is raised on failure. On success, the function - returns nothing. - """ - if not cert: - raise ValueError("empty or no certificate, match_hostname needs a " - "SSL socket or SSL context with either " - "CERT_OPTIONAL or CERT_REQUIRED") - try: - # Divergence from upstream: ipaddress can't handle byte str - host_ip = ipaddress.ip_address(_to_unicode(hostname)) - except ValueError: - # Not an IP address (common case) - host_ip = None - except UnicodeError: - # Divergence from upstream: Have to deal with ipaddress not taking - # byte strings. addresses should be all ascii, so we consider it not - # an ipaddress in this case - host_ip = None - except AttributeError: - # Divergence from upstream: Make ipaddress library optional - if ipaddress is None: - host_ip = None - else: - raise - dnsnames = [] - san = cert.get('subjectAltName', ()) - for key, value in san: - if key == 'DNS': - if host_ip is None and _dnsname_match(value, hostname): - return - dnsnames.append(value) - elif key == 'IP Address': - if host_ip is not None and _ipaddress_match(value, host_ip): - return - dnsnames.append(value) - if not dnsnames: - # The subject is only checked when there is no dNSName entry - # in subjectAltName - for sub in cert.get('subject', ()): - for key, value in sub: - # XXX according to RFC 2818, the most specific Common Name - # must be used. - if key == 'commonName': - if _dnsname_match(value, hostname): - return - dnsnames.append(value) - if len(dnsnames) > 1: - raise CertificateError("hostname %r " - "doesn't match either of %s" - % (hostname, ', '.join(map(repr, dnsnames)))) - elif len(dnsnames) == 1: - raise CertificateError("hostname %r " - "doesn't match %r" - % (hostname, dnsnames[0])) - else: - raise CertificateError("no appropriate commonName or " - "subjectAltName fields were found") diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/poolmanager.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/poolmanager.py deleted file mode 100644 index cc5a00e..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/poolmanager.py +++ /dev/null @@ -1,363 +0,0 @@ -from __future__ import absolute_import -import collections -import functools -import logging - -from ._collections import RecentlyUsedContainer -from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool -from .connectionpool import port_by_scheme -from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown -from .packages.six.moves.urllib.parse import urljoin -from .request import RequestMethods -from .util.url import parse_url -from .util.retry import Retry - - -__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] - - -log = logging.getLogger(__name__) - -SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', - 'ssl_version', 'ca_cert_dir', 'ssl_context') - -# The base fields to use when determining what pool to get a connection from; -# these do not rely on the ``connection_pool_kw`` and can be determined by the -# URL and potentially the ``urllib3.connection.port_by_scheme`` dictionary. -# -# All custom key schemes should include the fields in this key at a minimum. -BasePoolKey = collections.namedtuple('BasePoolKey', ('scheme', 'host', 'port')) - -# The fields to use when determining what pool to get a HTTP and HTTPS -# connection from. All additional fields must be present in the PoolManager's -# ``connection_pool_kw`` instance variable. -HTTPPoolKey = collections.namedtuple( - 'HTTPPoolKey', BasePoolKey._fields + ('timeout', 'retries', 'strict', - 'block', 'source_address') -) -HTTPSPoolKey = collections.namedtuple( - 'HTTPSPoolKey', HTTPPoolKey._fields + SSL_KEYWORDS -) - - -def _default_key_normalizer(key_class, request_context): - """ - Create a pool key of type ``key_class`` for a request. - - According to RFC 3986, both the scheme and host are case-insensitive. - Therefore, this function normalizes both before constructing the pool - key for an HTTPS request. If you wish to change this behaviour, provide - alternate callables to ``key_fn_by_scheme``. - - :param key_class: - The class to use when constructing the key. This should be a namedtuple - with the ``scheme`` and ``host`` keys at a minimum. - - :param request_context: - A dictionary-like object that contain the context for a request. - It should contain a key for each field in the :class:`HTTPPoolKey` - """ - context = {} - for key in key_class._fields: - context[key] = request_context.get(key) - context['scheme'] = context['scheme'].lower() - context['host'] = context['host'].lower() - return key_class(**context) - - -# A dictionary that maps a scheme to a callable that creates a pool key. -# This can be used to alter the way pool keys are constructed, if desired. -# Each PoolManager makes a copy of this dictionary so they can be configured -# globally here, or individually on the instance. -key_fn_by_scheme = { - 'http': functools.partial(_default_key_normalizer, HTTPPoolKey), - 'https': functools.partial(_default_key_normalizer, HTTPSPoolKey), -} - -pool_classes_by_scheme = { - 'http': HTTPConnectionPool, - 'https': HTTPSConnectionPool, -} - - -class PoolManager(RequestMethods): - """ - Allows for arbitrary requests while transparently keeping track of - necessary connection pools for you. - - :param num_pools: - Number of connection pools to cache before discarding the least - recently used pool. - - :param headers: - Headers to include with all requests, unless other headers are given - explicitly. - - :param \\**connection_pool_kw: - Additional parameters are used to create fresh - :class:`urllib3.connectionpool.ConnectionPool` instances. - - Example:: - - >>> manager = PoolManager(num_pools=2) - >>> r = manager.request('GET', 'http://google.com/') - >>> r = manager.request('GET', 'http://google.com/mail') - >>> r = manager.request('GET', 'http://yahoo.com/') - >>> len(manager.pools) - 2 - - """ - - proxy = None - - def __init__(self, num_pools=10, headers=None, **connection_pool_kw): - RequestMethods.__init__(self, headers) - self.connection_pool_kw = connection_pool_kw - self.pools = RecentlyUsedContainer(num_pools, - dispose_func=lambda p: p.close()) - - # Locally set the pool classes and keys so other PoolManagers can - # override them. - self.pool_classes_by_scheme = pool_classes_by_scheme - self.key_fn_by_scheme = key_fn_by_scheme.copy() - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.clear() - # Return False to re-raise any potential exceptions - return False - - def _new_pool(self, scheme, host, port): - """ - Create a new :class:`ConnectionPool` based on host, port and scheme. - - This method is used to actually create the connection pools handed out - by :meth:`connection_from_url` and companion methods. It is intended - to be overridden for customization. - """ - pool_cls = self.pool_classes_by_scheme[scheme] - kwargs = self.connection_pool_kw - if scheme == 'http': - kwargs = self.connection_pool_kw.copy() - for kw in SSL_KEYWORDS: - kwargs.pop(kw, None) - - return pool_cls(host, port, **kwargs) - - def clear(self): - """ - Empty our store of pools and direct them all to close. - - This will not affect in-flight connections, but they will not be - re-used after completion. - """ - self.pools.clear() - - def connection_from_host(self, host, port=None, scheme='http'): - """ - Get a :class:`ConnectionPool` based on the host, port, and scheme. - - If ``port`` isn't given, it will be derived from the ``scheme`` using - ``urllib3.connectionpool.port_by_scheme``. - """ - - if not host: - raise LocationValueError("No host specified.") - - request_context = self.connection_pool_kw.copy() - request_context['scheme'] = scheme or 'http' - if not port: - port = port_by_scheme.get(request_context['scheme'].lower(), 80) - request_context['port'] = port - request_context['host'] = host - - return self.connection_from_context(request_context) - - def connection_from_context(self, request_context): - """ - Get a :class:`ConnectionPool` based on the request context. - - ``request_context`` must at least contain the ``scheme`` key and its - value must be a key in ``key_fn_by_scheme`` instance variable. - """ - scheme = request_context['scheme'].lower() - pool_key_constructor = self.key_fn_by_scheme[scheme] - pool_key = pool_key_constructor(request_context) - - return self.connection_from_pool_key(pool_key) - - def connection_from_pool_key(self, pool_key): - """ - Get a :class:`ConnectionPool` based on the provided pool key. - - ``pool_key`` should be a namedtuple that only contains immutable - objects. At a minimum it must have the ``scheme``, ``host``, and - ``port`` fields. - """ - with self.pools.lock: - # If the scheme, host, or port doesn't match existing open - # connections, open a new ConnectionPool. - pool = self.pools.get(pool_key) - if pool: - return pool - - # Make a fresh ConnectionPool of the desired type - pool = self._new_pool(pool_key.scheme, pool_key.host, pool_key.port) - self.pools[pool_key] = pool - - return pool - - def connection_from_url(self, url): - """ - Similar to :func:`urllib3.connectionpool.connection_from_url` but - doesn't pass any additional parameters to the - :class:`urllib3.connectionpool.ConnectionPool` constructor. - - Additional parameters are taken from the :class:`.PoolManager` - constructor. - """ - u = parse_url(url) - return self.connection_from_host(u.host, port=u.port, scheme=u.scheme) - - def urlopen(self, method, url, redirect=True, **kw): - """ - Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` - with custom cross-host redirect logic and only sends the request-uri - portion of the ``url``. - - The given ``url`` parameter must be absolute, such that an appropriate - :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. - """ - u = parse_url(url) - conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) - - kw['assert_same_host'] = False - kw['redirect'] = False - if 'headers' not in kw: - kw['headers'] = self.headers - - if self.proxy is not None and u.scheme == "http": - response = conn.urlopen(method, url, **kw) - else: - response = conn.urlopen(method, u.request_uri, **kw) - - redirect_location = redirect and response.get_redirect_location() - if not redirect_location: - return response - - # Support relative URLs for redirecting. - redirect_location = urljoin(url, redirect_location) - - # RFC 7231, Section 6.4.4 - if response.status == 303: - method = 'GET' - - retries = kw.get('retries') - if not isinstance(retries, Retry): - retries = Retry.from_int(retries, redirect=redirect) - - try: - retries = retries.increment(method, url, response=response, _pool=conn) - except MaxRetryError: - if retries.raise_on_redirect: - raise - return response - - kw['retries'] = retries - kw['redirect'] = redirect - - log.info("Redirecting %s -> %s", url, redirect_location) - return self.urlopen(method, redirect_location, **kw) - - -class ProxyManager(PoolManager): - """ - Behaves just like :class:`PoolManager`, but sends all requests through - the defined proxy, using the CONNECT method for HTTPS URLs. - - :param proxy_url: - The URL of the proxy to be used. - - :param proxy_headers: - A dictionary contaning headers that will be sent to the proxy. In case - of HTTP they are being sent with each request, while in the - HTTPS/CONNECT case they are sent only once. Could be used for proxy - authentication. - - Example: - >>> proxy = urllib3.ProxyManager('http://localhost:3128/') - >>> r1 = proxy.request('GET', 'http://google.com/') - >>> r2 = proxy.request('GET', 'http://httpbin.org/') - >>> len(proxy.pools) - 1 - >>> r3 = proxy.request('GET', 'https://httpbin.org/') - >>> r4 = proxy.request('GET', 'https://twitter.com/') - >>> len(proxy.pools) - 3 - - """ - - def __init__(self, proxy_url, num_pools=10, headers=None, - proxy_headers=None, **connection_pool_kw): - - if isinstance(proxy_url, HTTPConnectionPool): - proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host, - proxy_url.port) - proxy = parse_url(proxy_url) - if not proxy.port: - port = port_by_scheme.get(proxy.scheme, 80) - proxy = proxy._replace(port=port) - - if proxy.scheme not in ("http", "https"): - raise ProxySchemeUnknown(proxy.scheme) - - self.proxy = proxy - self.proxy_headers = proxy_headers or {} - - connection_pool_kw['_proxy'] = self.proxy - connection_pool_kw['_proxy_headers'] = self.proxy_headers - - super(ProxyManager, self).__init__( - num_pools, headers, **connection_pool_kw) - - def connection_from_host(self, host, port=None, scheme='http'): - if scheme == "https": - return super(ProxyManager, self).connection_from_host( - host, port, scheme) - - return super(ProxyManager, self).connection_from_host( - self.proxy.host, self.proxy.port, self.proxy.scheme) - - def _set_proxy_headers(self, url, headers=None): - """ - Sets headers needed by proxies: specifically, the Accept and Host - headers. Only sets headers not provided by the user. - """ - headers_ = {'Accept': '*/*'} - - netloc = parse_url(url).netloc - if netloc: - headers_['Host'] = netloc - - if headers: - headers_.update(headers) - return headers_ - - def urlopen(self, method, url, redirect=True, **kw): - "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." - u = parse_url(url) - - if u.scheme == "http": - # For proxied HTTPS requests, httplib sets the necessary headers - # on the CONNECT to the proxy. For HTTP, we'll definitely - # need to set 'Host' at the very least. - headers = kw.get('headers', self.headers) - kw['headers'] = self._set_proxy_headers(url, headers) - - return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) - - -def proxy_from_url(url, **kw): - return ProxyManager(proxy_url=url, **kw) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/request.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/request.py deleted file mode 100644 index c0fddff..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/request.py +++ /dev/null @@ -1,148 +0,0 @@ -from __future__ import absolute_import - -from .filepost import encode_multipart_formdata -from .packages.six.moves.urllib.parse import urlencode - - -__all__ = ['RequestMethods'] - - -class RequestMethods(object): - """ - Convenience mixin for classes who implement a :meth:`urlopen` method, such - as :class:`~urllib3.connectionpool.HTTPConnectionPool` and - :class:`~urllib3.poolmanager.PoolManager`. - - Provides behavior for making common types of HTTP request methods and - decides which type of request field encoding to use. - - Specifically, - - :meth:`.request_encode_url` is for sending requests whose fields are - encoded in the URL (such as GET, HEAD, DELETE). - - :meth:`.request_encode_body` is for sending requests whose fields are - encoded in the *body* of the request using multipart or www-form-urlencoded - (such as for POST, PUT, PATCH). - - :meth:`.request` is for making any kind of request, it will look up the - appropriate encoding format and use one of the above two methods to make - the request. - - Initializer parameters: - - :param headers: - Headers to include with all requests, unless other headers are given - explicitly. - """ - - _encode_url_methods = set(['DELETE', 'GET', 'HEAD', 'OPTIONS']) - - def __init__(self, headers=None): - self.headers = headers or {} - - def urlopen(self, method, url, body=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **kw): # Abstract - raise NotImplemented("Classes extending RequestMethods must implement " - "their own ``urlopen`` method.") - - def request(self, method, url, fields=None, headers=None, **urlopen_kw): - """ - Make a request using :meth:`urlopen` with the appropriate encoding of - ``fields`` based on the ``method`` used. - - This is a convenience method that requires the least amount of manual - effort. It can be used in most situations, while still having the - option to drop down to more specific methods when necessary, such as - :meth:`request_encode_url`, :meth:`request_encode_body`, - or even the lowest level :meth:`urlopen`. - """ - method = method.upper() - - if method in self._encode_url_methods: - return self.request_encode_url(method, url, fields=fields, - headers=headers, - **urlopen_kw) - else: - return self.request_encode_body(method, url, fields=fields, - headers=headers, - **urlopen_kw) - - def request_encode_url(self, method, url, fields=None, headers=None, - **urlopen_kw): - """ - Make a request using :meth:`urlopen` with the ``fields`` encoded in - the url. This is useful for request methods like GET, HEAD, DELETE, etc. - """ - if headers is None: - headers = self.headers - - extra_kw = {'headers': headers} - extra_kw.update(urlopen_kw) - - if fields: - url += '?' + urlencode(fields) - - return self.urlopen(method, url, **extra_kw) - - def request_encode_body(self, method, url, fields=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **urlopen_kw): - """ - Make a request using :meth:`urlopen` with the ``fields`` encoded in - the body. This is useful for request methods like POST, PUT, PATCH, etc. - - When ``encode_multipart=True`` (default), then - :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode - the payload with the appropriate content type. Otherwise - :meth:`urllib.urlencode` is used with the - 'application/x-www-form-urlencoded' content type. - - Multipart encoding must be used when posting files, and it's reasonably - safe to use it in other times too. However, it may break request - signing, such as with OAuth. - - Supports an optional ``fields`` parameter of key/value strings AND - key/filetuple. A filetuple is a (filename, data, MIME type) tuple where - the MIME type is optional. For example:: - - fields = { - 'foo': 'bar', - 'fakefile': ('foofile.txt', 'contents of foofile'), - 'realfile': ('barfile.txt', open('realfile').read()), - 'typedfile': ('bazfile.bin', open('bazfile').read(), - 'image/jpeg'), - 'nonamefile': 'contents of nonamefile field', - } - - When uploading a file, providing a filename (the first parameter of the - tuple) is optional but recommended to best mimick behavior of browsers. - - Note that if ``headers`` are supplied, the 'Content-Type' header will - be overwritten because it depends on the dynamic random boundary string - which is used to compose the body of the request. The random boundary - string can be explicitly set with the ``multipart_boundary`` parameter. - """ - if headers is None: - headers = self.headers - - extra_kw = {'headers': {}} - - if fields: - if 'body' in urlopen_kw: - raise TypeError( - "request got values for both 'fields' and 'body', can only specify one.") - - if encode_multipart: - body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) - else: - body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' - - extra_kw['body'] = body - extra_kw['headers'] = {'Content-Type': content_type} - - extra_kw['headers'].update(headers) - extra_kw.update(urlopen_kw) - - return self.urlopen(method, url, **extra_kw) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/response.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/response.py deleted file mode 100644 index 6f1b63c..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/response.py +++ /dev/null @@ -1,618 +0,0 @@ -from __future__ import absolute_import -from contextlib import contextmanager -import zlib -import io -import logging -from socket import timeout as SocketTimeout -from socket import error as SocketError - -from ._collections import HTTPHeaderDict -from .exceptions import ( - BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, - ResponseNotChunked, IncompleteRead, InvalidHeader -) -from .packages.six import string_types as basestring, binary_type, PY3 -from .packages.six.moves import http_client as httplib -from .connection import HTTPException, BaseSSLError -from .util.response import is_fp_closed, is_response_to_head - -log = logging.getLogger(__name__) - - -class DeflateDecoder(object): - - def __init__(self): - self._first_try = True - self._data = binary_type() - self._obj = zlib.decompressobj() - - def __getattr__(self, name): - return getattr(self._obj, name) - - def decompress(self, data): - if not data: - return data - - if not self._first_try: - return self._obj.decompress(data) - - self._data += data - try: - return self._obj.decompress(data) - except zlib.error: - self._first_try = False - self._obj = zlib.decompressobj(-zlib.MAX_WBITS) - try: - return self.decompress(self._data) - finally: - self._data = None - - -class GzipDecoder(object): - - def __init__(self): - self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) - - def __getattr__(self, name): - return getattr(self._obj, name) - - def decompress(self, data): - if not data: - return data - return self._obj.decompress(data) - - -def _get_decoder(mode): - if mode == 'gzip': - return GzipDecoder() - - return DeflateDecoder() - - -class HTTPResponse(io.IOBase): - """ - HTTP Response container. - - Backwards-compatible to httplib's HTTPResponse but the response ``body`` is - loaded and decoded on-demand when the ``data`` property is accessed. This - class is also compatible with the Python standard library's :mod:`io` - module, and can hence be treated as a readable object in the context of that - framework. - - Extra parameters for behaviour not present in httplib.HTTPResponse: - - :param preload_content: - If True, the response's body will be preloaded during construction. - - :param decode_content: - If True, attempts to decode specific content-encoding's based on headers - (like 'gzip' and 'deflate') will be skipped and raw data will be used - instead. - - :param original_response: - When this HTTPResponse wrapper is generated from an httplib.HTTPResponse - object, it's convenient to include the original for debug purposes. It's - otherwise unused. - - :param retries: - The retries contains the last :class:`~urllib3.util.retry.Retry` that - was used during the request. - - :param enforce_content_length: - Enforce content length checking. Body returned by server must match - value of Content-Length header, if present. Otherwise, raise error. - """ - - CONTENT_DECODERS = ['gzip', 'deflate'] - REDIRECT_STATUSES = [301, 302, 303, 307, 308] - - def __init__(self, body='', headers=None, status=0, version=0, reason=None, - strict=0, preload_content=True, decode_content=True, - original_response=None, pool=None, connection=None, - retries=None, enforce_content_length=False, request_method=None): - - if isinstance(headers, HTTPHeaderDict): - self.headers = headers - else: - self.headers = HTTPHeaderDict(headers) - self.status = status - self.version = version - self.reason = reason - self.strict = strict - self.decode_content = decode_content - self.retries = retries - self.enforce_content_length = enforce_content_length - - self._decoder = None - self._body = None - self._fp = None - self._original_response = original_response - self._fp_bytes_read = 0 - - if body and isinstance(body, (basestring, binary_type)): - self._body = body - - self._pool = pool - self._connection = connection - - if hasattr(body, 'read'): - self._fp = body - - # Are we using the chunked-style of transfer encoding? - self.chunked = False - self.chunk_left = None - tr_enc = self.headers.get('transfer-encoding', '').lower() - # Don't incur the penalty of creating a list and then discarding it - encodings = (enc.strip() for enc in tr_enc.split(",")) - if "chunked" in encodings: - self.chunked = True - - # Determine length of response - self.length_remaining = self._init_length(request_method) - - # If requested, preload the body. - if preload_content and not self._body: - self._body = self.read(decode_content=decode_content) - - def get_redirect_location(self): - """ - Should we redirect and where to? - - :returns: Truthy redirect location string if we got a redirect status - code and valid location. ``None`` if redirect status and no - location. ``False`` if not a redirect status code. - """ - if self.status in self.REDIRECT_STATUSES: - return self.headers.get('location') - - return False - - def release_conn(self): - if not self._pool or not self._connection: - return - - self._pool._put_conn(self._connection) - self._connection = None - - @property - def data(self): - # For backwords-compat with earlier urllib3 0.4 and earlier. - if self._body: - return self._body - - if self._fp: - return self.read(cache_content=True) - - @property - def connection(self): - return self._connection - - def tell(self): - """ - Obtain the number of bytes pulled over the wire so far. May differ from - the amount of content returned by :meth:``HTTPResponse.read`` if bytes - are encoded on the wire (e.g, compressed). - """ - return self._fp_bytes_read - - def _init_length(self, request_method): - """ - Set initial length value for Response content if available. - """ - length = self.headers.get('content-length') - - if length is not None and self.chunked: - # This Response will fail with an IncompleteRead if it can't be - # received as chunked. This method falls back to attempt reading - # the response before raising an exception. - log.warning("Received response with both Content-Length and " - "Transfer-Encoding set. This is expressly forbidden " - "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " - "attempting to process response as Transfer-Encoding: " - "chunked.") - return None - - elif length is not None: - try: - # RFC 7230 section 3.3.2 specifies multiple content lengths can - # be sent in a single Content-Length header - # (e.g. Content-Length: 42, 42). This line ensures the values - # are all valid ints and that as long as the `set` length is 1, - # all values are the same. Otherwise, the header is invalid. - lengths = set([int(val) for val in length.split(',')]) - if len(lengths) > 1: - raise InvalidHeader("Content-Length contained multiple " - "unmatching values (%s)" % length) - length = lengths.pop() - except ValueError: - length = None - else: - if length < 0: - length = None - - # Convert status to int for comparison - # In some cases, httplib returns a status of "_UNKNOWN" - try: - status = int(self.status) - except ValueError: - status = 0 - - # Check for responses that shouldn't include a body - if status in (204, 304) or 100 <= status < 200 or request_method == 'HEAD': - length = 0 - - return length - - def _init_decoder(self): - """ - Set-up the _decoder attribute if necessary. - """ - # Note: content-encoding value should be case-insensitive, per RFC 7230 - # Section 3.2 - content_encoding = self.headers.get('content-encoding', '').lower() - if self._decoder is None and content_encoding in self.CONTENT_DECODERS: - self._decoder = _get_decoder(content_encoding) - - def _decode(self, data, decode_content, flush_decoder): - """ - Decode the data passed in and potentially flush the decoder. - """ - try: - if decode_content and self._decoder: - data = self._decoder.decompress(data) - except (IOError, zlib.error) as e: - content_encoding = self.headers.get('content-encoding', '').lower() - raise DecodeError( - "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, e) - - if flush_decoder and decode_content: - data += self._flush_decoder() - - return data - - def _flush_decoder(self): - """ - Flushes the decoder. Should only be called if the decoder is actually - being used. - """ - if self._decoder: - buf = self._decoder.decompress(b'') - return buf + self._decoder.flush() - - return b'' - - @contextmanager - def _error_catcher(self): - """ - Catch low-level python exceptions, instead re-raising urllib3 - variants, so that low-level exceptions are not leaked in the - high-level api. - - On exit, release the connection back to the pool. - """ - clean_exit = False - - try: - try: - yield - - except SocketTimeout: - # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but - # there is yet no clean way to get at it from this context. - raise ReadTimeoutError(self._pool, None, 'Read timed out.') - - except BaseSSLError as e: - # FIXME: Is there a better way to differentiate between SSLErrors? - if 'read operation timed out' not in str(e): # Defensive: - # This shouldn't happen but just in case we're missing an edge - # case, let's avoid swallowing SSL errors. - raise - - raise ReadTimeoutError(self._pool, None, 'Read timed out.') - - except (HTTPException, SocketError) as e: - # This includes IncompleteRead. - raise ProtocolError('Connection broken: %r' % e, e) - - # If no exception is thrown, we should avoid cleaning up - # unnecessarily. - clean_exit = True - finally: - # If we didn't terminate cleanly, we need to throw away our - # connection. - if not clean_exit: - # The response may not be closed but we're not going to use it - # anymore so close it now to ensure that the connection is - # released back to the pool. - if self._original_response: - self._original_response.close() - - # Closing the response may not actually be sufficient to close - # everything, so if we have a hold of the connection close that - # too. - if self._connection: - self._connection.close() - - # If we hold the original response but it's closed now, we should - # return the connection back to the pool. - if self._original_response and self._original_response.isclosed(): - self.release_conn() - - def read(self, amt=None, decode_content=None, cache_content=False): - """ - Similar to :meth:`httplib.HTTPResponse.read`, but with two additional - parameters: ``decode_content`` and ``cache_content``. - - :param amt: - How much of the content to read. If specified, caching is skipped - because it doesn't make sense to cache partial content as the full - response. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - - :param cache_content: - If True, will save the returned data such that the same result is - returned despite of the state of the underlying file object. This - is useful if you want the ``.data`` property to continue working - after having ``.read()`` the file object. (Overridden if ``amt`` is - set.) - """ - self._init_decoder() - if decode_content is None: - decode_content = self.decode_content - - if self._fp is None: - return - - flush_decoder = False - data = None - - with self._error_catcher(): - if amt is None: - # cStringIO doesn't like amt=None - data = self._fp.read() - flush_decoder = True - else: - cache_content = False - data = self._fp.read(amt) - if amt != 0 and not data: # Platform-specific: Buggy versions of Python. - # Close the connection when no data is returned - # - # This is redundant to what httplib/http.client _should_ - # already do. However, versions of python released before - # December 15, 2012 (http://bugs.python.org/issue16298) do - # not properly close the connection in all cases. There is - # no harm in redundantly calling close. - self._fp.close() - flush_decoder = True - if self.enforce_content_length and self.length_remaining not in (0, None): - # This is an edge case that httplib failed to cover due - # to concerns of backward compatibility. We're - # addressing it here to make sure IncompleteRead is - # raised during streaming, so all calls with incorrect - # Content-Length are caught. - raise IncompleteRead(self._fp_bytes_read, self.length_remaining) - - if data: - self._fp_bytes_read += len(data) - if self.length_remaining is not None: - self.length_remaining -= len(data) - - data = self._decode(data, decode_content, flush_decoder) - - if cache_content: - self._body = data - - return data - - def stream(self, amt=2**16, decode_content=None): - """ - A generator wrapper for the read() method. A call will block until - ``amt`` bytes have been read from the connection or until the - connection is closed. - - :param amt: - How much of the content to read. The generator will return up to - much data per iteration, but may return less. This is particularly - likely when using compressed data. However, the empty string will - never be returned. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - if self.chunked and self.supports_chunked_reads(): - for line in self.read_chunked(amt, decode_content=decode_content): - yield line - else: - while not is_fp_closed(self._fp): - data = self.read(amt=amt, decode_content=decode_content) - - if data: - yield data - - @classmethod - def from_httplib(ResponseCls, r, **response_kw): - """ - Given an :class:`httplib.HTTPResponse` instance ``r``, return a - corresponding :class:`urllib3.response.HTTPResponse` object. - - Remaining parameters are passed to the HTTPResponse constructor, along - with ``original_response=r``. - """ - headers = r.msg - - if not isinstance(headers, HTTPHeaderDict): - if PY3: # Python 3 - headers = HTTPHeaderDict(headers.items()) - else: # Python 2 - headers = HTTPHeaderDict.from_httplib(headers) - - # HTTPResponse objects in Python 3 don't have a .strict attribute - strict = getattr(r, 'strict', 0) - resp = ResponseCls(body=r, - headers=headers, - status=r.status, - version=r.version, - reason=r.reason, - strict=strict, - original_response=r, - **response_kw) - return resp - - # Backwards-compatibility methods for httplib.HTTPResponse - def getheaders(self): - return self.headers - - def getheader(self, name, default=None): - return self.headers.get(name, default) - - # Overrides from io.IOBase - def close(self): - if not self.closed: - self._fp.close() - - if self._connection: - self._connection.close() - - @property - def closed(self): - if self._fp is None: - return True - elif hasattr(self._fp, 'isclosed'): - return self._fp.isclosed() - elif hasattr(self._fp, 'closed'): - return self._fp.closed - else: - return True - - def fileno(self): - if self._fp is None: - raise IOError("HTTPResponse has no file to get a fileno from") - elif hasattr(self._fp, "fileno"): - return self._fp.fileno() - else: - raise IOError("The file-like object this HTTPResponse is wrapped " - "around has no file descriptor") - - def flush(self): - if self._fp is not None and hasattr(self._fp, 'flush'): - return self._fp.flush() - - def readable(self): - # This method is required for `io` module compatibility. - return True - - def readinto(self, b): - # This method is required for `io` module compatibility. - temp = self.read(len(b)) - if len(temp) == 0: - return 0 - else: - b[:len(temp)] = temp - return len(temp) - - def supports_chunked_reads(self): - """ - Checks if the underlying file-like object looks like a - httplib.HTTPResponse object. We do this by testing for the fp - attribute. If it is present we assume it returns raw chunks as - processed by read_chunked(). - """ - return hasattr(self._fp, 'fp') - - def _update_chunk_length(self): - # First, we'll figure out length of a chunk and then - # we'll try to read it from socket. - if self.chunk_left is not None: - return - line = self._fp.fp.readline() - line = line.split(b';', 1)[0] - try: - self.chunk_left = int(line, 16) - except ValueError: - # Invalid chunked protocol response, abort. - self.close() - raise httplib.IncompleteRead(line) - - def _handle_chunk(self, amt): - returned_chunk = None - if amt is None: - chunk = self._fp._safe_read(self.chunk_left) - returned_chunk = chunk - self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. - self.chunk_left = None - elif amt < self.chunk_left: - value = self._fp._safe_read(amt) - self.chunk_left = self.chunk_left - amt - returned_chunk = value - elif amt == self.chunk_left: - value = self._fp._safe_read(amt) - self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. - self.chunk_left = None - returned_chunk = value - else: # amt > self.chunk_left - returned_chunk = self._fp._safe_read(self.chunk_left) - self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. - self.chunk_left = None - return returned_chunk - - def read_chunked(self, amt=None, decode_content=None): - """ - Similar to :meth:`HTTPResponse.read`, but with an additional - parameter: ``decode_content``. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - self._init_decoder() - # FIXME: Rewrite this method and make it a class with a better structured logic. - if not self.chunked: - raise ResponseNotChunked( - "Response is not chunked. " - "Header 'transfer-encoding: chunked' is missing.") - if not self.supports_chunked_reads(): - raise BodyNotHttplibCompatible( - "Body should be httplib.HTTPResponse like. " - "It should have have an fp attribute which returns raw chunks.") - - # Don't bother reading the body of a HEAD request. - if self._original_response and is_response_to_head(self._original_response): - self._original_response.close() - return - - with self._error_catcher(): - while True: - self._update_chunk_length() - if self.chunk_left == 0: - break - chunk = self._handle_chunk(amt) - decoded = self._decode(chunk, decode_content=decode_content, - flush_decoder=False) - if decoded: - yield decoded - - if decode_content: - # On CPython and PyPy, we should never need to flush the - # decoder. However, on Jython we *might* need to, so - # lets defensively do it anyway. - decoded = self._flush_decoder() - if decoded: # Platform-specific: Jython. - yield decoded - - # Chunk content ends with \r\n: discard it. - while True: - line = self._fp.fp.readline() - if not line: - # Some sites may not end with '\r\n'. - break - if line == b'\r\n': - break - - # We read everything; close the "file". - if self._original_response: - self._original_response.close() diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/__init__.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/__init__.py deleted file mode 100644 index 5ced5a4..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import absolute_import -# For backwards compatibility, provide imports that used to be here. -from .connection import is_connection_dropped -from .request import make_headers -from .response import is_fp_closed -from .ssl_ import ( - SSLContext, - HAS_SNI, - IS_PYOPENSSL, - assert_fingerprint, - resolve_cert_reqs, - resolve_ssl_version, - ssl_wrap_socket, -) -from .timeout import ( - current_time, - Timeout, -) - -from .retry import Retry -from .url import ( - get_host, - parse_url, - split_first, - Url, -) -from .wait import ( - wait_for_read, - wait_for_write -) - -__all__ = ( - 'HAS_SNI', - 'IS_PYOPENSSL', - 'SSLContext', - 'Retry', - 'Timeout', - 'Url', - 'assert_fingerprint', - 'current_time', - 'is_connection_dropped', - 'is_fp_closed', - 'get_host', - 'parse_url', - 'make_headers', - 'resolve_cert_reqs', - 'resolve_ssl_version', - 'split_first', - 'ssl_wrap_socket', - 'wait_for_read', - 'wait_for_write' -) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/connection.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/connection.py deleted file mode 100644 index bf699cf..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/connection.py +++ /dev/null @@ -1,130 +0,0 @@ -from __future__ import absolute_import -import socket -from .wait import wait_for_read -from .selectors import HAS_SELECT, SelectorError - - -def is_connection_dropped(conn): # Platform-specific - """ - Returns True if the connection is dropped and should be closed. - - :param conn: - :class:`httplib.HTTPConnection` object. - - Note: For platforms like AppEngine, this will always return ``False`` to - let the platform handle connection recycling transparently for us. - """ - sock = getattr(conn, 'sock', False) - if sock is False: # Platform-specific: AppEngine - return False - if sock is None: # Connection already closed (such as by httplib). - return True - - if not HAS_SELECT: - return False - - try: - return bool(wait_for_read(sock, timeout=0.0)) - except SelectorError: - return True - - -# This function is copied from socket.py in the Python 2.7 standard -# library test suite. Added to its signature is only `socket_options`. -# One additional modification is that we avoid binding to IPv6 servers -# discovered in DNS if the system doesn't have IPv6 functionality. -def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None, socket_options=None): - """Connect to *address* and return the socket object. - - Convenience function. Connect to *address* (a 2-tuple ``(host, - port)``) and return the socket object. Passing the optional - *timeout* parameter will set the timeout on the socket instance - before attempting to connect. If no *timeout* is supplied, the - global default timeout setting returned by :func:`getdefaulttimeout` - is used. If *source_address* is set it must be a tuple of (host, port) - for the socket to bind as a source address before making the connection. - An host of '' or port 0 tells the OS to use the default. - """ - - host, port = address - if host.startswith('['): - host = host.strip('[]') - err = None - - # Using the value from allowed_gai_family() in the context of getaddrinfo lets - # us select whether to work with IPv4 DNS records, IPv6 records, or both. - # The original create_connection function always returns all records. - family = allowed_gai_family() - - for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - sock = None - try: - sock = socket.socket(af, socktype, proto) - - # If provided, set socket level options before connecting. - _set_socket_options(sock, socket_options) - - if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: - sock.settimeout(timeout) - if source_address: - sock.bind(source_address) - sock.connect(sa) - return sock - - except socket.error as e: - err = e - if sock is not None: - sock.close() - sock = None - - if err is not None: - raise err - - raise socket.error("getaddrinfo returns an empty list") - - -def _set_socket_options(sock, options): - if options is None: - return - - for opt in options: - sock.setsockopt(*opt) - - -def allowed_gai_family(): - """This function is designed to work in the context of - getaddrinfo, where family=socket.AF_UNSPEC is the default and - will perform a DNS search for both IPv6 and IPv4 records.""" - - family = socket.AF_INET - if HAS_IPV6: - family = socket.AF_UNSPEC - return family - - -def _has_ipv6(host): - """ Returns True if the system can bind an IPv6 address. """ - sock = None - has_ipv6 = False - - if socket.has_ipv6: - # has_ipv6 returns true if cPython was compiled with IPv6 support. - # It does not tell us if the system has IPv6 support enabled. To - # determine that we must bind to an IPv6 address. - # https://github.com/shazow/urllib3/pull/611 - # https://bugs.python.org/issue658327 - try: - sock = socket.socket(socket.AF_INET6) - sock.bind((host, 0)) - has_ipv6 = True - except Exception: - pass - - if sock: - sock.close() - return has_ipv6 - - -HAS_IPV6 = _has_ipv6('::1') diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/request.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/request.py deleted file mode 100644 index 974fc40..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/request.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import absolute_import -from base64 import b64encode - -from ..packages.six import b, integer_types -from ..exceptions import UnrewindableBodyError - -ACCEPT_ENCODING = 'gzip,deflate' -_FAILEDTELL = object() - - -def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, - basic_auth=None, proxy_basic_auth=None, disable_cache=None): - """ - Shortcuts for generating request headers. - - :param keep_alive: - If ``True``, adds 'connection: keep-alive' header. - - :param accept_encoding: - Can be a boolean, list, or string. - ``True`` translates to 'gzip,deflate'. - List will get joined by comma. - String will be used as provided. - - :param user_agent: - String representing the user-agent you want, such as - "python-urllib3/0.6" - - :param basic_auth: - Colon-separated username:password string for 'authorization: basic ...' - auth header. - - :param proxy_basic_auth: - Colon-separated username:password string for 'proxy-authorization: basic ...' - auth header. - - :param disable_cache: - If ``True``, adds 'cache-control: no-cache' header. - - Example:: - - >>> make_headers(keep_alive=True, user_agent="Batman/1.0") - {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} - >>> make_headers(accept_encoding=True) - {'accept-encoding': 'gzip,deflate'} - """ - headers = {} - if accept_encoding: - if isinstance(accept_encoding, str): - pass - elif isinstance(accept_encoding, list): - accept_encoding = ','.join(accept_encoding) - else: - accept_encoding = ACCEPT_ENCODING - headers['accept-encoding'] = accept_encoding - - if user_agent: - headers['user-agent'] = user_agent - - if keep_alive: - headers['connection'] = 'keep-alive' - - if basic_auth: - headers['authorization'] = 'Basic ' + \ - b64encode(b(basic_auth)).decode('utf-8') - - if proxy_basic_auth: - headers['proxy-authorization'] = 'Basic ' + \ - b64encode(b(proxy_basic_auth)).decode('utf-8') - - if disable_cache: - headers['cache-control'] = 'no-cache' - - return headers - - -def set_file_position(body, pos): - """ - If a position is provided, move file to that point. - Otherwise, we'll attempt to record a position for future use. - """ - if pos is not None: - rewind_body(body, pos) - elif getattr(body, 'tell', None) is not None: - try: - pos = body.tell() - except (IOError, OSError): - # This differentiates from None, allowing us to catch - # a failed `tell()` later when trying to rewind the body. - pos = _FAILEDTELL - - return pos - - -def rewind_body(body, body_pos): - """ - Attempt to rewind body to a certain position. - Primarily used for request redirects and retries. - - :param body: - File-like object that supports seek. - - :param int pos: - Position to seek to in file. - """ - body_seek = getattr(body, 'seek', None) - if body_seek is not None and isinstance(body_pos, integer_types): - try: - body_seek(body_pos) - except (IOError, OSError): - raise UnrewindableBodyError("An error occured when rewinding request " - "body for redirect/retry.") - elif body_pos is _FAILEDTELL: - raise UnrewindableBodyError("Unable to record file position for rewinding " - "request body during a redirect/retry.") - else: - raise ValueError("body_pos must be of type integer, " - "instead it was %s." % type(body_pos)) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/response.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/response.py deleted file mode 100644 index 67cf730..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/response.py +++ /dev/null @@ -1,81 +0,0 @@ -from __future__ import absolute_import -from ..packages.six.moves import http_client as httplib - -from ..exceptions import HeaderParsingError - - -def is_fp_closed(obj): - """ - Checks whether a given file-like object is closed. - - :param obj: - The file-like object to check. - """ - - try: - # Check `isclosed()` first, in case Python3 doesn't set `closed`. - # GH Issue #928 - return obj.isclosed() - except AttributeError: - pass - - try: - # Check via the official file-like-object way. - return obj.closed - except AttributeError: - pass - - try: - # Check if the object is a container for another file-like object that - # gets released on exhaustion (e.g. HTTPResponse). - return obj.fp is None - except AttributeError: - pass - - raise ValueError("Unable to determine whether fp is closed.") - - -def assert_header_parsing(headers): - """ - Asserts whether all headers have been successfully parsed. - Extracts encountered errors from the result of parsing headers. - - Only works on Python 3. - - :param headers: Headers to verify. - :type headers: `httplib.HTTPMessage`. - - :raises urllib3.exceptions.HeaderParsingError: - If parsing errors are found. - """ - - # This will fail silently if we pass in the wrong kind of parameter. - # To make debugging easier add an explicit check. - if not isinstance(headers, httplib.HTTPMessage): - raise TypeError('expected httplib.Message, got {0}.'.format( - type(headers))) - - defects = getattr(headers, 'defects', None) - get_payload = getattr(headers, 'get_payload', None) - - unparsed_data = None - if get_payload: # Platform-specific: Python 3. - unparsed_data = get_payload() - - if defects or unparsed_data: - raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) - - -def is_response_to_head(response): - """ - Checks whether the request of a response has been a HEAD-request. - Handles the quirks of AppEngine. - - :param conn: - :type conn: :class:`httplib.HTTPResponse` - """ - # FIXME: Can we do this somehow without accessing private httplib _method? - method = response._method - if isinstance(method, int): # Platform-specific: Appengine - return method == 3 - return method.upper() == 'HEAD' diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/retry.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/retry.py deleted file mode 100644 index c9e7d28..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/retry.py +++ /dev/null @@ -1,389 +0,0 @@ -from __future__ import absolute_import -import time -import logging -from collections import namedtuple -from itertools import takewhile -import email -import re - -from ..exceptions import ( - ConnectTimeoutError, - MaxRetryError, - ProtocolError, - ReadTimeoutError, - ResponseError, - InvalidHeader, -) -from ..packages import six - - -log = logging.getLogger(__name__) - -# Data structure for representing the metadata of requests that result in a retry. -RequestHistory = namedtuple('RequestHistory', ["method", "url", "error", - "status", "redirect_location"]) - - -class Retry(object): - """ Retry configuration. - - Each retry attempt will create a new Retry object with updated values, so - they can be safely reused. - - Retries can be defined as a default for a pool:: - - retries = Retry(connect=5, read=2, redirect=5) - http = PoolManager(retries=retries) - response = http.request('GET', 'http://example.com/') - - Or per-request (which overrides the default for the pool):: - - response = http.request('GET', 'http://example.com/', retries=Retry(10)) - - Retries can be disabled by passing ``False``:: - - response = http.request('GET', 'http://example.com/', retries=False) - - Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless - retries are disabled, in which case the causing exception will be raised. - - :param int total: - Total number of retries to allow. Takes precedence over other counts. - - Set to ``None`` to remove this constraint and fall back on other - counts. It's a good idea to set this to some sensibly-high value to - account for unexpected edge cases and avoid infinite retry loops. - - Set to ``0`` to fail on the first retry. - - Set to ``False`` to disable and imply ``raise_on_redirect=False``. - - :param int connect: - How many connection-related errors to retry on. - - These are errors raised before the request is sent to the remote server, - which we assume has not triggered the server to process the request. - - Set to ``0`` to fail on the first retry of this type. - - :param int read: - How many times to retry on read errors. - - These errors are raised after the request was sent to the server, so the - request may have side-effects. - - Set to ``0`` to fail on the first retry of this type. - - :param int redirect: - How many redirects to perform. Limit this to avoid infinite redirect - loops. - - A redirect is a HTTP response with a status code 301, 302, 303, 307 or - 308. - - Set to ``0`` to fail on the first retry of this type. - - Set to ``False`` to disable and imply ``raise_on_redirect=False``. - - :param iterable method_whitelist: - Set of uppercased HTTP method verbs that we should retry on. - - By default, we only retry on methods which are considered to be - idempotent (multiple requests with the same parameters end with the - same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. - - Set to a ``False`` value to retry on any verb. - - :param iterable status_forcelist: - A set of integer HTTP status codes that we should force a retry on. - A retry is initiated if the request method is in ``method_whitelist`` - and the response status code is in ``status_forcelist``. - - By default, this is disabled with ``None``. - - :param float backoff_factor: - A backoff factor to apply between attempts after the second try - (most errors are resolved immediately by a second try without a - delay). urllib3 will sleep for:: - - {backoff factor} * (2 ^ ({number of total retries} - 1)) - - seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep - for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer - than :attr:`Retry.BACKOFF_MAX`. - - By default, backoff is disabled (set to 0). - - :param bool raise_on_redirect: Whether, if the number of redirects is - exhausted, to raise a MaxRetryError, or to return a response with a - response code in the 3xx range. - - :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: - whether we should raise an exception, or return a response, - if status falls in ``status_forcelist`` range and retries have - been exhausted. - - :param tuple history: The history of the request encountered during - each call to :meth:`~Retry.increment`. The list is in the order - the requests occurred. Each list item is of class :class:`RequestHistory`. - - :param bool respect_retry_after_header: - Whether to respect Retry-After header on status codes defined as - :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. - - """ - - DEFAULT_METHOD_WHITELIST = frozenset([ - 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) - - RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) - - #: Maximum backoff time. - BACKOFF_MAX = 120 - - def __init__(self, total=10, connect=None, read=None, redirect=None, - method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, - backoff_factor=0, raise_on_redirect=True, raise_on_status=True, - history=None, respect_retry_after_header=True): - - self.total = total - self.connect = connect - self.read = read - - if redirect is False or total is False: - redirect = 0 - raise_on_redirect = False - - self.redirect = redirect - self.status_forcelist = status_forcelist or set() - self.method_whitelist = method_whitelist - self.backoff_factor = backoff_factor - self.raise_on_redirect = raise_on_redirect - self.raise_on_status = raise_on_status - self.history = history or tuple() - self.respect_retry_after_header = respect_retry_after_header - - def new(self, **kw): - params = dict( - total=self.total, - connect=self.connect, read=self.read, redirect=self.redirect, - method_whitelist=self.method_whitelist, - status_forcelist=self.status_forcelist, - backoff_factor=self.backoff_factor, - raise_on_redirect=self.raise_on_redirect, - raise_on_status=self.raise_on_status, - history=self.history, - ) - params.update(kw) - return type(self)(**params) - - @classmethod - def from_int(cls, retries, redirect=True, default=None): - """ Backwards-compatibility for the old retries format.""" - if retries is None: - retries = default if default is not None else cls.DEFAULT - - if isinstance(retries, Retry): - return retries - - redirect = bool(redirect) and None - new_retries = cls(retries, redirect=redirect) - log.debug("Converted retries value: %r -> %r", retries, new_retries) - return new_retries - - def get_backoff_time(self): - """ Formula for computing the current backoff - - :rtype: float - """ - # We want to consider only the last consecutive errors sequence (Ignore redirects). - consecutive_errors_len = len(list(takewhile(lambda x: x.redirect_location is None, - reversed(self.history)))) - if consecutive_errors_len <= 1: - return 0 - - backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) - return min(self.BACKOFF_MAX, backoff_value) - - def parse_retry_after(self, retry_after): - # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 - if re.match(r"^\s*[0-9]+\s*$", retry_after): - seconds = int(retry_after) - else: - retry_date_tuple = email.utils.parsedate(retry_after) - if retry_date_tuple is None: - raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) - retry_date = time.mktime(retry_date_tuple) - seconds = retry_date - time.time() - - if seconds < 0: - seconds = 0 - - return seconds - - def get_retry_after(self, response): - """ Get the value of Retry-After in seconds. """ - - retry_after = response.getheader("Retry-After") - - if retry_after is None: - return None - - return self.parse_retry_after(retry_after) - - def sleep_for_retry(self, response=None): - retry_after = self.get_retry_after(response) - if retry_after: - time.sleep(retry_after) - return True - - return False - - def _sleep_backoff(self): - backoff = self.get_backoff_time() - if backoff <= 0: - return - time.sleep(backoff) - - def sleep(self, response=None): - """ Sleep between retry attempts. - - This method will respect a server's ``Retry-After`` response header - and sleep the duration of the time requested. If that is not present, it - will use an exponential backoff. By default, the backoff factor is 0 and - this method will return immediately. - """ - - if response: - slept = self.sleep_for_retry(response) - if slept: - return - - self._sleep_backoff() - - def _is_connection_error(self, err): - """ Errors when we're fairly sure that the server did not receive the - request, so it should be safe to retry. - """ - return isinstance(err, ConnectTimeoutError) - - def _is_read_error(self, err): - """ Errors that occur after the request has been started, so we should - assume that the server began processing it. - """ - return isinstance(err, (ReadTimeoutError, ProtocolError)) - - def _is_method_retryable(self, method): - """ Checks if a given HTTP method should be retried upon, depending if - it is included on the method whitelist. - """ - if self.method_whitelist and method.upper() not in self.method_whitelist: - return False - - return True - - def is_retry(self, method, status_code, has_retry_after=False): - """ Is this method/status code retryable? (Based on whitelists and control - variables such as the number of total retries to allow, whether to - respect the Retry-After header, whether this header is present, and - whether the returned status code is on the list of status codes to - be retried upon on the presence of the aforementioned header) - """ - if not self._is_method_retryable(method): - return False - - if self.status_forcelist and status_code in self.status_forcelist: - return True - - return (self.total and self.respect_retry_after_header and - has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES)) - - def is_exhausted(self): - """ Are we out of retries? """ - retry_counts = (self.total, self.connect, self.read, self.redirect) - retry_counts = list(filter(None, retry_counts)) - if not retry_counts: - return False - - return min(retry_counts) < 0 - - def increment(self, method=None, url=None, response=None, error=None, - _pool=None, _stacktrace=None): - """ Return a new Retry object with incremented retry counters. - - :param response: A response object, or None, if the server did not - return a response. - :type response: :class:`~urllib3.response.HTTPResponse` - :param Exception error: An error encountered during the request, or - None if the response was received successfully. - - :return: A new ``Retry`` object. - """ - if self.total is False and error: - # Disabled, indicate to re-raise the error. - raise six.reraise(type(error), error, _stacktrace) - - total = self.total - if total is not None: - total -= 1 - - connect = self.connect - read = self.read - redirect = self.redirect - cause = 'unknown' - status = None - redirect_location = None - - if error and self._is_connection_error(error): - # Connect retry? - if connect is False: - raise six.reraise(type(error), error, _stacktrace) - elif connect is not None: - connect -= 1 - - elif error and self._is_read_error(error): - # Read retry? - if read is False or not self._is_method_retryable(method): - raise six.reraise(type(error), error, _stacktrace) - elif read is not None: - read -= 1 - - elif response and response.get_redirect_location(): - # Redirect retry? - if redirect is not None: - redirect -= 1 - cause = 'too many redirects' - redirect_location = response.get_redirect_location() - status = response.status - - else: - # Incrementing because of a server error like a 500 in - # status_forcelist and a the given method is in the whitelist - cause = ResponseError.GENERIC_ERROR - if response and response.status: - cause = ResponseError.SPECIFIC_ERROR.format( - status_code=response.status) - status = response.status - - history = self.history + (RequestHistory(method, url, error, status, redirect_location),) - - new_retry = self.new( - total=total, - connect=connect, read=read, redirect=redirect, - history=history) - - if new_retry.is_exhausted(): - raise MaxRetryError(_pool, url, error or ResponseError(cause)) - - log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) - - return new_retry - - def __repr__(self): - return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' - 'read={self.read}, redirect={self.redirect})').format( - cls=type(self), self=self) - - -# For backwards compatibility (equivalent to pre-v1.9): -Retry.DEFAULT = Retry(3) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/selectors.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/selectors.py deleted file mode 100644 index b381450..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/selectors.py +++ /dev/null @@ -1,529 +0,0 @@ -# Backport of selectors.py from Python 3.5+ to support Python < 3.4 -# Also has the behavior specified in PEP 475 which is to retry syscalls -# in the case of an EINTR error. This module is required because selectors34 -# does not follow this behavior and instead returns that no dile descriptor -# events have occurred rather than retry the syscall. The decision to drop -# support for select.devpoll is made to maintain 100% test coverage. - -import errno -import math -import select -from collections import namedtuple - -try: - from collections.abc import Mapping -except ImportError: - from collections import Mapping - -import time -try: - monotonic = time.monotonic -except (AttributeError, ImportError): # Python 3.3< - monotonic = time.time - -EVENT_READ = (1 << 0) -EVENT_WRITE = (1 << 1) - -HAS_SELECT = True # Variable that shows whether the platform has a selector. -_SYSCALL_SENTINEL = object() # Sentinel in case a system call returns None. - - -class SelectorError(Exception): - def __init__(self, errcode): - super(SelectorError, self).__init__() - self.errno = errcode - - def __repr__(self): - return "".format(self.errno) - - def __str__(self): - return self.__repr__() - - -def _fileobj_to_fd(fileobj): - """ Return a file descriptor from a file object. If - given an integer will simply return that integer back. """ - if isinstance(fileobj, int): - fd = fileobj - else: - try: - fd = int(fileobj.fileno()) - except (AttributeError, TypeError, ValueError): - raise ValueError("Invalid file object: {0!r}".format(fileobj)) - if fd < 0: - raise ValueError("Invalid file descriptor: {0}".format(fd)) - return fd - - -def _syscall_wrapper(func, recalc_timeout, *args, **kwargs): - """ Wrapper function for syscalls that could fail due to EINTR. - All functions should be retried if there is time left in the timeout - in accordance with PEP 475. """ - timeout = kwargs.get("timeout", None) - if timeout is None: - expires = None - recalc_timeout = False - else: - timeout = float(timeout) - if timeout < 0.0: # Timeout less than 0 treated as no timeout. - expires = None - else: - expires = monotonic() + timeout - - args = list(args) - if recalc_timeout and "timeout" not in kwargs: - raise ValueError( - "Timeout must be in args or kwargs to be recalculated") - - result = _SYSCALL_SENTINEL - while result is _SYSCALL_SENTINEL: - try: - result = func(*args, **kwargs) - # OSError is thrown by select.select - # IOError is thrown by select.epoll.poll - # select.error is thrown by select.poll.poll - # Aren't we thankful for Python 3.x rework for exceptions? - except (OSError, IOError, select.error) as e: - # select.error wasn't a subclass of OSError in the past. - errcode = None - if hasattr(e, "errno"): - errcode = e.errno - elif hasattr(e, "args"): - errcode = e.args[0] - - # Also test for the Windows equivalent of EINTR. - is_interrupt = (errcode == errno.EINTR or (hasattr(errno, "WSAEINTR") and - errcode == errno.WSAEINTR)) - - if is_interrupt: - if expires is not None: - current_time = monotonic() - if current_time > expires: - raise OSError(errno=errno.ETIMEDOUT) - if recalc_timeout: - if "timeout" in kwargs: - kwargs["timeout"] = expires - current_time - continue - if errcode: - raise SelectorError(errcode) - else: - raise - return result - - -SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data']) - - -class _SelectorMapping(Mapping): - """ Mapping of file objects to selector keys """ - - def __init__(self, selector): - self._selector = selector - - def __len__(self): - return len(self._selector._fd_to_key) - - def __getitem__(self, fileobj): - try: - fd = self._selector._fileobj_lookup(fileobj) - return self._selector._fd_to_key[fd] - except KeyError: - raise KeyError("{0!r} is not registered.".format(fileobj)) - - def __iter__(self): - return iter(self._selector._fd_to_key) - - -class BaseSelector(object): - """ Abstract Selector class - - A selector supports registering file objects to be monitored - for specific I/O events. - - A file object is a file descriptor or any object with a - `fileno()` method. An arbitrary object can be attached to the - file object which can be used for example to store context info, - a callback, etc. - - A selector can use various implementations (select(), poll(), epoll(), - and kqueue()) depending on the platform. The 'DefaultSelector' class uses - the most efficient implementation for the current platform. - """ - def __init__(self): - # Maps file descriptors to keys. - self._fd_to_key = {} - - # Read-only mapping returned by get_map() - self._map = _SelectorMapping(self) - - def _fileobj_lookup(self, fileobj): - """ Return a file descriptor from a file object. - This wraps _fileobj_to_fd() to do an exhaustive - search in case the object is invalid but we still - have it in our map. Used by unregister() so we can - unregister an object that was previously registered - even if it is closed. It is also used by _SelectorMapping - """ - try: - return _fileobj_to_fd(fileobj) - except ValueError: - - # Search through all our mapped keys. - for key in self._fd_to_key.values(): - if key.fileobj is fileobj: - return key.fd - - # Raise ValueError after all. - raise - - def register(self, fileobj, events, data=None): - """ Register a file object for a set of events to monitor. """ - if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): - raise ValueError("Invalid events: {0!r}".format(events)) - - key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data) - - if key.fd in self._fd_to_key: - raise KeyError("{0!r} (FD {1}) is already registered" - .format(fileobj, key.fd)) - - self._fd_to_key[key.fd] = key - return key - - def unregister(self, fileobj): - """ Unregister a file object from being monitored. """ - try: - key = self._fd_to_key.pop(self._fileobj_lookup(fileobj)) - except KeyError: - raise KeyError("{0!r} is not registered".format(fileobj)) - return key - - def modify(self, fileobj, events, data=None): - """ Change a registered file object monitored events and data. """ - # NOTE: Some subclasses optimize this operation even further. - try: - key = self._fd_to_key[self._fileobj_lookup(fileobj)] - except KeyError: - raise KeyError("{0!r} is not registered".format(fileobj)) - - if events != key.events: - self.unregister(fileobj) - key = self.register(fileobj, events, data) - - elif data != key.data: - # Use a shortcut to update the data. - key = key._replace(data=data) - self._fd_to_key[key.fd] = key - - return key - - def select(self, timeout=None): - """ Perform the actual selection until some monitored file objects - are ready or the timeout expires. """ - raise NotImplementedError() - - def close(self): - """ Close the selector. This must be called to ensure that all - underlying resources are freed. """ - self._fd_to_key.clear() - self._map = None - - def get_key(self, fileobj): - """ Return the key associated with a registered file object. """ - mapping = self.get_map() - if mapping is None: - raise RuntimeError("Selector is closed") - try: - return mapping[fileobj] - except KeyError: - raise KeyError("{0!r} is not registered".format(fileobj)) - - def get_map(self): - """ Return a mapping of file objects to selector keys """ - return self._map - - def _key_from_fd(self, fd): - """ Return the key associated to a given file descriptor - Return None if it is not found. """ - try: - return self._fd_to_key[fd] - except KeyError: - return None - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() - - -# Almost all platforms have select.select() -if hasattr(select, "select"): - class SelectSelector(BaseSelector): - """ Select-based selector. """ - def __init__(self): - super(SelectSelector, self).__init__() - self._readers = set() - self._writers = set() - - def register(self, fileobj, events, data=None): - key = super(SelectSelector, self).register(fileobj, events, data) - if events & EVENT_READ: - self._readers.add(key.fd) - if events & EVENT_WRITE: - self._writers.add(key.fd) - return key - - def unregister(self, fileobj): - key = super(SelectSelector, self).unregister(fileobj) - self._readers.discard(key.fd) - self._writers.discard(key.fd) - return key - - def _select(self, r, w, timeout=None): - """ Wrapper for select.select because timeout is a positional arg """ - return select.select(r, w, [], timeout) - - def select(self, timeout=None): - # Selecting on empty lists on Windows errors out. - if not len(self._readers) and not len(self._writers): - return [] - - timeout = None if timeout is None else max(timeout, 0.0) - ready = [] - r, w, _ = _syscall_wrapper(self._select, True, self._readers, - self._writers, timeout) - r = set(r) - w = set(w) - for fd in r | w: - events = 0 - if fd in r: - events |= EVENT_READ - if fd in w: - events |= EVENT_WRITE - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - return ready - - -if hasattr(select, "poll"): - class PollSelector(BaseSelector): - """ Poll-based selector """ - def __init__(self): - super(PollSelector, self).__init__() - self._poll = select.poll() - - def register(self, fileobj, events, data=None): - key = super(PollSelector, self).register(fileobj, events, data) - event_mask = 0 - if events & EVENT_READ: - event_mask |= select.POLLIN - if events & EVENT_WRITE: - event_mask |= select.POLLOUT - self._poll.register(key.fd, event_mask) - return key - - def unregister(self, fileobj): - key = super(PollSelector, self).unregister(fileobj) - self._poll.unregister(key.fd) - return key - - def _wrap_poll(self, timeout=None): - """ Wrapper function for select.poll.poll() so that - _syscall_wrapper can work with only seconds. """ - if timeout is not None: - if timeout <= 0: - timeout = 0 - else: - # select.poll.poll() has a resolution of 1 millisecond, - # round away from zero to wait *at least* timeout seconds. - timeout = math.ceil(timeout * 1e3) - - result = self._poll.poll(timeout) - return result - - def select(self, timeout=None): - ready = [] - fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout) - for fd, event_mask in fd_events: - events = 0 - if event_mask & ~select.POLLIN: - events |= EVENT_WRITE - if event_mask & ~select.POLLOUT: - events |= EVENT_READ - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - - return ready - - -if hasattr(select, "epoll"): - class EpollSelector(BaseSelector): - """ Epoll-based selector """ - def __init__(self): - super(EpollSelector, self).__init__() - self._epoll = select.epoll() - - def fileno(self): - return self._epoll.fileno() - - def register(self, fileobj, events, data=None): - key = super(EpollSelector, self).register(fileobj, events, data) - events_mask = 0 - if events & EVENT_READ: - events_mask |= select.EPOLLIN - if events & EVENT_WRITE: - events_mask |= select.EPOLLOUT - _syscall_wrapper(self._epoll.register, False, key.fd, events_mask) - return key - - def unregister(self, fileobj): - key = super(EpollSelector, self).unregister(fileobj) - try: - _syscall_wrapper(self._epoll.unregister, False, key.fd) - except SelectorError: - # This can occur when the fd was closed since registry. - pass - return key - - def select(self, timeout=None): - if timeout is not None: - if timeout <= 0: - timeout = 0.0 - else: - # select.epoll.poll() has a resolution of 1 millisecond - # but luckily takes seconds so we don't need a wrapper - # like PollSelector. Just for better rounding. - timeout = math.ceil(timeout * 1e3) * 1e-3 - timeout = float(timeout) - else: - timeout = -1.0 # epoll.poll() must have a float. - - # We always want at least 1 to ensure that select can be called - # with no file descriptors registered. Otherwise will fail. - max_events = max(len(self._fd_to_key), 1) - - ready = [] - fd_events = _syscall_wrapper(self._epoll.poll, True, - timeout=timeout, - maxevents=max_events) - for fd, event_mask in fd_events: - events = 0 - if event_mask & ~select.EPOLLIN: - events |= EVENT_WRITE - if event_mask & ~select.EPOLLOUT: - events |= EVENT_READ - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - return ready - - def close(self): - self._epoll.close() - super(EpollSelector, self).close() - - -if hasattr(select, "kqueue"): - class KqueueSelector(BaseSelector): - """ Kqueue / Kevent-based selector """ - def __init__(self): - super(KqueueSelector, self).__init__() - self._kqueue = select.kqueue() - - def fileno(self): - return self._kqueue.fileno() - - def register(self, fileobj, events, data=None): - key = super(KqueueSelector, self).register(fileobj, events, data) - if events & EVENT_READ: - kevent = select.kevent(key.fd, - select.KQ_FILTER_READ, - select.KQ_EV_ADD) - - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) - - if events & EVENT_WRITE: - kevent = select.kevent(key.fd, - select.KQ_FILTER_WRITE, - select.KQ_EV_ADD) - - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) - - return key - - def unregister(self, fileobj): - key = super(KqueueSelector, self).unregister(fileobj) - if key.events & EVENT_READ: - kevent = select.kevent(key.fd, - select.KQ_FILTER_READ, - select.KQ_EV_DELETE) - try: - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) - except SelectorError: - pass - if key.events & EVENT_WRITE: - kevent = select.kevent(key.fd, - select.KQ_FILTER_WRITE, - select.KQ_EV_DELETE) - try: - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) - except SelectorError: - pass - - return key - - def select(self, timeout=None): - if timeout is not None: - timeout = max(timeout, 0) - - max_events = len(self._fd_to_key) * 2 - ready_fds = {} - - kevent_list = _syscall_wrapper(self._kqueue.control, True, - None, max_events, timeout) - - for kevent in kevent_list: - fd = kevent.ident - event_mask = kevent.filter - events = 0 - if event_mask == select.KQ_FILTER_READ: - events |= EVENT_READ - if event_mask == select.KQ_FILTER_WRITE: - events |= EVENT_WRITE - - key = self._key_from_fd(fd) - if key: - if key.fd not in ready_fds: - ready_fds[key.fd] = (key, events & key.events) - else: - old_events = ready_fds[key.fd][1] - ready_fds[key.fd] = (key, (events | old_events) & key.events) - - return list(ready_fds.values()) - - def close(self): - self._kqueue.close() - super(KqueueSelector, self).close() - - -# Choose the best implementation, roughly: -# kqueue == epoll > poll > select. Devpoll not supported. (See above) -# select() also can't accept a FD > FD_SETSIZE (usually around 1024) -if 'KqueueSelector' in globals(): # Platform-specific: Mac OS and BSD - DefaultSelector = KqueueSelector -elif 'EpollSelector' in globals(): # Platform-specific: Linux - DefaultSelector = EpollSelector -elif 'PollSelector' in globals(): # Platform-specific: Linux - DefaultSelector = PollSelector -elif 'SelectSelector' in globals(): # Platform-specific: Windows - DefaultSelector = SelectSelector -else: # Platform-specific: AppEngine - def no_selector(_): - raise ValueError("Platform does not have a selector") - DefaultSelector = no_selector - HAS_SELECT = False diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/ssl_.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/ssl_.py deleted file mode 100644 index c4c55df..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/ssl_.py +++ /dev/null @@ -1,336 +0,0 @@ -from __future__ import absolute_import -import errno -import warnings -import hmac - -from binascii import hexlify, unhexlify -from hashlib import md5, sha1, sha256 - -from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning - - -SSLContext = None -HAS_SNI = False -IS_PYOPENSSL = False - -# Maps the length of a digest to a possible hash function producing this digest -HASHFUNC_MAP = { - 32: md5, - 40: sha1, - 64: sha256, -} - - -def _const_compare_digest_backport(a, b): - """ - Compare two digests of equal length in constant time. - - The digests must be of type str/bytes. - Returns True if the digests match, and False otherwise. - """ - result = abs(len(a) - len(b)) - for l, r in zip(bytearray(a), bytearray(b)): - result |= l ^ r - return result == 0 - - -_const_compare_digest = getattr(hmac, 'compare_digest', - _const_compare_digest_backport) - - -try: # Test for SSL features - import ssl - from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 - from ssl import HAS_SNI # Has SNI? -except ImportError: - pass - - -try: - from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION -except ImportError: - OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 - OP_NO_COMPRESSION = 0x20000 - -# A secure default. -# Sources for more information on TLS ciphers: -# -# - https://wiki.mozilla.org/Security/Server_Side_TLS -# - https://www.ssllabs.com/projects/best-practices/index.html -# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ -# -# The general intent is: -# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), -# - prefer ECDHE over DHE for better performance, -# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and -# security, -# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, -# - disable NULL authentication, MD5 MACs and DSS for security reasons. -DEFAULT_CIPHERS = ':'.join([ - 'ECDH+AESGCM', - 'ECDH+CHACHA20', - 'DH+AESGCM', - 'DH+CHACHA20', - 'ECDH+AES256', - 'DH+AES256', - 'ECDH+AES128', - 'DH+AES', - 'RSA+AESGCM', - 'RSA+AES', - '!aNULL', - '!eNULL', - '!MD5', -]) - -try: - from ssl import SSLContext # Modern SSL? -except ImportError: - import sys - - class SSLContext(object): # Platform-specific: Python 2 & 3.1 - supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or - (3, 2) <= sys.version_info) - - def __init__(self, protocol_version): - self.protocol = protocol_version - # Use default values from a real SSLContext - self.check_hostname = False - self.verify_mode = ssl.CERT_NONE - self.ca_certs = None - self.options = 0 - self.certfile = None - self.keyfile = None - self.ciphers = None - - def load_cert_chain(self, certfile, keyfile): - self.certfile = certfile - self.keyfile = keyfile - - def load_verify_locations(self, cafile=None, capath=None): - self.ca_certs = cafile - - if capath is not None: - raise SSLError("CA directories not supported in older Pythons") - - def set_ciphers(self, cipher_suite): - if not self.supports_set_ciphers: - raise TypeError( - 'Your version of Python does not support setting ' - 'a custom cipher suite. Please upgrade to Python ' - '2.7, 3.2, or later if you need this functionality.' - ) - self.ciphers = cipher_suite - - def wrap_socket(self, socket, server_hostname=None, server_side=False): - warnings.warn( - 'A true SSLContext object is not available. This prevents ' - 'urllib3 from configuring SSL appropriately and may cause ' - 'certain SSL connections to fail. You can upgrade to a newer ' - 'version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - InsecurePlatformWarning - ) - kwargs = { - 'keyfile': self.keyfile, - 'certfile': self.certfile, - 'ca_certs': self.ca_certs, - 'cert_reqs': self.verify_mode, - 'ssl_version': self.protocol, - 'server_side': server_side, - } - if self.supports_set_ciphers: # Platform-specific: Python 2.7+ - return wrap_socket(socket, ciphers=self.ciphers, **kwargs) - else: # Platform-specific: Python 2.6 - return wrap_socket(socket, **kwargs) - - -def assert_fingerprint(cert, fingerprint): - """ - Checks if given fingerprint matches the supplied certificate. - - :param cert: - Certificate as bytes object. - :param fingerprint: - Fingerprint as string of hexdigits, can be interspersed by colons. - """ - - fingerprint = fingerprint.replace(':', '').lower() - digest_length = len(fingerprint) - hashfunc = HASHFUNC_MAP.get(digest_length) - if not hashfunc: - raise SSLError( - 'Fingerprint of invalid length: {0}'.format(fingerprint)) - - # We need encode() here for py32; works on py2 and p33. - fingerprint_bytes = unhexlify(fingerprint.encode()) - - cert_digest = hashfunc(cert).digest() - - if not _const_compare_digest(cert_digest, fingerprint_bytes): - raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' - .format(fingerprint, hexlify(cert_digest))) - - -def resolve_cert_reqs(candidate): - """ - Resolves the argument to a numeric constant, which can be passed to - the wrap_socket function/method from the ssl module. - Defaults to :data:`ssl.CERT_NONE`. - If given a string it is assumed to be the name of the constant in the - :mod:`ssl` module or its abbrevation. - (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. - If it's neither `None` nor a string we assume it is already the numeric - constant which can directly be passed to wrap_socket. - """ - if candidate is None: - return CERT_NONE - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) - if res is None: - res = getattr(ssl, 'CERT_' + candidate) - return res - - return candidate - - -def resolve_ssl_version(candidate): - """ - like resolve_cert_reqs - """ - if candidate is None: - return PROTOCOL_SSLv23 - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) - if res is None: - res = getattr(ssl, 'PROTOCOL_' + candidate) - return res - - return candidate - - -def create_urllib3_context(ssl_version=None, cert_reqs=None, - options=None, ciphers=None): - """All arguments have the same meaning as ``ssl_wrap_socket``. - - By default, this function does a lot of the same work that - ``ssl.create_default_context`` does on Python 3.4+. It: - - - Disables SSLv2, SSLv3, and compression - - Sets a restricted set of server ciphers - - If you wish to enable SSLv3, you can do:: - - from urllib3.util import ssl_ - context = ssl_.create_urllib3_context() - context.options &= ~ssl_.OP_NO_SSLv3 - - You can do the same to enable compression (substituting ``COMPRESSION`` - for ``SSLv3`` in the last line above). - - :param ssl_version: - The desired protocol version to use. This will default to - PROTOCOL_SSLv23 which will negotiate the highest protocol that both - the server and your installation of OpenSSL support. - :param cert_reqs: - Whether to require the certificate verification. This defaults to - ``ssl.CERT_REQUIRED``. - :param options: - Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, - ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. - :param ciphers: - Which cipher suites to allow the server to select. - :returns: - Constructed SSLContext object with specified options - :rtype: SSLContext - """ - context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) - - # Setting the default here, as we may have no ssl module on import - cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs - - if options is None: - options = 0 - # SSLv2 is easily broken and is considered harmful and dangerous - options |= OP_NO_SSLv2 - # SSLv3 has several problems and is now dangerous - options |= OP_NO_SSLv3 - # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ - # (issue #309) - options |= OP_NO_COMPRESSION - - context.options |= options - - if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6 - context.set_ciphers(ciphers or DEFAULT_CIPHERS) - - context.verify_mode = cert_reqs - if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 - # We do our own verification, including fingerprints and alternative - # hostnames. So disable it here - context.check_hostname = False - return context - - -def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None, ciphers=None, ssl_context=None, - ca_cert_dir=None): - """ - All arguments except for server_hostname, ssl_context, and ca_cert_dir have - the same meaning as they do when using :func:`ssl.wrap_socket`. - - :param server_hostname: - When SNI is supported, the expected hostname of the certificate - :param ssl_context: - A pre-made :class:`SSLContext` object. If none is provided, one will - be created using :func:`create_urllib3_context`. - :param ciphers: - A string of ciphers we wish the client to support. This is not - supported on Python 2.6 as the ssl module does not support it. - :param ca_cert_dir: - A directory containing CA certificates in multiple separate files, as - supported by OpenSSL's -CApath flag or the capath argument to - SSLContext.load_verify_locations(). - """ - context = ssl_context - if context is None: - # Note: This branch of code and all the variables in it are no longer - # used by urllib3 itself. We should consider deprecating and removing - # this code. - context = create_urllib3_context(ssl_version, cert_reqs, - ciphers=ciphers) - - if ca_certs or ca_cert_dir: - try: - context.load_verify_locations(ca_certs, ca_cert_dir) - except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2 - raise SSLError(e) - # Py33 raises FileNotFoundError which subclasses OSError - # These are not equivalent unless we check the errno attribute - except OSError as e: # Platform-specific: Python 3.3 and beyond - if e.errno == errno.ENOENT: - raise SSLError(e) - raise - elif getattr(context, 'load_default_certs', None) is not None: - # try to load OS default certs; works well on Windows (require Python3.4+) - context.load_default_certs() - - if certfile: - context.load_cert_chain(certfile, keyfile) - if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI - return context.wrap_socket(sock, server_hostname=server_hostname) - - warnings.warn( - 'An HTTPS request has been made, but the SNI (Subject Name ' - 'Indication) extension to TLS is not available on this platform. ' - 'This may cause the server to present an incorrect TLS ' - 'certificate, which can cause validation failures. You can upgrade to ' - 'a newer version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - SNIMissingWarning - ) - return context.wrap_socket(sock) diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/timeout.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/timeout.py deleted file mode 100644 index cec817e..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/timeout.py +++ /dev/null @@ -1,242 +0,0 @@ -from __future__ import absolute_import -# The default socket timeout, used by httplib to indicate that no timeout was -# specified by the user -from socket import _GLOBAL_DEFAULT_TIMEOUT -import time - -from ..exceptions import TimeoutStateError - -# A sentinel value to indicate that no timeout was specified by the user in -# urllib3 -_Default = object() - - -# Use time.monotonic if available. -current_time = getattr(time, "monotonic", time.time) - - -class Timeout(object): - """ Timeout configuration. - - Timeouts can be defined as a default for a pool:: - - timeout = Timeout(connect=2.0, read=7.0) - http = PoolManager(timeout=timeout) - response = http.request('GET', 'http://example.com/') - - Or per-request (which overrides the default for the pool):: - - response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) - - Timeouts can be disabled by setting all the parameters to ``None``:: - - no_timeout = Timeout(connect=None, read=None) - response = http.request('GET', 'http://example.com/, timeout=no_timeout) - - - :param total: - This combines the connect and read timeouts into one; the read timeout - will be set to the time leftover from the connect attempt. In the - event that both a connect timeout and a total are specified, or a read - timeout and a total are specified, the shorter timeout will be applied. - - Defaults to None. - - :type total: integer, float, or None - - :param connect: - The maximum amount of time to wait for a connection attempt to a server - to succeed. Omitting the parameter will default the connect timeout to - the system default, probably `the global default timeout in socket.py - `_. - None will set an infinite timeout for connection attempts. - - :type connect: integer, float, or None - - :param read: - The maximum amount of time to wait between consecutive - read operations for a response from the server. Omitting - the parameter will default the read timeout to the system - default, probably `the global default timeout in socket.py - `_. - None will set an infinite timeout. - - :type read: integer, float, or None - - .. note:: - - Many factors can affect the total amount of time for urllib3 to return - an HTTP response. - - For example, Python's DNS resolver does not obey the timeout specified - on the socket. Other factors that can affect total request time include - high CPU load, high swap, the program running at a low priority level, - or other behaviors. - - In addition, the read and total timeouts only measure the time between - read operations on the socket connecting the client and the server, - not the total amount of time for the request to return a complete - response. For most requests, the timeout is raised because the server - has not sent the first byte in the specified time. This is not always - the case; if a server streams one byte every fifteen seconds, a timeout - of 20 seconds will not trigger, even though the request will take - several minutes to complete. - - If your goal is to cut off any request after a set amount of wall clock - time, consider having a second "watcher" thread to cut off a slow - request. - """ - - #: A sentinel object representing the default timeout value - DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT - - def __init__(self, total=None, connect=_Default, read=_Default): - self._connect = self._validate_timeout(connect, 'connect') - self._read = self._validate_timeout(read, 'read') - self.total = self._validate_timeout(total, 'total') - self._start_connect = None - - def __str__(self): - return '%s(connect=%r, read=%r, total=%r)' % ( - type(self).__name__, self._connect, self._read, self.total) - - @classmethod - def _validate_timeout(cls, value, name): - """ Check that a timeout attribute is valid. - - :param value: The timeout value to validate - :param name: The name of the timeout attribute to validate. This is - used to specify in error messages. - :return: The validated and casted version of the given value. - :raises ValueError: If it is a numeric value less than or equal to - zero, or the type is not an integer, float, or None. - """ - if value is _Default: - return cls.DEFAULT_TIMEOUT - - if value is None or value is cls.DEFAULT_TIMEOUT: - return value - - if isinstance(value, bool): - raise ValueError("Timeout cannot be a boolean value. It must " - "be an int, float or None.") - try: - float(value) - except (TypeError, ValueError): - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) - - try: - if value <= 0: - raise ValueError("Attempted to set %s timeout to %s, but the " - "timeout cannot be set to a value less " - "than or equal to 0." % (name, value)) - except TypeError: # Python 3 - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) - - return value - - @classmethod - def from_float(cls, timeout): - """ Create a new Timeout from a legacy timeout value. - - The timeout value used by httplib.py sets the same timeout on the - connect(), and recv() socket requests. This creates a :class:`Timeout` - object that sets the individual timeouts to the ``timeout`` value - passed to this function. - - :param timeout: The legacy timeout value. - :type timeout: integer, float, sentinel default object, or None - :return: Timeout object - :rtype: :class:`Timeout` - """ - return Timeout(read=timeout, connect=timeout) - - def clone(self): - """ Create a copy of the timeout object - - Timeout properties are stored per-pool but each request needs a fresh - Timeout object to ensure each one has its own start/stop configured. - - :return: a copy of the timeout object - :rtype: :class:`Timeout` - """ - # We can't use copy.deepcopy because that will also create a new object - # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to - # detect the user default. - return Timeout(connect=self._connect, read=self._read, - total=self.total) - - def start_connect(self): - """ Start the timeout clock, used during a connect() attempt - - :raises urllib3.exceptions.TimeoutStateError: if you attempt - to start a timer that has been started already. - """ - if self._start_connect is not None: - raise TimeoutStateError("Timeout timer has already been started.") - self._start_connect = current_time() - return self._start_connect - - def get_connect_duration(self): - """ Gets the time elapsed since the call to :meth:`start_connect`. - - :return: Elapsed time. - :rtype: float - :raises urllib3.exceptions.TimeoutStateError: if you attempt - to get duration for a timer that hasn't been started. - """ - if self._start_connect is None: - raise TimeoutStateError("Can't get connect duration for timer " - "that has not started.") - return current_time() - self._start_connect - - @property - def connect_timeout(self): - """ Get the value to use when setting a connection timeout. - - This will be a positive float or integer, the value None - (never timeout), or the default system timeout. - - :return: Connect timeout. - :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None - """ - if self.total is None: - return self._connect - - if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: - return self.total - - return min(self._connect, self.total) - - @property - def read_timeout(self): - """ Get the value for the read timeout. - - This assumes some time has elapsed in the connection timeout and - computes the read timeout appropriately. - - If self.total is set, the read timeout is dependent on the amount of - time taken by the connect timeout. If the connection time has not been - established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be - raised. - - :return: Value to use for the read timeout. - :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None - :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` - has not yet been called on this object. - """ - if (self.total is not None and - self.total is not self.DEFAULT_TIMEOUT and - self._read is not None and - self._read is not self.DEFAULT_TIMEOUT): - # In case the connect timeout has not yet been established. - if self._start_connect is None: - return self._read - return max(0, min(self.total - self.get_connect_duration(), - self._read)) - elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: - return max(0, self.total - self.get_connect_duration()) - else: - return self._read diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/url.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/url.py deleted file mode 100644 index 61a326e..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/url.py +++ /dev/null @@ -1,226 +0,0 @@ -from __future__ import absolute_import -from collections import namedtuple - -from ..exceptions import LocationParseError - - -url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] - - -class Url(namedtuple('Url', url_attrs)): - """ - Datastructure for representing an HTTP URL. Used as a return value for - :func:`parse_url`. Both the scheme and host are normalized as they are - both case-insensitive according to RFC 3986. - """ - __slots__ = () - - def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, - query=None, fragment=None): - if path and not path.startswith('/'): - path = '/' + path - if scheme: - scheme = scheme.lower() - if host: - host = host.lower() - return super(Url, cls).__new__(cls, scheme, auth, host, port, path, - query, fragment) - - @property - def hostname(self): - """For backwards-compatibility with urlparse. We're nice like that.""" - return self.host - - @property - def request_uri(self): - """Absolute path including the query string.""" - uri = self.path or '/' - - if self.query is not None: - uri += '?' + self.query - - return uri - - @property - def netloc(self): - """Network location including host and port""" - if self.port: - return '%s:%d' % (self.host, self.port) - return self.host - - @property - def url(self): - """ - Convert self into a url - - This function should more or less round-trip with :func:`.parse_url`. The - returned url may not be exactly the same as the url inputted to - :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls - with a blank port will have : removed). - - Example: :: - - >>> U = parse_url('http://google.com/mail/') - >>> U.url - 'http://google.com/mail/' - >>> Url('http', 'username:password', 'host.com', 80, - ... '/path', 'query', 'fragment').url - 'http://username:password@host.com:80/path?query#fragment' - """ - scheme, auth, host, port, path, query, fragment = self - url = '' - - # We use "is not None" we want things to happen with empty strings (or 0 port) - if scheme is not None: - url += scheme + '://' - if auth is not None: - url += auth + '@' - if host is not None: - url += host - if port is not None: - url += ':' + str(port) - if path is not None: - url += path - if query is not None: - url += '?' + query - if fragment is not None: - url += '#' + fragment - - return url - - def __str__(self): - return self.url - - -def split_first(s, delims): - """ - Given a string and an iterable of delimiters, split on the first found - delimiter. Return two split parts and the matched delimiter. - - If not found, then the first part is the full input string. - - Example:: - - >>> split_first('foo/bar?baz', '?/=') - ('foo', 'bar?baz', '/') - >>> split_first('foo/bar?baz', '123') - ('foo/bar?baz', '', None) - - Scales linearly with number of delims. Not ideal for large number of delims. - """ - min_idx = None - min_delim = None - for d in delims: - idx = s.find(d) - if idx < 0: - continue - - if min_idx is None or idx < min_idx: - min_idx = idx - min_delim = d - - if min_idx is None or min_idx < 0: - return s, '', None - - return s[:min_idx], s[min_idx + 1:], min_delim - - -def parse_url(url): - """ - Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is - performed to parse incomplete urls. Fields not provided will be None. - - Partly backwards-compatible with :mod:`urlparse`. - - Example:: - - >>> parse_url('http://google.com/mail/') - Url(scheme='http', host='google.com', port=None, path='/mail/', ...) - >>> parse_url('google.com:80') - Url(scheme=None, host='google.com', port=80, path=None, ...) - >>> parse_url('/foo?bar') - Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) - """ - - # While this code has overlap with stdlib's urlparse, it is much - # simplified for our needs and less annoying. - # Additionally, this implementations does silly things to be optimal - # on CPython. - - if not url: - # Empty - return Url() - - scheme = None - auth = None - host = None - port = None - path = None - fragment = None - query = None - - # Scheme - if '://' in url: - scheme, url = url.split('://', 1) - - # Find the earliest Authority Terminator - # (http://tools.ietf.org/html/rfc3986#section-3.2) - url, path_, delim = split_first(url, ['/', '?', '#']) - - if delim: - # Reassemble the path - path = delim + path_ - - # Auth - if '@' in url: - # Last '@' denotes end of auth part - auth, url = url.rsplit('@', 1) - - # IPv6 - if url and url[0] == '[': - host, url = url.split(']', 1) - host += ']' - - # Port - if ':' in url: - _host, port = url.split(':', 1) - - if not host: - host = _host - - if port: - # If given, ports must be integers. No whitespace, no plus or - # minus prefixes, no non-integer digits such as ^2 (superscript). - if not port.isdigit(): - raise LocationParseError(url) - try: - port = int(port) - except ValueError: - raise LocationParseError(url) - else: - # Blank ports are cool, too. (rfc3986#section-3.2.3) - port = None - - elif not host and url: - host = url - - if not path: - return Url(scheme, auth, host, port, path, query, fragment) - - # Fragment - if '#' in path: - path, fragment = path.split('#', 1) - - # Query - if '?' in path: - path, query = path.split('?', 1) - - return Url(scheme, auth, host, port, path, query, fragment) - - -def get_host(url): - """ - Deprecated. Use :func:`parse_url` instead. - """ - p = parse_url(url) - return p.scheme or 'http', p.hostname, p.port diff --git a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/wait.py b/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/wait.py deleted file mode 100644 index cb396e5..0000000 --- a/telegramer/include/telegram/vendor/ptb_urllib3/urllib3/util/wait.py +++ /dev/null @@ -1,40 +0,0 @@ -from .selectors import ( - HAS_SELECT, - DefaultSelector, - EVENT_READ, - EVENT_WRITE -) - - -def _wait_for_io_events(socks, events, timeout=None): - """ Waits for IO events to be available from a list of sockets - or optionally a single socket if passed in. Returns a list of - sockets that can be interacted with immediately. """ - if not HAS_SELECT: - raise ValueError('Platform does not have a selector') - if not isinstance(socks, list): - # Probably just a single socket. - if hasattr(socks, "fileno"): - socks = [socks] - # Otherwise it might be a non-list iterable. - else: - socks = list(socks) - with DefaultSelector() as selector: - for sock in socks: - selector.register(sock, events) - return [key[0].fileobj for key in - selector.select(timeout) if key[1] & events] - - -def wait_for_read(socks, timeout=None): - """ Waits for reading to be available from a list of sockets - or optionally a single socket if passed in. Returns a list of - sockets that can be read from immediately. """ - return _wait_for_io_events(socks, EVENT_READ, timeout) - - -def wait_for_write(socks, timeout=None): - """ Waits for writing to be available from a list of sockets - or optionally a single socket if passed in. Returns a list of - sockets that can be written to immediately. """ - return _wait_for_io_events(socks, EVENT_WRITE, timeout) diff --git a/telegramer/include/telegram/version.py b/telegramer/include/telegram/version.py deleted file mode 100644 index f831eee..0000000 --- a/telegramer/include/telegram/version.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -# pylint: disable=C0114 - -from telegram import constants - -__version__ = '13.11' -bot_api_version = constants.BOT_API_VERSION # pylint: disable=C0103 diff --git a/telegramer/include/telegram/voicechat.py b/telegramer/include/telegram/voicechat.py deleted file mode 100644 index 1430f5d..0000000 --- a/telegramer/include/telegram/voicechat.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python -# pylint: disable=R0903 -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains objects related to Telegram voice chats.""" - -import datetime as dtm -from typing import TYPE_CHECKING, Any, Optional, List - -from telegram import TelegramObject, User -from telegram.utils.helpers import from_timestamp, to_timestamp -from telegram.utils.types import JSONDict - -if TYPE_CHECKING: - from telegram import Bot - - -class VoiceChatStarted(TelegramObject): - """ - This object represents a service message about a voice - chat started in the chat. Currently holds no information. - - .. versionadded:: 13.4 - """ - - __slots__ = () - - def __init__(self, **_kwargs: Any): # skipcq: PTC-W0049 - pass - - -class VoiceChatEnded(TelegramObject): - """ - This object represents a service message about a - voice chat ended in the chat. - - Objects of this class are comparable in terms of equality. - Two objects of this class are considered equal, if their - :attr:`duration` are equal. - - .. versionadded:: 13.4 - - Args: - duration (:obj:`int`): Voice chat duration in seconds. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - duration (:obj:`int`): Voice chat duration in seconds. - - """ - - __slots__ = ('duration', '_id_attrs') - - def __init__(self, duration: int, **_kwargs: Any) -> None: - self.duration = int(duration) if duration is not None else None - self._id_attrs = (self.duration,) - - -class VoiceChatParticipantsInvited(TelegramObject): - """ - This object represents a service message about - new members invited to a voice chat. - - Objects of this class are comparable in terms of equality. - Two objects of this class are considered equal, if their - :attr:`users` are equal. - - .. versionadded:: 13.4 - - Args: - users (List[:class:`telegram.User`]): New members that - were invited to the voice chat. - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - users (List[:class:`telegram.User`]): New members that - were invited to the voice chat. - - """ - - __slots__ = ('users', '_id_attrs') - - def __init__(self, users: List[User], **_kwargs: Any) -> None: - self.users = users - self._id_attrs = (self.users,) - - def __hash__(self) -> int: - return hash(tuple(self.users)) - - @classmethod - def de_json( - cls, data: Optional[JSONDict], bot: 'Bot' - ) -> Optional['VoiceChatParticipantsInvited']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['users'] = User.de_list(data.get('users', []), bot) - return cls(**data) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - data["users"] = [u.to_dict() for u in self.users] - return data - - -class VoiceChatScheduled(TelegramObject): - """This object represents a service message about a voice chat scheduled in the chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`start_date` are equal. - - Args: - start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the voice - chat is supposed to be started by a chat administrator - **kwargs (:obj:`dict`): Arbitrary keyword arguments. - - Attributes: - start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the voice - chat is supposed to be started by a chat administrator - - """ - - __slots__ = ('start_date', '_id_attrs') - - def __init__(self, start_date: dtm.datetime, **_kwargs: Any) -> None: - self.start_date = start_date - - self._id_attrs = (self.start_date,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VoiceChatScheduled']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['start_date'] = from_timestamp(data['start_date']) - - return cls(**data, bot=bot) - - def to_dict(self) -> JSONDict: - """See :meth:`telegram.TelegramObject.to_dict`.""" - data = super().to_dict() - - # Required - data['start_date'] = to_timestamp(self.start_date) - - return data diff --git a/telegramer/include/telegram/webhookinfo.py b/telegramer/include/telegram/webhookinfo.py deleted file mode 100644 index 1611d6c..0000000 --- a/telegramer/include/telegram/webhookinfo.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015-2022 -# Leandro Toledo de Souza -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser Public License for more details. -# -# You should have received a copy of the GNU Lesser Public License -# along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains an object that represents a Telegram WebhookInfo.""" - -from typing import Any, List - -from telegram import TelegramObject - - -class WebhookInfo(TelegramObject): - """This object represents a Telegram WebhookInfo. - - Contains information about the current status of a webhook. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`url`, :attr:`has_custom_certificate`, - :attr:`pending_update_count`, :attr:`ip_address`, :attr:`last_error_date`, - :attr:`last_error_message`, :attr:`max_connections` and :attr:`allowed_updates` are equal. - - Args: - url (:obj:`str`): Webhook URL, may be empty if webhook is not set up. - has_custom_certificate (:obj:`bool`): :obj:`True`, if a custom certificate was provided for - webhook certificate checks. - pending_update_count (:obj:`int`): Number of updates awaiting delivery. - ip_address (:obj:`str`, optional): Currently used webhook IP address. - last_error_date (:obj:`int`, optional): Unix time for the most recent error that happened - when trying to deliver an update via webhook. - last_error_message (:obj:`str`, optional): Error message in human-readable format for the - most recent error that happened when trying to deliver an update via webhook. - max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS - connections to the webhook for update delivery. - allowed_updates (List[:obj:`str`], optional): A list of update types the bot is subscribed - to. Defaults to all update types, except :attr:`telegram.Update.chat_member`. - - Attributes: - url (:obj:`str`): Webhook URL. - has_custom_certificate (:obj:`bool`): If a custom certificate was provided for webhook. - pending_update_count (:obj:`int`): Number of updates awaiting delivery. - ip_address (:obj:`str`): Optional. Currently used webhook IP address. - last_error_date (:obj:`int`): Optional. Unix time for the most recent error that happened. - last_error_message (:obj:`str`): Optional. Error message in human-readable format. - max_connections (:obj:`int`): Optional. Maximum allowed number of simultaneous HTTPS - connections. - allowed_updates (List[:obj:`str`]): Optional. A list of update types the bot is subscribed - to. Defaults to all update types, except :attr:`telegram.Update.chat_member`. - - """ - - __slots__ = ( - 'allowed_updates', - 'url', - 'max_connections', - 'last_error_date', - 'ip_address', - 'last_error_message', - 'pending_update_count', - 'has_custom_certificate', - '_id_attrs', - ) - - def __init__( - self, - url: str, - has_custom_certificate: bool, - pending_update_count: int, - last_error_date: int = None, - last_error_message: str = None, - max_connections: int = None, - allowed_updates: List[str] = None, - ip_address: str = None, - **_kwargs: Any, - ): - # Required - self.url = url - self.has_custom_certificate = has_custom_certificate - self.pending_update_count = pending_update_count - - # Optional - self.ip_address = ip_address - self.last_error_date = last_error_date - self.last_error_message = last_error_message - self.max_connections = max_connections - self.allowed_updates = allowed_updates - - self._id_attrs = ( - self.url, - self.has_custom_certificate, - self.pending_update_count, - self.ip_address, - self.last_error_date, - self.last_error_message, - self.max_connections, - self.allowed_updates, - ) diff --git a/telegramer/include/tornado/__init__.py b/telegramer/include/tornado/__init__.py deleted file mode 100644 index a5f45e5..0000000 --- a/telegramer/include/tornado/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""The Tornado web server and tools.""" - -# version is a human-readable version number. - -# version_info is a four-tuple for programmatic comparison. The first -# three numbers are the components of the version number. The fourth -# is zero for an official release, positive for a development branch, -# or negative for a release candidate or beta (after the base version -# number has been incremented) -version = "6.1" -version_info = (6, 1, 0, 0) diff --git a/telegramer/include/tornado/_locale_data.py b/telegramer/include/tornado/_locale_data.py deleted file mode 100644 index c706230..0000000 --- a/telegramer/include/tornado/_locale_data.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2012 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Data used by the tornado.locale module.""" - -LOCALE_NAMES = { - "af_ZA": {"name_en": u"Afrikaans", "name": u"Afrikaans"}, - "am_ET": {"name_en": u"Amharic", "name": u"አማርኛ"}, - "ar_AR": {"name_en": u"Arabic", "name": u"العربية"}, - "bg_BG": {"name_en": u"Bulgarian", "name": u"Български"}, - "bn_IN": {"name_en": u"Bengali", "name": u"বাংলা"}, - "bs_BA": {"name_en": u"Bosnian", "name": u"Bosanski"}, - "ca_ES": {"name_en": u"Catalan", "name": u"Català"}, - "cs_CZ": {"name_en": u"Czech", "name": u"Čeština"}, - "cy_GB": {"name_en": u"Welsh", "name": u"Cymraeg"}, - "da_DK": {"name_en": u"Danish", "name": u"Dansk"}, - "de_DE": {"name_en": u"German", "name": u"Deutsch"}, - "el_GR": {"name_en": u"Greek", "name": u"Ελληνικά"}, - "en_GB": {"name_en": u"English (UK)", "name": u"English (UK)"}, - "en_US": {"name_en": u"English (US)", "name": u"English (US)"}, - "es_ES": {"name_en": u"Spanish (Spain)", "name": u"Español (España)"}, - "es_LA": {"name_en": u"Spanish", "name": u"Español"}, - "et_EE": {"name_en": u"Estonian", "name": u"Eesti"}, - "eu_ES": {"name_en": u"Basque", "name": u"Euskara"}, - "fa_IR": {"name_en": u"Persian", "name": u"فارسی"}, - "fi_FI": {"name_en": u"Finnish", "name": u"Suomi"}, - "fr_CA": {"name_en": u"French (Canada)", "name": u"Français (Canada)"}, - "fr_FR": {"name_en": u"French", "name": u"Français"}, - "ga_IE": {"name_en": u"Irish", "name": u"Gaeilge"}, - "gl_ES": {"name_en": u"Galician", "name": u"Galego"}, - "he_IL": {"name_en": u"Hebrew", "name": u"עברית"}, - "hi_IN": {"name_en": u"Hindi", "name": u"हिन्दी"}, - "hr_HR": {"name_en": u"Croatian", "name": u"Hrvatski"}, - "hu_HU": {"name_en": u"Hungarian", "name": u"Magyar"}, - "id_ID": {"name_en": u"Indonesian", "name": u"Bahasa Indonesia"}, - "is_IS": {"name_en": u"Icelandic", "name": u"Íslenska"}, - "it_IT": {"name_en": u"Italian", "name": u"Italiano"}, - "ja_JP": {"name_en": u"Japanese", "name": u"日本語"}, - "ko_KR": {"name_en": u"Korean", "name": u"한국어"}, - "lt_LT": {"name_en": u"Lithuanian", "name": u"Lietuvių"}, - "lv_LV": {"name_en": u"Latvian", "name": u"Latviešu"}, - "mk_MK": {"name_en": u"Macedonian", "name": u"Македонски"}, - "ml_IN": {"name_en": u"Malayalam", "name": u"മലയാളം"}, - "ms_MY": {"name_en": u"Malay", "name": u"Bahasa Melayu"}, - "nb_NO": {"name_en": u"Norwegian (bokmal)", "name": u"Norsk (bokmål)"}, - "nl_NL": {"name_en": u"Dutch", "name": u"Nederlands"}, - "nn_NO": {"name_en": u"Norwegian (nynorsk)", "name": u"Norsk (nynorsk)"}, - "pa_IN": {"name_en": u"Punjabi", "name": u"ਪੰਜਾਬੀ"}, - "pl_PL": {"name_en": u"Polish", "name": u"Polski"}, - "pt_BR": {"name_en": u"Portuguese (Brazil)", "name": u"Português (Brasil)"}, - "pt_PT": {"name_en": u"Portuguese (Portugal)", "name": u"Português (Portugal)"}, - "ro_RO": {"name_en": u"Romanian", "name": u"Română"}, - "ru_RU": {"name_en": u"Russian", "name": u"Русский"}, - "sk_SK": {"name_en": u"Slovak", "name": u"Slovenčina"}, - "sl_SI": {"name_en": u"Slovenian", "name": u"Slovenščina"}, - "sq_AL": {"name_en": u"Albanian", "name": u"Shqip"}, - "sr_RS": {"name_en": u"Serbian", "name": u"Српски"}, - "sv_SE": {"name_en": u"Swedish", "name": u"Svenska"}, - "sw_KE": {"name_en": u"Swahili", "name": u"Kiswahili"}, - "ta_IN": {"name_en": u"Tamil", "name": u"தமிழ்"}, - "te_IN": {"name_en": u"Telugu", "name": u"తెలుగు"}, - "th_TH": {"name_en": u"Thai", "name": u"ภาษาไทย"}, - "tl_PH": {"name_en": u"Filipino", "name": u"Filipino"}, - "tr_TR": {"name_en": u"Turkish", "name": u"Türkçe"}, - "uk_UA": {"name_en": u"Ukraini ", "name": u"Українська"}, - "vi_VN": {"name_en": u"Vietnamese", "name": u"Tiếng Việt"}, - "zh_CN": {"name_en": u"Chinese (Simplified)", "name": u"中文(简体)"}, - "zh_TW": {"name_en": u"Chinese (Traditional)", "name": u"中文(繁體)"}, -} diff --git a/telegramer/include/tornado/auth.py b/telegramer/include/tornado/auth.py deleted file mode 100644 index 5f1068c..0000000 --- a/telegramer/include/tornado/auth.py +++ /dev/null @@ -1,1187 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""This module contains implementations of various third-party -authentication schemes. - -All the classes in this file are class mixins designed to be used with -the `tornado.web.RequestHandler` class. They are used in two ways: - -* On a login handler, use methods such as ``authenticate_redirect()``, - ``authorize_redirect()``, and ``get_authenticated_user()`` to - establish the user's identity and store authentication tokens to your - database and/or cookies. -* In non-login handlers, use methods such as ``facebook_request()`` - or ``twitter_request()`` to use the authentication tokens to make - requests to the respective services. - -They all take slightly different arguments due to the fact all these -services implement authentication and authorization slightly differently. -See the individual service classes below for complete documentation. - -Example usage for Google OAuth: - -.. testcode:: - - class GoogleOAuth2LoginHandler(tornado.web.RequestHandler, - tornado.auth.GoogleOAuth2Mixin): - async def get(self): - if self.get_argument('code', False): - user = await self.get_authenticated_user( - redirect_uri='http://your.site.com/auth/google', - code=self.get_argument('code')) - # Save the user with e.g. set_secure_cookie - else: - self.authorize_redirect( - redirect_uri='http://your.site.com/auth/google', - client_id=self.settings['google_oauth']['key'], - scope=['profile', 'email'], - response_type='code', - extra_params={'approval_prompt': 'auto'}) - -.. testoutput:: - :hide: - -""" - -import base64 -import binascii -import hashlib -import hmac -import time -import urllib.parse -import uuid - -from tornado import httpclient -from tornado import escape -from tornado.httputil import url_concat -from tornado.util import unicode_type -from tornado.web import RequestHandler - -from typing import List, Any, Dict, cast, Iterable, Union, Optional - - -class AuthError(Exception): - pass - - -class OpenIdMixin(object): - """Abstract implementation of OpenID and Attribute Exchange. - - Class attributes: - - * ``_OPENID_ENDPOINT``: the identity provider's URI. - """ - - def authenticate_redirect( - self, - callback_uri: Optional[str] = None, - ax_attrs: List[str] = ["name", "email", "language", "username"], - ) -> None: - """Redirects to the authentication URL for this service. - - After authentication, the service will redirect back to the given - callback URI with additional parameters including ``openid.mode``. - - We request the given attributes for the authenticated user by - default (name, email, language, and username). If you don't need - all those attributes for your app, you can request fewer with - the ax_attrs keyword argument. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed and this method no - longer returns an awaitable object. It is now an ordinary - synchronous function. - """ - handler = cast(RequestHandler, self) - callback_uri = callback_uri or handler.request.uri - assert callback_uri is not None - args = self._openid_args(callback_uri, ax_attrs=ax_attrs) - endpoint = self._OPENID_ENDPOINT # type: ignore - handler.redirect(endpoint + "?" + urllib.parse.urlencode(args)) - - async def get_authenticated_user( - self, http_client: Optional[httpclient.AsyncHTTPClient] = None - ) -> Dict[str, Any]: - """Fetches the authenticated user data upon redirect. - - This method should be called by the handler that receives the - redirect from the `authenticate_redirect()` method (which is - often the same as the one that calls it; in that case you would - call `get_authenticated_user` if the ``openid.mode`` parameter - is present and `authenticate_redirect` if it is not). - - The result of this method will generally be used to set a cookie. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - awaitable object instead. - """ - handler = cast(RequestHandler, self) - # Verify the OpenID response via direct request to the OP - args = dict( - (k, v[-1]) for k, v in handler.request.arguments.items() - ) # type: Dict[str, Union[str, bytes]] - args["openid.mode"] = u"check_authentication" - url = self._OPENID_ENDPOINT # type: ignore - if http_client is None: - http_client = self.get_auth_http_client() - resp = await http_client.fetch( - url, method="POST", body=urllib.parse.urlencode(args) - ) - return self._on_authentication_verified(resp) - - def _openid_args( - self, - callback_uri: str, - ax_attrs: Iterable[str] = [], - oauth_scope: Optional[str] = None, - ) -> Dict[str, str]: - handler = cast(RequestHandler, self) - url = urllib.parse.urljoin(handler.request.full_url(), callback_uri) - args = { - "openid.ns": "http://specs.openid.net/auth/2.0", - "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select", - "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select", - "openid.return_to": url, - "openid.realm": urllib.parse.urljoin(url, "/"), - "openid.mode": "checkid_setup", - } - if ax_attrs: - args.update( - { - "openid.ns.ax": "http://openid.net/srv/ax/1.0", - "openid.ax.mode": "fetch_request", - } - ) - ax_attrs = set(ax_attrs) - required = [] # type: List[str] - if "name" in ax_attrs: - ax_attrs -= set(["name", "firstname", "fullname", "lastname"]) - required += ["firstname", "fullname", "lastname"] - args.update( - { - "openid.ax.type.firstname": "http://axschema.org/namePerson/first", - "openid.ax.type.fullname": "http://axschema.org/namePerson", - "openid.ax.type.lastname": "http://axschema.org/namePerson/last", - } - ) - known_attrs = { - "email": "http://axschema.org/contact/email", - "language": "http://axschema.org/pref/language", - "username": "http://axschema.org/namePerson/friendly", - } - for name in ax_attrs: - args["openid.ax.type." + name] = known_attrs[name] - required.append(name) - args["openid.ax.required"] = ",".join(required) - if oauth_scope: - args.update( - { - "openid.ns.oauth": "http://specs.openid.net/extensions/oauth/1.0", - "openid.oauth.consumer": handler.request.host.split(":")[0], - "openid.oauth.scope": oauth_scope, - } - ) - return args - - def _on_authentication_verified( - self, response: httpclient.HTTPResponse - ) -> Dict[str, Any]: - handler = cast(RequestHandler, self) - if b"is_valid:true" not in response.body: - raise AuthError("Invalid OpenID response: %r" % response.body) - - # Make sure we got back at least an email from attribute exchange - ax_ns = None - for key in handler.request.arguments: - if ( - key.startswith("openid.ns.") - and handler.get_argument(key) == u"http://openid.net/srv/ax/1.0" - ): - ax_ns = key[10:] - break - - def get_ax_arg(uri: str) -> str: - if not ax_ns: - return u"" - prefix = "openid." + ax_ns + ".type." - ax_name = None - for name in handler.request.arguments.keys(): - if handler.get_argument(name) == uri and name.startswith(prefix): - part = name[len(prefix) :] - ax_name = "openid." + ax_ns + ".value." + part - break - if not ax_name: - return u"" - return handler.get_argument(ax_name, u"") - - email = get_ax_arg("http://axschema.org/contact/email") - name = get_ax_arg("http://axschema.org/namePerson") - first_name = get_ax_arg("http://axschema.org/namePerson/first") - last_name = get_ax_arg("http://axschema.org/namePerson/last") - username = get_ax_arg("http://axschema.org/namePerson/friendly") - locale = get_ax_arg("http://axschema.org/pref/language").lower() - user = dict() - name_parts = [] - if first_name: - user["first_name"] = first_name - name_parts.append(first_name) - if last_name: - user["last_name"] = last_name - name_parts.append(last_name) - if name: - user["name"] = name - elif name_parts: - user["name"] = u" ".join(name_parts) - elif email: - user["name"] = email.split("@")[0] - if email: - user["email"] = email - if locale: - user["locale"] = locale - if username: - user["username"] = username - claimed_id = handler.get_argument("openid.claimed_id", None) - if claimed_id: - user["claimed_id"] = claimed_id - return user - - def get_auth_http_client(self) -> httpclient.AsyncHTTPClient: - """Returns the `.AsyncHTTPClient` instance to be used for auth requests. - - May be overridden by subclasses to use an HTTP client other than - the default. - """ - return httpclient.AsyncHTTPClient() - - -class OAuthMixin(object): - """Abstract implementation of OAuth 1.0 and 1.0a. - - See `TwitterMixin` below for an example implementation. - - Class attributes: - - * ``_OAUTH_AUTHORIZE_URL``: The service's OAuth authorization url. - * ``_OAUTH_ACCESS_TOKEN_URL``: The service's OAuth access token url. - * ``_OAUTH_VERSION``: May be either "1.0" or "1.0a". - * ``_OAUTH_NO_CALLBACKS``: Set this to True if the service requires - advance registration of callbacks. - - Subclasses must also override the `_oauth_get_user_future` and - `_oauth_consumer_token` methods. - """ - - async def authorize_redirect( - self, - callback_uri: Optional[str] = None, - extra_params: Optional[Dict[str, Any]] = None, - http_client: Optional[httpclient.AsyncHTTPClient] = None, - ) -> None: - """Redirects the user to obtain OAuth authorization for this service. - - The ``callback_uri`` may be omitted if you have previously - registered a callback URI with the third-party service. For - some services, you must use a previously-registered callback - URI and cannot specify a callback via this method. - - This method sets a cookie called ``_oauth_request_token`` which is - subsequently used (and cleared) in `get_authenticated_user` for - security purposes. - - This method is asynchronous and must be called with ``await`` - or ``yield`` (This is different from other ``auth*_redirect`` - methods defined in this module). It calls - `.RequestHandler.finish` for you so you should not write any - other response after it returns. - - .. versionchanged:: 3.1 - Now returns a `.Future` and takes an optional callback, for - compatibility with `.gen.coroutine`. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - awaitable object instead. - - """ - if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False): - raise Exception("This service does not support oauth_callback") - if http_client is None: - http_client = self.get_auth_http_client() - assert http_client is not None - if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": - response = await http_client.fetch( - self._oauth_request_token_url( - callback_uri=callback_uri, extra_params=extra_params - ) - ) - else: - response = await http_client.fetch(self._oauth_request_token_url()) - url = self._OAUTH_AUTHORIZE_URL # type: ignore - self._on_request_token(url, callback_uri, response) - - async def get_authenticated_user( - self, http_client: Optional[httpclient.AsyncHTTPClient] = None - ) -> Dict[str, Any]: - """Gets the OAuth authorized user and access token. - - This method should be called from the handler for your - OAuth callback URL to complete the registration process. We run the - callback with the authenticated user dictionary. This dictionary - will contain an ``access_key`` which can be used to make authorized - requests to this service on behalf of the user. The dictionary will - also contain other fields such as ``name``, depending on the service - used. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - awaitable object instead. - """ - handler = cast(RequestHandler, self) - request_key = escape.utf8(handler.get_argument("oauth_token")) - oauth_verifier = handler.get_argument("oauth_verifier", None) - request_cookie = handler.get_cookie("_oauth_request_token") - if not request_cookie: - raise AuthError("Missing OAuth request token cookie") - handler.clear_cookie("_oauth_request_token") - cookie_key, cookie_secret = [ - base64.b64decode(escape.utf8(i)) for i in request_cookie.split("|") - ] - if cookie_key != request_key: - raise AuthError("Request token does not match cookie") - token = dict( - key=cookie_key, secret=cookie_secret - ) # type: Dict[str, Union[str, bytes]] - if oauth_verifier: - token["verifier"] = oauth_verifier - if http_client is None: - http_client = self.get_auth_http_client() - assert http_client is not None - response = await http_client.fetch(self._oauth_access_token_url(token)) - access_token = _oauth_parse_response(response.body) - user = await self._oauth_get_user_future(access_token) - if not user: - raise AuthError("Error getting user") - user["access_token"] = access_token - return user - - def _oauth_request_token_url( - self, - callback_uri: Optional[str] = None, - extra_params: Optional[Dict[str, Any]] = None, - ) -> str: - handler = cast(RequestHandler, self) - consumer_token = self._oauth_consumer_token() - url = self._OAUTH_REQUEST_TOKEN_URL # type: ignore - args = dict( - oauth_consumer_key=escape.to_basestring(consumer_token["key"]), - oauth_signature_method="HMAC-SHA1", - oauth_timestamp=str(int(time.time())), - oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)), - oauth_version="1.0", - ) - if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": - if callback_uri == "oob": - args["oauth_callback"] = "oob" - elif callback_uri: - args["oauth_callback"] = urllib.parse.urljoin( - handler.request.full_url(), callback_uri - ) - if extra_params: - args.update(extra_params) - signature = _oauth10a_signature(consumer_token, "GET", url, args) - else: - signature = _oauth_signature(consumer_token, "GET", url, args) - - args["oauth_signature"] = signature - return url + "?" + urllib.parse.urlencode(args) - - def _on_request_token( - self, - authorize_url: str, - callback_uri: Optional[str], - response: httpclient.HTTPResponse, - ) -> None: - handler = cast(RequestHandler, self) - request_token = _oauth_parse_response(response.body) - data = ( - base64.b64encode(escape.utf8(request_token["key"])) - + b"|" - + base64.b64encode(escape.utf8(request_token["secret"])) - ) - handler.set_cookie("_oauth_request_token", data) - args = dict(oauth_token=request_token["key"]) - if callback_uri == "oob": - handler.finish(authorize_url + "?" + urllib.parse.urlencode(args)) - return - elif callback_uri: - args["oauth_callback"] = urllib.parse.urljoin( - handler.request.full_url(), callback_uri - ) - handler.redirect(authorize_url + "?" + urllib.parse.urlencode(args)) - - def _oauth_access_token_url(self, request_token: Dict[str, Any]) -> str: - consumer_token = self._oauth_consumer_token() - url = self._OAUTH_ACCESS_TOKEN_URL # type: ignore - args = dict( - oauth_consumer_key=escape.to_basestring(consumer_token["key"]), - oauth_token=escape.to_basestring(request_token["key"]), - oauth_signature_method="HMAC-SHA1", - oauth_timestamp=str(int(time.time())), - oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)), - oauth_version="1.0", - ) - if "verifier" in request_token: - args["oauth_verifier"] = request_token["verifier"] - - if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": - signature = _oauth10a_signature( - consumer_token, "GET", url, args, request_token - ) - else: - signature = _oauth_signature( - consumer_token, "GET", url, args, request_token - ) - - args["oauth_signature"] = signature - return url + "?" + urllib.parse.urlencode(args) - - def _oauth_consumer_token(self) -> Dict[str, Any]: - """Subclasses must override this to return their OAuth consumer keys. - - The return value should be a `dict` with keys ``key`` and ``secret``. - """ - raise NotImplementedError() - - async def _oauth_get_user_future( - self, access_token: Dict[str, Any] - ) -> Dict[str, Any]: - """Subclasses must override this to get basic information about the - user. - - Should be a coroutine whose result is a dictionary - containing information about the user, which may have been - retrieved by using ``access_token`` to make a request to the - service. - - The access token will be added to the returned dictionary to make - the result of `get_authenticated_user`. - - .. versionchanged:: 5.1 - - Subclasses may also define this method with ``async def``. - - .. versionchanged:: 6.0 - - A synchronous fallback to ``_oauth_get_user`` was removed. - """ - raise NotImplementedError() - - def _oauth_request_parameters( - self, - url: str, - access_token: Dict[str, Any], - parameters: Dict[str, Any] = {}, - method: str = "GET", - ) -> Dict[str, Any]: - """Returns the OAuth parameters as a dict for the given request. - - parameters should include all POST arguments and query string arguments - that will be sent with the request. - """ - consumer_token = self._oauth_consumer_token() - base_args = dict( - oauth_consumer_key=escape.to_basestring(consumer_token["key"]), - oauth_token=escape.to_basestring(access_token["key"]), - oauth_signature_method="HMAC-SHA1", - oauth_timestamp=str(int(time.time())), - oauth_nonce=escape.to_basestring(binascii.b2a_hex(uuid.uuid4().bytes)), - oauth_version="1.0", - ) - args = {} - args.update(base_args) - args.update(parameters) - if getattr(self, "_OAUTH_VERSION", "1.0a") == "1.0a": - signature = _oauth10a_signature( - consumer_token, method, url, args, access_token - ) - else: - signature = _oauth_signature( - consumer_token, method, url, args, access_token - ) - base_args["oauth_signature"] = escape.to_basestring(signature) - return base_args - - def get_auth_http_client(self) -> httpclient.AsyncHTTPClient: - """Returns the `.AsyncHTTPClient` instance to be used for auth requests. - - May be overridden by subclasses to use an HTTP client other than - the default. - """ - return httpclient.AsyncHTTPClient() - - -class OAuth2Mixin(object): - """Abstract implementation of OAuth 2.0. - - See `FacebookGraphMixin` or `GoogleOAuth2Mixin` below for example - implementations. - - Class attributes: - - * ``_OAUTH_AUTHORIZE_URL``: The service's authorization url. - * ``_OAUTH_ACCESS_TOKEN_URL``: The service's access token url. - """ - - def authorize_redirect( - self, - redirect_uri: Optional[str] = None, - client_id: Optional[str] = None, - client_secret: Optional[str] = None, - extra_params: Optional[Dict[str, Any]] = None, - scope: Optional[List[str]] = None, - response_type: str = "code", - ) -> None: - """Redirects the user to obtain OAuth authorization for this service. - - Some providers require that you register a redirect URL with - your application instead of passing one via this method. You - should call this method to log the user in, and then call - ``get_authenticated_user`` in the handler for your - redirect URL to complete the authorization process. - - .. versionchanged:: 6.0 - - The ``callback`` argument and returned awaitable were removed; - this is now an ordinary synchronous function. - """ - handler = cast(RequestHandler, self) - args = {"response_type": response_type} - if redirect_uri is not None: - args["redirect_uri"] = redirect_uri - if client_id is not None: - args["client_id"] = client_id - if extra_params: - args.update(extra_params) - if scope: - args["scope"] = " ".join(scope) - url = self._OAUTH_AUTHORIZE_URL # type: ignore - handler.redirect(url_concat(url, args)) - - def _oauth_request_token_url( - self, - redirect_uri: Optional[str] = None, - client_id: Optional[str] = None, - client_secret: Optional[str] = None, - code: Optional[str] = None, - extra_params: Optional[Dict[str, Any]] = None, - ) -> str: - url = self._OAUTH_ACCESS_TOKEN_URL # type: ignore - args = {} # type: Dict[str, str] - if redirect_uri is not None: - args["redirect_uri"] = redirect_uri - if code is not None: - args["code"] = code - if client_id is not None: - args["client_id"] = client_id - if client_secret is not None: - args["client_secret"] = client_secret - if extra_params: - args.update(extra_params) - return url_concat(url, args) - - async def oauth2_request( - self, - url: str, - access_token: Optional[str] = None, - post_args: Optional[Dict[str, Any]] = None, - **args: Any - ) -> Any: - """Fetches the given URL auth an OAuth2 access token. - - If the request is a POST, ``post_args`` should be provided. Query - string arguments should be given as keyword arguments. - - Example usage: - - ..testcode:: - - class MainHandler(tornado.web.RequestHandler, - tornado.auth.FacebookGraphMixin): - @tornado.web.authenticated - async def get(self): - new_entry = await self.oauth2_request( - "https://graph.facebook.com/me/feed", - post_args={"message": "I am posting from my Tornado application!"}, - access_token=self.current_user["access_token"]) - - if not new_entry: - # Call failed; perhaps missing permission? - self.authorize_redirect() - return - self.finish("Posted a message!") - - .. testoutput:: - :hide: - - .. versionadded:: 4.3 - - .. versionchanged::: 6.0 - - The ``callback`` argument was removed. Use the returned awaitable object instead. - """ - all_args = {} - if access_token: - all_args["access_token"] = access_token - all_args.update(args) - - if all_args: - url += "?" + urllib.parse.urlencode(all_args) - http = self.get_auth_http_client() - if post_args is not None: - response = await http.fetch( - url, method="POST", body=urllib.parse.urlencode(post_args) - ) - else: - response = await http.fetch(url) - return escape.json_decode(response.body) - - def get_auth_http_client(self) -> httpclient.AsyncHTTPClient: - """Returns the `.AsyncHTTPClient` instance to be used for auth requests. - - May be overridden by subclasses to use an HTTP client other than - the default. - - .. versionadded:: 4.3 - """ - return httpclient.AsyncHTTPClient() - - -class TwitterMixin(OAuthMixin): - """Twitter OAuth authentication. - - To authenticate with Twitter, register your application with - Twitter at http://twitter.com/apps. Then copy your Consumer Key - and Consumer Secret to the application - `~tornado.web.Application.settings` ``twitter_consumer_key`` and - ``twitter_consumer_secret``. Use this mixin on the handler for the - URL you registered as your application's callback URL. - - When your application is set up, you can use this mixin like this - to authenticate the user with Twitter and get access to their stream: - - .. testcode:: - - class TwitterLoginHandler(tornado.web.RequestHandler, - tornado.auth.TwitterMixin): - async def get(self): - if self.get_argument("oauth_token", None): - user = await self.get_authenticated_user() - # Save the user using e.g. set_secure_cookie() - else: - await self.authorize_redirect() - - .. testoutput:: - :hide: - - The user object returned by `~OAuthMixin.get_authenticated_user` - includes the attributes ``username``, ``name``, ``access_token``, - and all of the custom Twitter user attributes described at - https://dev.twitter.com/docs/api/1.1/get/users/show - """ - - _OAUTH_REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token" - _OAUTH_ACCESS_TOKEN_URL = "https://api.twitter.com/oauth/access_token" - _OAUTH_AUTHORIZE_URL = "https://api.twitter.com/oauth/authorize" - _OAUTH_AUTHENTICATE_URL = "https://api.twitter.com/oauth/authenticate" - _OAUTH_NO_CALLBACKS = False - _TWITTER_BASE_URL = "https://api.twitter.com/1.1" - - async def authenticate_redirect(self, callback_uri: Optional[str] = None) -> None: - """Just like `~OAuthMixin.authorize_redirect`, but - auto-redirects if authorized. - - This is generally the right interface to use if you are using - Twitter for single-sign on. - - .. versionchanged:: 3.1 - Now returns a `.Future` and takes an optional callback, for - compatibility with `.gen.coroutine`. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - awaitable object instead. - """ - http = self.get_auth_http_client() - response = await http.fetch( - self._oauth_request_token_url(callback_uri=callback_uri) - ) - self._on_request_token(self._OAUTH_AUTHENTICATE_URL, None, response) - - async def twitter_request( - self, - path: str, - access_token: Dict[str, Any], - post_args: Optional[Dict[str, Any]] = None, - **args: Any - ) -> Any: - """Fetches the given API path, e.g., ``statuses/user_timeline/btaylor`` - - The path should not include the format or API version number. - (we automatically use JSON format and API version 1). - - If the request is a POST, ``post_args`` should be provided. Query - string arguments should be given as keyword arguments. - - All the Twitter methods are documented at http://dev.twitter.com/ - - Many methods require an OAuth access token which you can - obtain through `~OAuthMixin.authorize_redirect` and - `~OAuthMixin.get_authenticated_user`. The user returned through that - process includes an 'access_token' attribute that can be used - to make authenticated requests via this method. Example - usage: - - .. testcode:: - - class MainHandler(tornado.web.RequestHandler, - tornado.auth.TwitterMixin): - @tornado.web.authenticated - async def get(self): - new_entry = await self.twitter_request( - "/statuses/update", - post_args={"status": "Testing Tornado Web Server"}, - access_token=self.current_user["access_token"]) - if not new_entry: - # Call failed; perhaps missing permission? - await self.authorize_redirect() - return - self.finish("Posted a message!") - - .. testoutput:: - :hide: - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - awaitable object instead. - """ - if path.startswith("http:") or path.startswith("https:"): - # Raw urls are useful for e.g. search which doesn't follow the - # usual pattern: http://search.twitter.com/search.json - url = path - else: - url = self._TWITTER_BASE_URL + path + ".json" - # Add the OAuth resource request signature if we have credentials - if access_token: - all_args = {} - all_args.update(args) - all_args.update(post_args or {}) - method = "POST" if post_args is not None else "GET" - oauth = self._oauth_request_parameters( - url, access_token, all_args, method=method - ) - args.update(oauth) - if args: - url += "?" + urllib.parse.urlencode(args) - http = self.get_auth_http_client() - if post_args is not None: - response = await http.fetch( - url, method="POST", body=urllib.parse.urlencode(post_args) - ) - else: - response = await http.fetch(url) - return escape.json_decode(response.body) - - def _oauth_consumer_token(self) -> Dict[str, Any]: - handler = cast(RequestHandler, self) - handler.require_setting("twitter_consumer_key", "Twitter OAuth") - handler.require_setting("twitter_consumer_secret", "Twitter OAuth") - return dict( - key=handler.settings["twitter_consumer_key"], - secret=handler.settings["twitter_consumer_secret"], - ) - - async def _oauth_get_user_future( - self, access_token: Dict[str, Any] - ) -> Dict[str, Any]: - user = await self.twitter_request( - "/account/verify_credentials", access_token=access_token - ) - if user: - user["username"] = user["screen_name"] - return user - - -class GoogleOAuth2Mixin(OAuth2Mixin): - """Google authentication using OAuth2. - - In order to use, register your application with Google and copy the - relevant parameters to your application settings. - - * Go to the Google Dev Console at http://console.developers.google.com - * Select a project, or create a new one. - * In the sidebar on the left, select APIs & Auth. - * In the list of APIs, find the Google+ API service and set it to ON. - * In the sidebar on the left, select Credentials. - * In the OAuth section of the page, select Create New Client ID. - * Set the Redirect URI to point to your auth handler - * Copy the "Client secret" and "Client ID" to the application settings as - ``{"google_oauth": {"key": CLIENT_ID, "secret": CLIENT_SECRET}}`` - - .. versionadded:: 3.2 - """ - - _OAUTH_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth" - _OAUTH_ACCESS_TOKEN_URL = "https://www.googleapis.com/oauth2/v4/token" - _OAUTH_USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo" - _OAUTH_NO_CALLBACKS = False - _OAUTH_SETTINGS_KEY = "google_oauth" - - async def get_authenticated_user( - self, redirect_uri: str, code: str - ) -> Dict[str, Any]: - """Handles the login for the Google user, returning an access token. - - The result is a dictionary containing an ``access_token`` field - ([among others](https://developers.google.com/identity/protocols/OAuth2WebServer#handlingtheresponse)). - Unlike other ``get_authenticated_user`` methods in this package, - this method does not return any additional information about the user. - The returned access token can be used with `OAuth2Mixin.oauth2_request` - to request additional information (perhaps from - ``https://www.googleapis.com/oauth2/v2/userinfo``) - - Example usage: - - .. testcode:: - - class GoogleOAuth2LoginHandler(tornado.web.RequestHandler, - tornado.auth.GoogleOAuth2Mixin): - async def get(self): - if self.get_argument('code', False): - access = await self.get_authenticated_user( - redirect_uri='http://your.site.com/auth/google', - code=self.get_argument('code')) - user = await self.oauth2_request( - "https://www.googleapis.com/oauth2/v1/userinfo", - access_token=access["access_token"]) - # Save the user and access token with - # e.g. set_secure_cookie. - else: - self.authorize_redirect( - redirect_uri='http://your.site.com/auth/google', - client_id=self.settings['google_oauth']['key'], - scope=['profile', 'email'], - response_type='code', - extra_params={'approval_prompt': 'auto'}) - - .. testoutput:: - :hide: - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned awaitable object instead. - """ # noqa: E501 - handler = cast(RequestHandler, self) - http = self.get_auth_http_client() - body = urllib.parse.urlencode( - { - "redirect_uri": redirect_uri, - "code": code, - "client_id": handler.settings[self._OAUTH_SETTINGS_KEY]["key"], - "client_secret": handler.settings[self._OAUTH_SETTINGS_KEY]["secret"], - "grant_type": "authorization_code", - } - ) - - response = await http.fetch( - self._OAUTH_ACCESS_TOKEN_URL, - method="POST", - headers={"Content-Type": "application/x-www-form-urlencoded"}, - body=body, - ) - return escape.json_decode(response.body) - - -class FacebookGraphMixin(OAuth2Mixin): - """Facebook authentication using the new Graph API and OAuth2.""" - - _OAUTH_ACCESS_TOKEN_URL = "https://graph.facebook.com/oauth/access_token?" - _OAUTH_AUTHORIZE_URL = "https://www.facebook.com/dialog/oauth?" - _OAUTH_NO_CALLBACKS = False - _FACEBOOK_BASE_URL = "https://graph.facebook.com" - - async def get_authenticated_user( - self, - redirect_uri: str, - client_id: str, - client_secret: str, - code: str, - extra_fields: Optional[Dict[str, Any]] = None, - ) -> Optional[Dict[str, Any]]: - """Handles the login for the Facebook user, returning a user object. - - Example usage: - - .. testcode:: - - class FacebookGraphLoginHandler(tornado.web.RequestHandler, - tornado.auth.FacebookGraphMixin): - async def get(self): - if self.get_argument("code", False): - user = await self.get_authenticated_user( - redirect_uri='/auth/facebookgraph/', - client_id=self.settings["facebook_api_key"], - client_secret=self.settings["facebook_secret"], - code=self.get_argument("code")) - # Save the user with e.g. set_secure_cookie - else: - self.authorize_redirect( - redirect_uri='/auth/facebookgraph/', - client_id=self.settings["facebook_api_key"], - extra_params={"scope": "read_stream,offline_access"}) - - .. testoutput:: - :hide: - - This method returns a dictionary which may contain the following fields: - - * ``access_token``, a string which may be passed to `facebook_request` - * ``session_expires``, an integer encoded as a string representing - the time until the access token expires in seconds. This field should - be used like ``int(user['session_expires'])``; in a future version of - Tornado it will change from a string to an integer. - * ``id``, ``name``, ``first_name``, ``last_name``, ``locale``, ``picture``, - ``link``, plus any fields named in the ``extra_fields`` argument. These - fields are copied from the Facebook graph API - `user object `_ - - .. versionchanged:: 4.5 - The ``session_expires`` field was updated to support changes made to the - Facebook API in March 2017. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned awaitable object instead. - """ - http = self.get_auth_http_client() - args = { - "redirect_uri": redirect_uri, - "code": code, - "client_id": client_id, - "client_secret": client_secret, - } - - fields = set( - ["id", "name", "first_name", "last_name", "locale", "picture", "link"] - ) - if extra_fields: - fields.update(extra_fields) - - response = await http.fetch( - self._oauth_request_token_url(**args) # type: ignore - ) - args = escape.json_decode(response.body) - session = { - "access_token": args.get("access_token"), - "expires_in": args.get("expires_in"), - } - assert session["access_token"] is not None - - user = await self.facebook_request( - path="/me", - access_token=session["access_token"], - appsecret_proof=hmac.new( - key=client_secret.encode("utf8"), - msg=session["access_token"].encode("utf8"), - digestmod=hashlib.sha256, - ).hexdigest(), - fields=",".join(fields), - ) - - if user is None: - return None - - fieldmap = {} - for field in fields: - fieldmap[field] = user.get(field) - - # session_expires is converted to str for compatibility with - # older versions in which the server used url-encoding and - # this code simply returned the string verbatim. - # This should change in Tornado 5.0. - fieldmap.update( - { - "access_token": session["access_token"], - "session_expires": str(session.get("expires_in")), - } - ) - return fieldmap - - async def facebook_request( - self, - path: str, - access_token: Optional[str] = None, - post_args: Optional[Dict[str, Any]] = None, - **args: Any - ) -> Any: - """Fetches the given relative API path, e.g., "/btaylor/picture" - - If the request is a POST, ``post_args`` should be provided. Query - string arguments should be given as keyword arguments. - - An introduction to the Facebook Graph API can be found at - http://developers.facebook.com/docs/api - - Many methods require an OAuth access token which you can - obtain through `~OAuth2Mixin.authorize_redirect` and - `get_authenticated_user`. The user returned through that - process includes an ``access_token`` attribute that can be - used to make authenticated requests via this method. - - Example usage: - - .. testcode:: - - class MainHandler(tornado.web.RequestHandler, - tornado.auth.FacebookGraphMixin): - @tornado.web.authenticated - async def get(self): - new_entry = await self.facebook_request( - "/me/feed", - post_args={"message": "I am posting from my Tornado application!"}, - access_token=self.current_user["access_token"]) - - if not new_entry: - # Call failed; perhaps missing permission? - self.authorize_redirect() - return - self.finish("Posted a message!") - - .. testoutput:: - :hide: - - The given path is relative to ``self._FACEBOOK_BASE_URL``, - by default "https://graph.facebook.com". - - This method is a wrapper around `OAuth2Mixin.oauth2_request`; - the only difference is that this method takes a relative path, - while ``oauth2_request`` takes a complete url. - - .. versionchanged:: 3.1 - Added the ability to override ``self._FACEBOOK_BASE_URL``. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned awaitable object instead. - """ - url = self._FACEBOOK_BASE_URL + path - return await self.oauth2_request( - url, access_token=access_token, post_args=post_args, **args - ) - - -def _oauth_signature( - consumer_token: Dict[str, Any], - method: str, - url: str, - parameters: Dict[str, Any] = {}, - token: Optional[Dict[str, Any]] = None, -) -> bytes: - """Calculates the HMAC-SHA1 OAuth signature for the given request. - - See http://oauth.net/core/1.0/#signing_process - """ - parts = urllib.parse.urlparse(url) - scheme, netloc, path = parts[:3] - normalized_url = scheme.lower() + "://" + netloc.lower() + path - - base_elems = [] - base_elems.append(method.upper()) - base_elems.append(normalized_url) - base_elems.append( - "&".join( - "%s=%s" % (k, _oauth_escape(str(v))) for k, v in sorted(parameters.items()) - ) - ) - base_string = "&".join(_oauth_escape(e) for e in base_elems) - - key_elems = [escape.utf8(consumer_token["secret"])] - key_elems.append(escape.utf8(token["secret"] if token else "")) - key = b"&".join(key_elems) - - hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1) - return binascii.b2a_base64(hash.digest())[:-1] - - -def _oauth10a_signature( - consumer_token: Dict[str, Any], - method: str, - url: str, - parameters: Dict[str, Any] = {}, - token: Optional[Dict[str, Any]] = None, -) -> bytes: - """Calculates the HMAC-SHA1 OAuth 1.0a signature for the given request. - - See http://oauth.net/core/1.0a/#signing_process - """ - parts = urllib.parse.urlparse(url) - scheme, netloc, path = parts[:3] - normalized_url = scheme.lower() + "://" + netloc.lower() + path - - base_elems = [] - base_elems.append(method.upper()) - base_elems.append(normalized_url) - base_elems.append( - "&".join( - "%s=%s" % (k, _oauth_escape(str(v))) for k, v in sorted(parameters.items()) - ) - ) - - base_string = "&".join(_oauth_escape(e) for e in base_elems) - key_elems = [escape.utf8(urllib.parse.quote(consumer_token["secret"], safe="~"))] - key_elems.append( - escape.utf8(urllib.parse.quote(token["secret"], safe="~") if token else "") - ) - key = b"&".join(key_elems) - - hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1) - return binascii.b2a_base64(hash.digest())[:-1] - - -def _oauth_escape(val: Union[str, bytes]) -> str: - if isinstance(val, unicode_type): - val = val.encode("utf-8") - return urllib.parse.quote(val, safe="~") - - -def _oauth_parse_response(body: bytes) -> Dict[str, Any]: - # I can't find an officially-defined encoding for oauth responses and - # have never seen anyone use non-ascii. Leave the response in a byte - # string for python 2, and use utf8 on python 3. - body_str = escape.native_str(body) - p = urllib.parse.parse_qs(body_str, keep_blank_values=False) - token = dict(key=p["oauth_token"][0], secret=p["oauth_token_secret"][0]) - - # Add the extra parameters the Provider included to the token - special = ("oauth_token", "oauth_token_secret") - token.update((k, p[k][0]) for k in p if k not in special) - return token diff --git a/telegramer/include/tornado/autoreload.py b/telegramer/include/tornado/autoreload.py deleted file mode 100644 index 3299a3b..0000000 --- a/telegramer/include/tornado/autoreload.py +++ /dev/null @@ -1,363 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Automatically restart the server when a source file is modified. - -Most applications should not access this module directly. Instead, -pass the keyword argument ``autoreload=True`` to the -`tornado.web.Application` constructor (or ``debug=True``, which -enables this setting and several others). This will enable autoreload -mode as well as checking for changes to templates and static -resources. Note that restarting is a destructive operation and any -requests in progress will be aborted when the process restarts. (If -you want to disable autoreload while using other debug-mode features, -pass both ``debug=True`` and ``autoreload=False``). - -This module can also be used as a command-line wrapper around scripts -such as unit test runners. See the `main` method for details. - -The command-line wrapper and Application debug modes can be used together. -This combination is encouraged as the wrapper catches syntax errors and -other import-time failures, while debug mode catches changes once -the server has started. - -This module will not work correctly when `.HTTPServer`'s multi-process -mode is used. - -Reloading loses any Python interpreter command-line arguments (e.g. ``-u``) -because it re-executes Python using ``sys.executable`` and ``sys.argv``. -Additionally, modifying these variables will cause reloading to behave -incorrectly. - -""" - -import os -import sys - -# sys.path handling -# ----------------- -# -# If a module is run with "python -m", the current directory (i.e. "") -# is automatically prepended to sys.path, but not if it is run as -# "path/to/file.py". The processing for "-m" rewrites the former to -# the latter, so subsequent executions won't have the same path as the -# original. -# -# Conversely, when run as path/to/file.py, the directory containing -# file.py gets added to the path, which can cause confusion as imports -# may become relative in spite of the future import. -# -# We address the former problem by reconstructing the original command -# line (Python >= 3.4) or by setting the $PYTHONPATH environment -# variable (Python < 3.4) before re-execution so the new process will -# see the correct path. We attempt to address the latter problem when -# tornado.autoreload is run as __main__. - -if __name__ == "__main__": - # This sys.path manipulation must come before our imports (as much - # as possible - if we introduced a tornado.sys or tornado.os - # module we'd be in trouble), or else our imports would become - # relative again despite the future import. - # - # There is a separate __main__ block at the end of the file to call main(). - if sys.path[0] == os.path.dirname(__file__): - del sys.path[0] - -import functools -import logging -import os -import pkgutil # type: ignore -import sys -import traceback -import types -import subprocess -import weakref - -from tornado import ioloop -from tornado.log import gen_log -from tornado import process -from tornado.util import exec_in - -try: - import signal -except ImportError: - signal = None # type: ignore - -import typing -from typing import Callable, Dict - -if typing.TYPE_CHECKING: - from typing import List, Optional, Union # noqa: F401 - -# os.execv is broken on Windows and can't properly parse command line -# arguments and executable name if they contain whitespaces. subprocess -# fixes that behavior. -_has_execv = sys.platform != "win32" - -_watched_files = set() -_reload_hooks = [] -_reload_attempted = False -_io_loops = weakref.WeakKeyDictionary() # type: ignore -_autoreload_is_main = False -_original_argv = None # type: Optional[List[str]] -_original_spec = None - - -def start(check_time: int = 500) -> None: - """Begins watching source files for changes. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - """ - io_loop = ioloop.IOLoop.current() - if io_loop in _io_loops: - return - _io_loops[io_loop] = True - if len(_io_loops) > 1: - gen_log.warning("tornado.autoreload started more than once in the same process") - modify_times = {} # type: Dict[str, float] - callback = functools.partial(_reload_on_update, modify_times) - scheduler = ioloop.PeriodicCallback(callback, check_time) - scheduler.start() - - -def wait() -> None: - """Wait for a watched file to change, then restart the process. - - Intended to be used at the end of scripts like unit test runners, - to run the tests again after any source file changes (but see also - the command-line interface in `main`) - """ - io_loop = ioloop.IOLoop() - io_loop.add_callback(start) - io_loop.start() - - -def watch(filename: str) -> None: - """Add a file to the watch list. - - All imported modules are watched by default. - """ - _watched_files.add(filename) - - -def add_reload_hook(fn: Callable[[], None]) -> None: - """Add a function to be called before reloading the process. - - Note that for open file and socket handles it is generally - preferable to set the ``FD_CLOEXEC`` flag (using `fcntl` or - `os.set_inheritable`) instead of using a reload hook to close them. - """ - _reload_hooks.append(fn) - - -def _reload_on_update(modify_times: Dict[str, float]) -> None: - if _reload_attempted: - # We already tried to reload and it didn't work, so don't try again. - return - if process.task_id() is not None: - # We're in a child process created by fork_processes. If child - # processes restarted themselves, they'd all restart and then - # all call fork_processes again. - return - for module in list(sys.modules.values()): - # Some modules play games with sys.modules (e.g. email/__init__.py - # in the standard library), and occasionally this can cause strange - # failures in getattr. Just ignore anything that's not an ordinary - # module. - if not isinstance(module, types.ModuleType): - continue - path = getattr(module, "__file__", None) - if not path: - continue - if path.endswith(".pyc") or path.endswith(".pyo"): - path = path[:-1] - _check_file(modify_times, path) - for path in _watched_files: - _check_file(modify_times, path) - - -def _check_file(modify_times: Dict[str, float], path: str) -> None: - try: - modified = os.stat(path).st_mtime - except Exception: - return - if path not in modify_times: - modify_times[path] = modified - return - if modify_times[path] != modified: - gen_log.info("%s modified; restarting server", path) - _reload() - - -def _reload() -> None: - global _reload_attempted - _reload_attempted = True - for fn in _reload_hooks: - fn() - if hasattr(signal, "setitimer"): - # Clear the alarm signal set by - # ioloop.set_blocking_log_threshold so it doesn't fire - # after the exec. - signal.setitimer(signal.ITIMER_REAL, 0, 0) - # sys.path fixes: see comments at top of file. If __main__.__spec__ - # exists, we were invoked with -m and the effective path is about to - # change on re-exec. Reconstruct the original command line to - # ensure that the new process sees the same path we did. If - # __spec__ is not available (Python < 3.4), check instead if - # sys.path[0] is an empty string and add the current directory to - # $PYTHONPATH. - if _autoreload_is_main: - assert _original_argv is not None - spec = _original_spec - argv = _original_argv - else: - spec = getattr(sys.modules["__main__"], "__spec__", None) - argv = sys.argv - if spec: - argv = ["-m", spec.name] + argv[1:] - else: - path_prefix = "." + os.pathsep - if sys.path[0] == "" and not os.environ.get("PYTHONPATH", "").startswith( - path_prefix - ): - os.environ["PYTHONPATH"] = path_prefix + os.environ.get("PYTHONPATH", "") - if not _has_execv: - subprocess.Popen([sys.executable] + argv) - os._exit(0) - else: - try: - os.execv(sys.executable, [sys.executable] + argv) - except OSError: - # Mac OS X versions prior to 10.6 do not support execv in - # a process that contains multiple threads. Instead of - # re-executing in the current process, start a new one - # and cause the current process to exit. This isn't - # ideal since the new process is detached from the parent - # terminal and thus cannot easily be killed with ctrl-C, - # but it's better than not being able to autoreload at - # all. - # Unfortunately the errno returned in this case does not - # appear to be consistent, so we can't easily check for - # this error specifically. - os.spawnv( - os.P_NOWAIT, sys.executable, [sys.executable] + argv # type: ignore - ) - # At this point the IOLoop has been closed and finally - # blocks will experience errors if we allow the stack to - # unwind, so just exit uncleanly. - os._exit(0) - - -_USAGE = """\ -Usage: - python -m tornado.autoreload -m module.to.run [args...] - python -m tornado.autoreload path/to/script.py [args...] -""" - - -def main() -> None: - """Command-line wrapper to re-run a script whenever its source changes. - - Scripts may be specified by filename or module name:: - - python -m tornado.autoreload -m tornado.test.runtests - python -m tornado.autoreload tornado/test/runtests.py - - Running a script with this wrapper is similar to calling - `tornado.autoreload.wait` at the end of the script, but this wrapper - can catch import-time problems like syntax errors that would otherwise - prevent the script from reaching its call to `wait`. - """ - # Remember that we were launched with autoreload as main. - # The main module can be tricky; set the variables both in our globals - # (which may be __main__) and the real importable version. - import tornado.autoreload - - global _autoreload_is_main - global _original_argv, _original_spec - tornado.autoreload._autoreload_is_main = _autoreload_is_main = True - original_argv = sys.argv - tornado.autoreload._original_argv = _original_argv = original_argv - original_spec = getattr(sys.modules["__main__"], "__spec__", None) - tornado.autoreload._original_spec = _original_spec = original_spec - sys.argv = sys.argv[:] - if len(sys.argv) >= 3 and sys.argv[1] == "-m": - mode = "module" - module = sys.argv[2] - del sys.argv[1:3] - elif len(sys.argv) >= 2: - mode = "script" - script = sys.argv[1] - sys.argv = sys.argv[1:] - else: - print(_USAGE, file=sys.stderr) - sys.exit(1) - - try: - if mode == "module": - import runpy - - runpy.run_module(module, run_name="__main__", alter_sys=True) - elif mode == "script": - with open(script) as f: - # Execute the script in our namespace instead of creating - # a new one so that something that tries to import __main__ - # (e.g. the unittest module) will see names defined in the - # script instead of just those defined in this module. - global __file__ - __file__ = script - # If __package__ is defined, imports may be incorrectly - # interpreted as relative to this module. - global __package__ - del __package__ - exec_in(f.read(), globals(), globals()) - except SystemExit as e: - logging.basicConfig() - gen_log.info("Script exited with status %s", e.code) - except Exception as e: - logging.basicConfig() - gen_log.warning("Script exited with uncaught exception", exc_info=True) - # If an exception occurred at import time, the file with the error - # never made it into sys.modules and so we won't know to watch it. - # Just to make sure we've covered everything, walk the stack trace - # from the exception and watch every file. - for (filename, lineno, name, line) in traceback.extract_tb(sys.exc_info()[2]): - watch(filename) - if isinstance(e, SyntaxError): - # SyntaxErrors are special: their innermost stack frame is fake - # so extract_tb won't see it and we have to get the filename - # from the exception object. - watch(e.filename) - else: - logging.basicConfig() - gen_log.info("Script exited normally") - # restore sys.argv so subsequent executions will include autoreload - sys.argv = original_argv - - if mode == "module": - # runpy did a fake import of the module as __main__, but now it's - # no longer in sys.modules. Figure out where it is and watch it. - loader = pkgutil.get_loader(module) - if loader is not None: - watch(loader.get_filename()) # type: ignore - - wait() - - -if __name__ == "__main__": - # See also the other __main__ block at the top of the file, which modifies - # sys.path before our imports - main() diff --git a/telegramer/include/tornado/concurrent.py b/telegramer/include/tornado/concurrent.py deleted file mode 100644 index 7638fcf..0000000 --- a/telegramer/include/tornado/concurrent.py +++ /dev/null @@ -1,263 +0,0 @@ -# -# Copyright 2012 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -"""Utilities for working with ``Future`` objects. - -Tornado previously provided its own ``Future`` class, but now uses -`asyncio.Future`. This module contains utility functions for working -with `asyncio.Future` in a way that is backwards-compatible with -Tornado's old ``Future`` implementation. - -While this module is an important part of Tornado's internal -implementation, applications rarely need to interact with it -directly. - -""" - -import asyncio -from concurrent import futures -import functools -import sys -import types - -from tornado.log import app_log - -import typing -from typing import Any, Callable, Optional, Tuple, Union - -_T = typing.TypeVar("_T") - - -class ReturnValueIgnoredError(Exception): - # No longer used; was previously used by @return_future - pass - - -Future = asyncio.Future - -FUTURES = (futures.Future, Future) - - -def is_future(x: Any) -> bool: - return isinstance(x, FUTURES) - - -class DummyExecutor(futures.Executor): - def submit( - self, fn: Callable[..., _T], *args: Any, **kwargs: Any - ) -> "futures.Future[_T]": - future = futures.Future() # type: futures.Future[_T] - try: - future_set_result_unless_cancelled(future, fn(*args, **kwargs)) - except Exception: - future_set_exc_info(future, sys.exc_info()) - return future - - def shutdown(self, wait: bool = True) -> None: - pass - - -dummy_executor = DummyExecutor() - - -def run_on_executor(*args: Any, **kwargs: Any) -> Callable: - """Decorator to run a synchronous method asynchronously on an executor. - - Returns a future. - - The executor to be used is determined by the ``executor`` - attributes of ``self``. To use a different attribute name, pass a - keyword argument to the decorator:: - - @run_on_executor(executor='_thread_pool') - def foo(self): - pass - - This decorator should not be confused with the similarly-named - `.IOLoop.run_in_executor`. In general, using ``run_in_executor`` - when *calling* a blocking method is recommended instead of using - this decorator when *defining* a method. If compatibility with older - versions of Tornado is required, consider defining an executor - and using ``executor.submit()`` at the call site. - - .. versionchanged:: 4.2 - Added keyword arguments to use alternative attributes. - - .. versionchanged:: 5.0 - Always uses the current IOLoop instead of ``self.io_loop``. - - .. versionchanged:: 5.1 - Returns a `.Future` compatible with ``await`` instead of a - `concurrent.futures.Future`. - - .. deprecated:: 5.1 - - The ``callback`` argument is deprecated and will be removed in - 6.0. The decorator itself is discouraged in new code but will - not be removed in 6.0. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. - """ - # Fully type-checking decorators is tricky, and this one is - # discouraged anyway so it doesn't have all the generic magic. - def run_on_executor_decorator(fn: Callable) -> Callable[..., Future]: - executor = kwargs.get("executor", "executor") - - @functools.wraps(fn) - def wrapper(self: Any, *args: Any, **kwargs: Any) -> Future: - async_future = Future() # type: Future - conc_future = getattr(self, executor).submit(fn, self, *args, **kwargs) - chain_future(conc_future, async_future) - return async_future - - return wrapper - - if args and kwargs: - raise ValueError("cannot combine positional and keyword args") - if len(args) == 1: - return run_on_executor_decorator(args[0]) - elif len(args) != 0: - raise ValueError("expected 1 argument, got %d", len(args)) - return run_on_executor_decorator - - -_NO_RESULT = object() - - -def chain_future(a: "Future[_T]", b: "Future[_T]") -> None: - """Chain two futures together so that when one completes, so does the other. - - The result (success or failure) of ``a`` will be copied to ``b``, unless - ``b`` has already been completed or cancelled by the time ``a`` finishes. - - .. versionchanged:: 5.0 - - Now accepts both Tornado/asyncio `Future` objects and - `concurrent.futures.Future`. - - """ - - def copy(future: "Future[_T]") -> None: - assert future is a - if b.done(): - return - if hasattr(a, "exc_info") and a.exc_info() is not None: # type: ignore - future_set_exc_info(b, a.exc_info()) # type: ignore - elif a.exception() is not None: - b.set_exception(a.exception()) - else: - b.set_result(a.result()) - - if isinstance(a, Future): - future_add_done_callback(a, copy) - else: - # concurrent.futures.Future - from tornado.ioloop import IOLoop - - IOLoop.current().add_future(a, copy) - - -def future_set_result_unless_cancelled( - future: "Union[futures.Future[_T], Future[_T]]", value: _T -) -> None: - """Set the given ``value`` as the `Future`'s result, if not cancelled. - - Avoids ``asyncio.InvalidStateError`` when calling ``set_result()`` on - a cancelled `asyncio.Future`. - - .. versionadded:: 5.0 - """ - if not future.cancelled(): - future.set_result(value) - - -def future_set_exception_unless_cancelled( - future: "Union[futures.Future[_T], Future[_T]]", exc: BaseException -) -> None: - """Set the given ``exc`` as the `Future`'s exception. - - If the Future is already canceled, logs the exception instead. If - this logging is not desired, the caller should explicitly check - the state of the Future and call ``Future.set_exception`` instead of - this wrapper. - - Avoids ``asyncio.InvalidStateError`` when calling ``set_exception()`` on - a cancelled `asyncio.Future`. - - .. versionadded:: 6.0 - - """ - if not future.cancelled(): - future.set_exception(exc) - else: - app_log.error("Exception after Future was cancelled", exc_info=exc) - - -def future_set_exc_info( - future: "Union[futures.Future[_T], Future[_T]]", - exc_info: Tuple[ - Optional[type], Optional[BaseException], Optional[types.TracebackType] - ], -) -> None: - """Set the given ``exc_info`` as the `Future`'s exception. - - Understands both `asyncio.Future` and the extensions in older - versions of Tornado to enable better tracebacks on Python 2. - - .. versionadded:: 5.0 - - .. versionchanged:: 6.0 - - If the future is already cancelled, this function is a no-op. - (previously ``asyncio.InvalidStateError`` would be raised) - - """ - if exc_info[1] is None: - raise Exception("future_set_exc_info called with no exception") - future_set_exception_unless_cancelled(future, exc_info[1]) - - -@typing.overload -def future_add_done_callback( - future: "futures.Future[_T]", callback: Callable[["futures.Future[_T]"], None] -) -> None: - pass - - -@typing.overload # noqa: F811 -def future_add_done_callback( - future: "Future[_T]", callback: Callable[["Future[_T]"], None] -) -> None: - pass - - -def future_add_done_callback( # noqa: F811 - future: "Union[futures.Future[_T], Future[_T]]", callback: Callable[..., None] -) -> None: - """Arrange to call ``callback`` when ``future`` is complete. - - ``callback`` is invoked with one argument, the ``future``. - - If ``future`` is already done, ``callback`` is invoked immediately. - This may differ from the behavior of ``Future.add_done_callback``, - which makes no such guarantee. - - .. versionadded:: 5.0 - """ - if future.done(): - callback(future) - else: - future.add_done_callback(callback) diff --git a/telegramer/include/tornado/curl_httpclient.py b/telegramer/include/tornado/curl_httpclient.py deleted file mode 100644 index 6553999..0000000 --- a/telegramer/include/tornado/curl_httpclient.py +++ /dev/null @@ -1,583 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Non-blocking HTTP client implementation using pycurl.""" - -import collections -import functools -import logging -import pycurl -import threading -import time -from io import BytesIO - -from tornado import httputil -from tornado import ioloop - -from tornado.escape import utf8, native_str -from tornado.httpclient import ( - HTTPRequest, - HTTPResponse, - HTTPError, - AsyncHTTPClient, - main, -) -from tornado.log import app_log - -from typing import Dict, Any, Callable, Union, Tuple, Optional -import typing - -if typing.TYPE_CHECKING: - from typing import Deque # noqa: F401 - -curl_log = logging.getLogger("tornado.curl_httpclient") - - -class CurlAsyncHTTPClient(AsyncHTTPClient): - def initialize( # type: ignore - self, max_clients: int = 10, defaults: Optional[Dict[str, Any]] = None - ) -> None: - super().initialize(defaults=defaults) - # Typeshed is incomplete for CurlMulti, so just use Any for now. - self._multi = pycurl.CurlMulti() # type: Any - self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout) - self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket) - self._curls = [self._curl_create() for i in range(max_clients)] - self._free_list = self._curls[:] - self._requests = ( - collections.deque() - ) # type: Deque[Tuple[HTTPRequest, Callable[[HTTPResponse], None], float]] - self._fds = {} # type: Dict[int, int] - self._timeout = None # type: Optional[object] - - # libcurl has bugs that sometimes cause it to not report all - # relevant file descriptors and timeouts to TIMERFUNCTION/ - # SOCKETFUNCTION. Mitigate the effects of such bugs by - # forcing a periodic scan of all active requests. - self._force_timeout_callback = ioloop.PeriodicCallback( - self._handle_force_timeout, 1000 - ) - self._force_timeout_callback.start() - - # Work around a bug in libcurl 7.29.0: Some fields in the curl - # multi object are initialized lazily, and its destructor will - # segfault if it is destroyed without having been used. Add - # and remove a dummy handle to make sure everything is - # initialized. - dummy_curl_handle = pycurl.Curl() - self._multi.add_handle(dummy_curl_handle) - self._multi.remove_handle(dummy_curl_handle) - - def close(self) -> None: - self._force_timeout_callback.stop() - if self._timeout is not None: - self.io_loop.remove_timeout(self._timeout) - for curl in self._curls: - curl.close() - self._multi.close() - super().close() - - # Set below properties to None to reduce the reference count of current - # instance, because those properties hold some methods of current - # instance that will case circular reference. - self._force_timeout_callback = None # type: ignore - self._multi = None - - def fetch_impl( - self, request: HTTPRequest, callback: Callable[[HTTPResponse], None] - ) -> None: - self._requests.append((request, callback, self.io_loop.time())) - self._process_queue() - self._set_timeout(0) - - def _handle_socket(self, event: int, fd: int, multi: Any, data: bytes) -> None: - """Called by libcurl when it wants to change the file descriptors - it cares about. - """ - event_map = { - pycurl.POLL_NONE: ioloop.IOLoop.NONE, - pycurl.POLL_IN: ioloop.IOLoop.READ, - pycurl.POLL_OUT: ioloop.IOLoop.WRITE, - pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE, - } - if event == pycurl.POLL_REMOVE: - if fd in self._fds: - self.io_loop.remove_handler(fd) - del self._fds[fd] - else: - ioloop_event = event_map[event] - # libcurl sometimes closes a socket and then opens a new - # one using the same FD without giving us a POLL_NONE in - # between. This is a problem with the epoll IOLoop, - # because the kernel can tell when a socket is closed and - # removes it from the epoll automatically, causing future - # update_handler calls to fail. Since we can't tell when - # this has happened, always use remove and re-add - # instead of update. - if fd in self._fds: - self.io_loop.remove_handler(fd) - self.io_loop.add_handler(fd, self._handle_events, ioloop_event) - self._fds[fd] = ioloop_event - - def _set_timeout(self, msecs: int) -> None: - """Called by libcurl to schedule a timeout.""" - if self._timeout is not None: - self.io_loop.remove_timeout(self._timeout) - self._timeout = self.io_loop.add_timeout( - self.io_loop.time() + msecs / 1000.0, self._handle_timeout - ) - - def _handle_events(self, fd: int, events: int) -> None: - """Called by IOLoop when there is activity on one of our - file descriptors. - """ - action = 0 - if events & ioloop.IOLoop.READ: - action |= pycurl.CSELECT_IN - if events & ioloop.IOLoop.WRITE: - action |= pycurl.CSELECT_OUT - while True: - try: - ret, num_handles = self._multi.socket_action(fd, action) - except pycurl.error as e: - ret = e.args[0] - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - self._finish_pending_requests() - - def _handle_timeout(self) -> None: - """Called by IOLoop when the requested timeout has passed.""" - self._timeout = None - while True: - try: - ret, num_handles = self._multi.socket_action(pycurl.SOCKET_TIMEOUT, 0) - except pycurl.error as e: - ret = e.args[0] - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - self._finish_pending_requests() - - # In theory, we shouldn't have to do this because curl will - # call _set_timeout whenever the timeout changes. However, - # sometimes after _handle_timeout we will need to reschedule - # immediately even though nothing has changed from curl's - # perspective. This is because when socket_action is - # called with SOCKET_TIMEOUT, libcurl decides internally which - # timeouts need to be processed by using a monotonic clock - # (where available) while tornado uses python's time.time() - # to decide when timeouts have occurred. When those clocks - # disagree on elapsed time (as they will whenever there is an - # NTP adjustment), tornado might call _handle_timeout before - # libcurl is ready. After each timeout, resync the scheduled - # timeout with libcurl's current state. - new_timeout = self._multi.timeout() - if new_timeout >= 0: - self._set_timeout(new_timeout) - - def _handle_force_timeout(self) -> None: - """Called by IOLoop periodically to ask libcurl to process any - events it may have forgotten about. - """ - while True: - try: - ret, num_handles = self._multi.socket_all() - except pycurl.error as e: - ret = e.args[0] - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - self._finish_pending_requests() - - def _finish_pending_requests(self) -> None: - """Process any requests that were completed by the last - call to multi.socket_action. - """ - while True: - num_q, ok_list, err_list = self._multi.info_read() - for curl in ok_list: - self._finish(curl) - for curl, errnum, errmsg in err_list: - self._finish(curl, errnum, errmsg) - if num_q == 0: - break - self._process_queue() - - def _process_queue(self) -> None: - while True: - started = 0 - while self._free_list and self._requests: - started += 1 - curl = self._free_list.pop() - (request, callback, queue_start_time) = self._requests.popleft() - # TODO: Don't smuggle extra data on an attribute of the Curl object. - curl.info = { # type: ignore - "headers": httputil.HTTPHeaders(), - "buffer": BytesIO(), - "request": request, - "callback": callback, - "queue_start_time": queue_start_time, - "curl_start_time": time.time(), - "curl_start_ioloop_time": self.io_loop.current().time(), - } - try: - self._curl_setup_request( - curl, - request, - curl.info["buffer"], # type: ignore - curl.info["headers"], # type: ignore - ) - except Exception as e: - # If there was an error in setup, pass it on - # to the callback. Note that allowing the - # error to escape here will appear to work - # most of the time since we are still in the - # caller's original stack frame, but when - # _process_queue() is called from - # _finish_pending_requests the exceptions have - # nowhere to go. - self._free_list.append(curl) - callback(HTTPResponse(request=request, code=599, error=e)) - else: - self._multi.add_handle(curl) - - if not started: - break - - def _finish( - self, - curl: pycurl.Curl, - curl_error: Optional[int] = None, - curl_message: Optional[str] = None, - ) -> None: - info = curl.info # type: ignore - curl.info = None # type: ignore - self._multi.remove_handle(curl) - self._free_list.append(curl) - buffer = info["buffer"] - if curl_error: - assert curl_message is not None - error = CurlError(curl_error, curl_message) # type: Optional[CurlError] - assert error is not None - code = error.code - effective_url = None - buffer.close() - buffer = None - else: - error = None - code = curl.getinfo(pycurl.HTTP_CODE) - effective_url = curl.getinfo(pycurl.EFFECTIVE_URL) - buffer.seek(0) - # the various curl timings are documented at - # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html - time_info = dict( - queue=info["curl_start_ioloop_time"] - info["queue_start_time"], - namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME), - connect=curl.getinfo(pycurl.CONNECT_TIME), - appconnect=curl.getinfo(pycurl.APPCONNECT_TIME), - pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME), - starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME), - total=curl.getinfo(pycurl.TOTAL_TIME), - redirect=curl.getinfo(pycurl.REDIRECT_TIME), - ) - try: - info["callback"]( - HTTPResponse( - request=info["request"], - code=code, - headers=info["headers"], - buffer=buffer, - effective_url=effective_url, - error=error, - reason=info["headers"].get("X-Http-Reason", None), - request_time=self.io_loop.time() - info["curl_start_ioloop_time"], - start_time=info["curl_start_time"], - time_info=time_info, - ) - ) - except Exception: - self.handle_callback_exception(info["callback"]) - - def handle_callback_exception(self, callback: Any) -> None: - app_log.error("Exception in callback %r", callback, exc_info=True) - - def _curl_create(self) -> pycurl.Curl: - curl = pycurl.Curl() - if curl_log.isEnabledFor(logging.DEBUG): - curl.setopt(pycurl.VERBOSE, 1) - curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug) - if hasattr( - pycurl, "PROTOCOLS" - ): # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12) - curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS) - curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS) - return curl - - def _curl_setup_request( - self, - curl: pycurl.Curl, - request: HTTPRequest, - buffer: BytesIO, - headers: httputil.HTTPHeaders, - ) -> None: - curl.setopt(pycurl.URL, native_str(request.url)) - - # libcurl's magic "Expect: 100-continue" behavior causes delays - # with servers that don't support it (which include, among others, - # Google's OpenID endpoint). Additionally, this behavior has - # a bug in conjunction with the curl_multi_socket_action API - # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976), - # which increases the delays. It's more trouble than it's worth, - # so just turn off the feature (yes, setting Expect: to an empty - # value is the official way to disable this) - if "Expect" not in request.headers: - request.headers["Expect"] = "" - - # libcurl adds Pragma: no-cache by default; disable that too - if "Pragma" not in request.headers: - request.headers["Pragma"] = "" - - curl.setopt( - pycurl.HTTPHEADER, - [ - "%s: %s" % (native_str(k), native_str(v)) - for k, v in request.headers.get_all() - ], - ) - - curl.setopt( - pycurl.HEADERFUNCTION, - functools.partial( - self._curl_header_callback, headers, request.header_callback - ), - ) - if request.streaming_callback: - - def write_function(b: Union[bytes, bytearray]) -> int: - assert request.streaming_callback is not None - self.io_loop.add_callback(request.streaming_callback, b) - return len(b) - - else: - write_function = buffer.write - curl.setopt(pycurl.WRITEFUNCTION, write_function) - curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects) - curl.setopt(pycurl.MAXREDIRS, request.max_redirects) - assert request.connect_timeout is not None - curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout)) - assert request.request_timeout is not None - curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout)) - if request.user_agent: - curl.setopt(pycurl.USERAGENT, native_str(request.user_agent)) - else: - curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)") - if request.network_interface: - curl.setopt(pycurl.INTERFACE, request.network_interface) - if request.decompress_response: - curl.setopt(pycurl.ENCODING, "gzip,deflate") - else: - curl.setopt(pycurl.ENCODING, None) - if request.proxy_host and request.proxy_port: - curl.setopt(pycurl.PROXY, request.proxy_host) - curl.setopt(pycurl.PROXYPORT, request.proxy_port) - if request.proxy_username: - assert request.proxy_password is not None - credentials = httputil.encode_username_password( - request.proxy_username, request.proxy_password - ) - curl.setopt(pycurl.PROXYUSERPWD, credentials) - - if request.proxy_auth_mode is None or request.proxy_auth_mode == "basic": - curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC) - elif request.proxy_auth_mode == "digest": - curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST) - else: - raise ValueError( - "Unsupported proxy_auth_mode %s" % request.proxy_auth_mode - ) - else: - try: - curl.unsetopt(pycurl.PROXY) - except TypeError: # not supported, disable proxy - curl.setopt(pycurl.PROXY, "") - curl.unsetopt(pycurl.PROXYUSERPWD) - if request.validate_cert: - curl.setopt(pycurl.SSL_VERIFYPEER, 1) - curl.setopt(pycurl.SSL_VERIFYHOST, 2) - else: - curl.setopt(pycurl.SSL_VERIFYPEER, 0) - curl.setopt(pycurl.SSL_VERIFYHOST, 0) - if request.ca_certs is not None: - curl.setopt(pycurl.CAINFO, request.ca_certs) - else: - # There is no way to restore pycurl.CAINFO to its default value - # (Using unsetopt makes it reject all certificates). - # I don't see any way to read the default value from python so it - # can be restored later. We'll have to just leave CAINFO untouched - # if no ca_certs file was specified, and require that if any - # request uses a custom ca_certs file, they all must. - pass - - if request.allow_ipv6 is False: - # Curl behaves reasonably when DNS resolution gives an ipv6 address - # that we can't reach, so allow ipv6 unless the user asks to disable. - curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) - else: - curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER) - - # Set the request method through curl's irritating interface which makes - # up names for almost every single method - curl_options = { - "GET": pycurl.HTTPGET, - "POST": pycurl.POST, - "PUT": pycurl.UPLOAD, - "HEAD": pycurl.NOBODY, - } - custom_methods = set(["DELETE", "OPTIONS", "PATCH"]) - for o in curl_options.values(): - curl.setopt(o, False) - if request.method in curl_options: - curl.unsetopt(pycurl.CUSTOMREQUEST) - curl.setopt(curl_options[request.method], True) - elif request.allow_nonstandard_methods or request.method in custom_methods: - curl.setopt(pycurl.CUSTOMREQUEST, request.method) - else: - raise KeyError("unknown method " + request.method) - - body_expected = request.method in ("POST", "PATCH", "PUT") - body_present = request.body is not None - if not request.allow_nonstandard_methods: - # Some HTTP methods nearly always have bodies while others - # almost never do. Fail in this case unless the user has - # opted out of sanity checks with allow_nonstandard_methods. - if (body_expected and not body_present) or ( - body_present and not body_expected - ): - raise ValueError( - "Body must %sbe None for method %s (unless " - "allow_nonstandard_methods is true)" - % ("not " if body_expected else "", request.method) - ) - - if body_expected or body_present: - if request.method == "GET": - # Even with `allow_nonstandard_methods` we disallow - # GET with a body (because libcurl doesn't allow it - # unless we use CUSTOMREQUEST). While the spec doesn't - # forbid clients from sending a body, it arguably - # disallows the server from doing anything with them. - raise ValueError("Body must be None for GET request") - request_buffer = BytesIO(utf8(request.body or "")) - - def ioctl(cmd: int) -> None: - if cmd == curl.IOCMD_RESTARTREAD: # type: ignore - request_buffer.seek(0) - - curl.setopt(pycurl.READFUNCTION, request_buffer.read) - curl.setopt(pycurl.IOCTLFUNCTION, ioctl) - if request.method == "POST": - curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or "")) - else: - curl.setopt(pycurl.UPLOAD, True) - curl.setopt(pycurl.INFILESIZE, len(request.body or "")) - - if request.auth_username is not None: - assert request.auth_password is not None - if request.auth_mode is None or request.auth_mode == "basic": - curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) - elif request.auth_mode == "digest": - curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST) - else: - raise ValueError("Unsupported auth_mode %s" % request.auth_mode) - - userpwd = httputil.encode_username_password( - request.auth_username, request.auth_password - ) - curl.setopt(pycurl.USERPWD, userpwd) - curl_log.debug( - "%s %s (username: %r)", - request.method, - request.url, - request.auth_username, - ) - else: - curl.unsetopt(pycurl.USERPWD) - curl_log.debug("%s %s", request.method, request.url) - - if request.client_cert is not None: - curl.setopt(pycurl.SSLCERT, request.client_cert) - - if request.client_key is not None: - curl.setopt(pycurl.SSLKEY, request.client_key) - - if request.ssl_options is not None: - raise ValueError("ssl_options not supported in curl_httpclient") - - if threading.active_count() > 1: - # libcurl/pycurl is not thread-safe by default. When multiple threads - # are used, signals should be disabled. This has the side effect - # of disabling DNS timeouts in some environments (when libcurl is - # not linked against ares), so we don't do it when there is only one - # thread. Applications that use many short-lived threads may need - # to set NOSIGNAL manually in a prepare_curl_callback since - # there may not be any other threads running at the time we call - # threading.activeCount. - curl.setopt(pycurl.NOSIGNAL, 1) - if request.prepare_curl_callback is not None: - request.prepare_curl_callback(curl) - - def _curl_header_callback( - self, - headers: httputil.HTTPHeaders, - header_callback: Callable[[str], None], - header_line_bytes: bytes, - ) -> None: - header_line = native_str(header_line_bytes.decode("latin1")) - if header_callback is not None: - self.io_loop.add_callback(header_callback, header_line) - # header_line as returned by curl includes the end-of-line characters. - # whitespace at the start should be preserved to allow multi-line headers - header_line = header_line.rstrip() - if header_line.startswith("HTTP/"): - headers.clear() - try: - (__, __, reason) = httputil.parse_response_start_line(header_line) - header_line = "X-Http-Reason: %s" % reason - except httputil.HTTPInputError: - return - if not header_line: - return - headers.parse_line(header_line) - - def _curl_debug(self, debug_type: int, debug_msg: str) -> None: - debug_types = ("I", "<", ">", "<", ">") - if debug_type == 0: - debug_msg = native_str(debug_msg) - curl_log.debug("%s", debug_msg.strip()) - elif debug_type in (1, 2): - debug_msg = native_str(debug_msg) - for line in debug_msg.splitlines(): - curl_log.debug("%s %s", debug_types[debug_type], line) - elif debug_type == 4: - curl_log.debug("%s %r", debug_types[debug_type], debug_msg) - - -class CurlError(HTTPError): - def __init__(self, errno: int, message: str) -> None: - HTTPError.__init__(self, 599, message) - self.errno = errno - - -if __name__ == "__main__": - AsyncHTTPClient.configure(CurlAsyncHTTPClient) - main() diff --git a/telegramer/include/tornado/escape.py b/telegramer/include/tornado/escape.py deleted file mode 100644 index 3cf7ff2..0000000 --- a/telegramer/include/tornado/escape.py +++ /dev/null @@ -1,402 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Escaping/unescaping methods for HTML, JSON, URLs, and others. - -Also includes a few other miscellaneous string manipulation functions that -have crept in over time. -""" - -import html.entities -import json -import re -import urllib.parse - -from tornado.util import unicode_type - -import typing -from typing import Union, Any, Optional, Dict, List, Callable - - -_XHTML_ESCAPE_RE = re.compile("[&<>\"']") -_XHTML_ESCAPE_DICT = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", -} - - -def xhtml_escape(value: Union[str, bytes]) -> str: - """Escapes a string so it is valid within HTML or XML. - - Escapes the characters ``<``, ``>``, ``"``, ``'``, and ``&``. - When used in attribute values the escaped strings must be enclosed - in quotes. - - .. versionchanged:: 3.2 - - Added the single quote to the list of escaped characters. - """ - return _XHTML_ESCAPE_RE.sub( - lambda match: _XHTML_ESCAPE_DICT[match.group(0)], to_basestring(value) - ) - - -def xhtml_unescape(value: Union[str, bytes]) -> str: - """Un-escapes an XML-escaped string.""" - return re.sub(r"&(#?)(\w+?);", _convert_entity, _unicode(value)) - - -# The fact that json_encode wraps json.dumps is an implementation detail. -# Please see https://github.com/tornadoweb/tornado/pull/706 -# before sending a pull request that adds **kwargs to this function. -def json_encode(value: Any) -> str: - """JSON-encodes the given Python object.""" - # JSON permits but does not require forward slashes to be escaped. - # This is useful when json data is emitted in a tags from prematurely terminating - # the JavaScript. Some json libraries do this escaping by default, - # although python's standard library does not, so we do it here. - # http://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped - return json.dumps(value).replace(" Any: - """Returns Python objects for the given JSON string. - - Supports both `str` and `bytes` inputs. - """ - return json.loads(to_basestring(value)) - - -def squeeze(value: str) -> str: - """Replace all sequences of whitespace chars with a single space.""" - return re.sub(r"[\x00-\x20]+", " ", value).strip() - - -def url_escape(value: Union[str, bytes], plus: bool = True) -> str: - """Returns a URL-encoded version of the given value. - - If ``plus`` is true (the default), spaces will be represented - as "+" instead of "%20". This is appropriate for query strings - but not for the path component of a URL. Note that this default - is the reverse of Python's urllib module. - - .. versionadded:: 3.1 - The ``plus`` argument - """ - quote = urllib.parse.quote_plus if plus else urllib.parse.quote - return quote(utf8(value)) - - -@typing.overload -def url_unescape(value: Union[str, bytes], encoding: None, plus: bool = True) -> bytes: - pass - - -@typing.overload # noqa: F811 -def url_unescape( - value: Union[str, bytes], encoding: str = "utf-8", plus: bool = True -) -> str: - pass - - -def url_unescape( # noqa: F811 - value: Union[str, bytes], encoding: Optional[str] = "utf-8", plus: bool = True -) -> Union[str, bytes]: - """Decodes the given value from a URL. - - The argument may be either a byte or unicode string. - - If encoding is None, the result will be a byte string. Otherwise, - the result is a unicode string in the specified encoding. - - If ``plus`` is true (the default), plus signs will be interpreted - as spaces (literal plus signs must be represented as "%2B"). This - is appropriate for query strings and form-encoded values but not - for the path component of a URL. Note that this default is the - reverse of Python's urllib module. - - .. versionadded:: 3.1 - The ``plus`` argument - """ - if encoding is None: - if plus: - # unquote_to_bytes doesn't have a _plus variant - value = to_basestring(value).replace("+", " ") - return urllib.parse.unquote_to_bytes(value) - else: - unquote = urllib.parse.unquote_plus if plus else urllib.parse.unquote - return unquote(to_basestring(value), encoding=encoding) - - -def parse_qs_bytes( - qs: Union[str, bytes], keep_blank_values: bool = False, strict_parsing: bool = False -) -> Dict[str, List[bytes]]: - """Parses a query string like urlparse.parse_qs, - but takes bytes and returns the values as byte strings. - - Keys still become type str (interpreted as latin1 in python3!) - because it's too painful to keep them as byte strings in - python3 and in practice they're nearly always ascii anyway. - """ - # This is gross, but python3 doesn't give us another way. - # Latin1 is the universal donor of character encodings. - if isinstance(qs, bytes): - qs = qs.decode("latin1") - result = urllib.parse.parse_qs( - qs, keep_blank_values, strict_parsing, encoding="latin1", errors="strict" - ) - encoded = {} - for k, v in result.items(): - encoded[k] = [i.encode("latin1") for i in v] - return encoded - - -_UTF8_TYPES = (bytes, type(None)) - - -@typing.overload -def utf8(value: bytes) -> bytes: - pass - - -@typing.overload # noqa: F811 -def utf8(value: str) -> bytes: - pass - - -@typing.overload # noqa: F811 -def utf8(value: None) -> None: - pass - - -def utf8(value: Union[None, str, bytes]) -> Optional[bytes]: # noqa: F811 - """Converts a string argument to a byte string. - - If the argument is already a byte string or None, it is returned unchanged. - Otherwise it must be a unicode string and is encoded as utf8. - """ - if isinstance(value, _UTF8_TYPES): - return value - if not isinstance(value, unicode_type): - raise TypeError("Expected bytes, unicode, or None; got %r" % type(value)) - return value.encode("utf-8") - - -_TO_UNICODE_TYPES = (unicode_type, type(None)) - - -@typing.overload -def to_unicode(value: str) -> str: - pass - - -@typing.overload # noqa: F811 -def to_unicode(value: bytes) -> str: - pass - - -@typing.overload # noqa: F811 -def to_unicode(value: None) -> None: - pass - - -def to_unicode(value: Union[None, str, bytes]) -> Optional[str]: # noqa: F811 - """Converts a string argument to a unicode string. - - If the argument is already a unicode string or None, it is returned - unchanged. Otherwise it must be a byte string and is decoded as utf8. - """ - if isinstance(value, _TO_UNICODE_TYPES): - return value - if not isinstance(value, bytes): - raise TypeError("Expected bytes, unicode, or None; got %r" % type(value)) - return value.decode("utf-8") - - -# to_unicode was previously named _unicode not because it was private, -# but to avoid conflicts with the built-in unicode() function/type -_unicode = to_unicode - -# When dealing with the standard library across python 2 and 3 it is -# sometimes useful to have a direct conversion to the native string type -native_str = to_unicode -to_basestring = to_unicode - - -def recursive_unicode(obj: Any) -> Any: - """Walks a simple data structure, converting byte strings to unicode. - - Supports lists, tuples, and dictionaries. - """ - if isinstance(obj, dict): - return dict( - (recursive_unicode(k), recursive_unicode(v)) for (k, v) in obj.items() - ) - elif isinstance(obj, list): - return list(recursive_unicode(i) for i in obj) - elif isinstance(obj, tuple): - return tuple(recursive_unicode(i) for i in obj) - elif isinstance(obj, bytes): - return to_unicode(obj) - else: - return obj - - -# I originally used the regex from -# http://daringfireball.net/2010/07/improved_regex_for_matching_urls -# but it gets all exponential on certain patterns (such as too many trailing -# dots), causing the regex matcher to never return. -# This regex should avoid those problems. -# Use to_unicode instead of tornado.util.u - we don't want backslashes getting -# processed as escapes. -_URL_RE = re.compile( - to_unicode( - r"""\b((?:([\w-]+):(/{1,3})|www[.])(?:(?:(?:[^\s&()]|&|")*(?:[^!"#$%&'()*+,.:;<=>?@\[\]^`{|}~\s]))|(?:\((?:[^\s&()]|&|")*\)))+)""" # noqa: E501 - ) -) - - -def linkify( - text: Union[str, bytes], - shorten: bool = False, - extra_params: Union[str, Callable[[str], str]] = "", - require_protocol: bool = False, - permitted_protocols: List[str] = ["http", "https"], -) -> str: - """Converts plain text into HTML with links. - - For example: ``linkify("Hello http://tornadoweb.org!")`` would return - ``Hello http://tornadoweb.org!`` - - Parameters: - - * ``shorten``: Long urls will be shortened for display. - - * ``extra_params``: Extra text to include in the link tag, or a callable - taking the link as an argument and returning the extra text - e.g. ``linkify(text, extra_params='rel="nofollow" class="external"')``, - or:: - - def extra_params_cb(url): - if url.startswith("http://example.com"): - return 'class="internal"' - else: - return 'class="external" rel="nofollow"' - linkify(text, extra_params=extra_params_cb) - - * ``require_protocol``: Only linkify urls which include a protocol. If - this is False, urls such as www.facebook.com will also be linkified. - - * ``permitted_protocols``: List (or set) of protocols which should be - linkified, e.g. ``linkify(text, permitted_protocols=["http", "ftp", - "mailto"])``. It is very unsafe to include protocols such as - ``javascript``. - """ - if extra_params and not callable(extra_params): - extra_params = " " + extra_params.strip() - - def make_link(m: typing.Match) -> str: - url = m.group(1) - proto = m.group(2) - if require_protocol and not proto: - return url # not protocol, no linkify - - if proto and proto not in permitted_protocols: - return url # bad protocol, no linkify - - href = m.group(1) - if not proto: - href = "http://" + href # no proto specified, use http - - if callable(extra_params): - params = " " + extra_params(href).strip() - else: - params = extra_params - - # clip long urls. max_len is just an approximation - max_len = 30 - if shorten and len(url) > max_len: - before_clip = url - if proto: - proto_len = len(proto) + 1 + len(m.group(3) or "") # +1 for : - else: - proto_len = 0 - - parts = url[proto_len:].split("/") - if len(parts) > 1: - # Grab the whole host part plus the first bit of the path - # The path is usually not that interesting once shortened - # (no more slug, etc), so it really just provides a little - # extra indication of shortening. - url = ( - url[:proto_len] - + parts[0] - + "/" - + parts[1][:8].split("?")[0].split(".")[0] - ) - - if len(url) > max_len * 1.5: # still too long - url = url[:max_len] - - if url != before_clip: - amp = url.rfind("&") - # avoid splitting html char entities - if amp > max_len - 5: - url = url[:amp] - url += "..." - - if len(url) >= len(before_clip): - url = before_clip - else: - # full url is visible on mouse-over (for those who don't - # have a status bar, such as Safari by default) - params += ' title="%s"' % href - - return u'%s' % (href, params, url) - - # First HTML-escape so that our strings are all safe. - # The regex is modified to avoid character entites other than & so - # that we won't pick up ", etc. - text = _unicode(xhtml_escape(text)) - return _URL_RE.sub(make_link, text) - - -def _convert_entity(m: typing.Match) -> str: - if m.group(1) == "#": - try: - if m.group(2)[:1].lower() == "x": - return chr(int(m.group(2)[1:], 16)) - else: - return chr(int(m.group(2))) - except ValueError: - return "&#%s;" % m.group(2) - try: - return _HTML_UNICODE_MAP[m.group(2)] - except KeyError: - return "&%s;" % m.group(2) - - -def _build_unicode_map() -> Dict[str, str]: - unicode_map = {} - for name, value in html.entities.name2codepoint.items(): - unicode_map[name] = chr(value) - return unicode_map - - -_HTML_UNICODE_MAP = _build_unicode_map() diff --git a/telegramer/include/tornado/gen.py b/telegramer/include/tornado/gen.py deleted file mode 100644 index cab9689..0000000 --- a/telegramer/include/tornado/gen.py +++ /dev/null @@ -1,872 +0,0 @@ -"""``tornado.gen`` implements generator-based coroutines. - -.. note:: - - The "decorator and generator" approach in this module is a - precursor to native coroutines (using ``async def`` and ``await``) - which were introduced in Python 3.5. Applications that do not - require compatibility with older versions of Python should use - native coroutines instead. Some parts of this module are still - useful with native coroutines, notably `multi`, `sleep`, - `WaitIterator`, and `with_timeout`. Some of these functions have - counterparts in the `asyncio` module which may be used as well, - although the two may not necessarily be 100% compatible. - -Coroutines provide an easier way to work in an asynchronous -environment than chaining callbacks. Code using coroutines is -technically asynchronous, but it is written as a single generator -instead of a collection of separate functions. - -For example, here's a coroutine-based handler: - -.. testcode:: - - class GenAsyncHandler(RequestHandler): - @gen.coroutine - def get(self): - http_client = AsyncHTTPClient() - response = yield http_client.fetch("http://example.com") - do_something_with_response(response) - self.render("template.html") - -.. testoutput:: - :hide: - -Asynchronous functions in Tornado return an ``Awaitable`` or `.Future`; -yielding this object returns its result. - -You can also yield a list or dict of other yieldable objects, which -will be started at the same time and run in parallel; a list or dict -of results will be returned when they are all finished: - -.. testcode:: - - @gen.coroutine - def get(self): - http_client = AsyncHTTPClient() - response1, response2 = yield [http_client.fetch(url1), - http_client.fetch(url2)] - response_dict = yield dict(response3=http_client.fetch(url3), - response4=http_client.fetch(url4)) - response3 = response_dict['response3'] - response4 = response_dict['response4'] - -.. testoutput:: - :hide: - -If ``tornado.platform.twisted`` is imported, it is also possible to -yield Twisted's ``Deferred`` objects. See the `convert_yielded` -function to extend this mechanism. - -.. versionchanged:: 3.2 - Dict support added. - -.. versionchanged:: 4.1 - Support added for yielding ``asyncio`` Futures and Twisted Deferreds - via ``singledispatch``. - -""" -import asyncio -import builtins -import collections -from collections.abc import Generator -import concurrent.futures -import datetime -import functools -from functools import singledispatch -from inspect import isawaitable -import sys -import types - -from tornado.concurrent import ( - Future, - is_future, - chain_future, - future_set_exc_info, - future_add_done_callback, - future_set_result_unless_cancelled, -) -from tornado.ioloop import IOLoop -from tornado.log import app_log -from tornado.util import TimeoutError - -try: - import contextvars -except ImportError: - contextvars = None # type: ignore - -import typing -from typing import Union, Any, Callable, List, Type, Tuple, Awaitable, Dict, overload - -if typing.TYPE_CHECKING: - from typing import Sequence, Deque, Optional, Set, Iterable # noqa: F401 - -_T = typing.TypeVar("_T") - -_Yieldable = Union[ - None, Awaitable, List[Awaitable], Dict[Any, Awaitable], concurrent.futures.Future -] - - -class KeyReuseError(Exception): - pass - - -class UnknownKeyError(Exception): - pass - - -class LeakedCallbackError(Exception): - pass - - -class BadYieldError(Exception): - pass - - -class ReturnValueIgnoredError(Exception): - pass - - -def _value_from_stopiteration(e: Union[StopIteration, "Return"]) -> Any: - try: - # StopIteration has a value attribute beginning in py33. - # So does our Return class. - return e.value - except AttributeError: - pass - try: - # Cython backports coroutine functionality by putting the value in - # e.args[0]. - return e.args[0] - except (AttributeError, IndexError): - return None - - -def _create_future() -> Future: - future = Future() # type: Future - # Fixup asyncio debug info by removing extraneous stack entries - source_traceback = getattr(future, "_source_traceback", ()) - while source_traceback: - # Each traceback entry is equivalent to a - # (filename, self.lineno, self.name, self.line) tuple - filename = source_traceback[-1][0] - if filename == __file__: - del source_traceback[-1] - else: - break - return future - - -def _fake_ctx_run(f: Callable[..., _T], *args: Any, **kw: Any) -> _T: - return f(*args, **kw) - - -@overload -def coroutine( - func: Callable[..., "Generator[Any, Any, _T]"] -) -> Callable[..., "Future[_T]"]: - ... - - -@overload -def coroutine(func: Callable[..., _T]) -> Callable[..., "Future[_T]"]: - ... - - -def coroutine( - func: Union[Callable[..., "Generator[Any, Any, _T]"], Callable[..., _T]] -) -> Callable[..., "Future[_T]"]: - """Decorator for asynchronous generators. - - For compatibility with older versions of Python, coroutines may - also "return" by raising the special exception `Return(value) - `. - - Functions with this decorator return a `.Future`. - - .. warning:: - - When exceptions occur inside a coroutine, the exception - information will be stored in the `.Future` object. You must - examine the result of the `.Future` object, or the exception - may go unnoticed by your code. This means yielding the function - if called from another coroutine, using something like - `.IOLoop.run_sync` for top-level calls, or passing the `.Future` - to `.IOLoop.add_future`. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - awaitable object instead. - - """ - - @functools.wraps(func) - def wrapper(*args, **kwargs): - # type: (*Any, **Any) -> Future[_T] - # This function is type-annotated with a comment to work around - # https://bitbucket.org/pypy/pypy/issues/2868/segfault-with-args-type-annotation-in - future = _create_future() - if contextvars is not None: - ctx_run = contextvars.copy_context().run # type: Callable - else: - ctx_run = _fake_ctx_run - try: - result = ctx_run(func, *args, **kwargs) - except (Return, StopIteration) as e: - result = _value_from_stopiteration(e) - except Exception: - future_set_exc_info(future, sys.exc_info()) - try: - return future - finally: - # Avoid circular references - future = None # type: ignore - else: - if isinstance(result, Generator): - # Inline the first iteration of Runner.run. This lets us - # avoid the cost of creating a Runner when the coroutine - # never actually yields, which in turn allows us to - # use "optional" coroutines in critical path code without - # performance penalty for the synchronous case. - try: - yielded = ctx_run(next, result) - except (StopIteration, Return) as e: - future_set_result_unless_cancelled( - future, _value_from_stopiteration(e) - ) - except Exception: - future_set_exc_info(future, sys.exc_info()) - else: - # Provide strong references to Runner objects as long - # as their result future objects also have strong - # references (typically from the parent coroutine's - # Runner). This keeps the coroutine's Runner alive. - # We do this by exploiting the public API - # add_done_callback() instead of putting a private - # attribute on the Future. - # (GitHub issues #1769, #2229). - runner = Runner(ctx_run, result, future, yielded) - future.add_done_callback(lambda _: runner) - yielded = None - try: - return future - finally: - # Subtle memory optimization: if next() raised an exception, - # the future's exc_info contains a traceback which - # includes this stack frame. This creates a cycle, - # which will be collected at the next full GC but has - # been shown to greatly increase memory usage of - # benchmarks (relative to the refcount-based scheme - # used in the absence of cycles). We can avoid the - # cycle by clearing the local variable after we return it. - future = None # type: ignore - future_set_result_unless_cancelled(future, result) - return future - - wrapper.__wrapped__ = func # type: ignore - wrapper.__tornado_coroutine__ = True # type: ignore - return wrapper - - -def is_coroutine_function(func: Any) -> bool: - """Return whether *func* is a coroutine function, i.e. a function - wrapped with `~.gen.coroutine`. - - .. versionadded:: 4.5 - """ - return getattr(func, "__tornado_coroutine__", False) - - -class Return(Exception): - """Special exception to return a value from a `coroutine`. - - If this exception is raised, its value argument is used as the - result of the coroutine:: - - @gen.coroutine - def fetch_json(url): - response = yield AsyncHTTPClient().fetch(url) - raise gen.Return(json_decode(response.body)) - - In Python 3.3, this exception is no longer necessary: the ``return`` - statement can be used directly to return a value (previously - ``yield`` and ``return`` with a value could not be combined in the - same function). - - By analogy with the return statement, the value argument is optional, - but it is never necessary to ``raise gen.Return()``. The ``return`` - statement can be used with no arguments instead. - """ - - def __init__(self, value: Any = None) -> None: - super().__init__() - self.value = value - # Cython recognizes subclasses of StopIteration with a .args tuple. - self.args = (value,) - - -class WaitIterator(object): - """Provides an iterator to yield the results of awaitables as they finish. - - Yielding a set of awaitables like this: - - ``results = yield [awaitable1, awaitable2]`` - - pauses the coroutine until both ``awaitable1`` and ``awaitable2`` - return, and then restarts the coroutine with the results of both - awaitables. If either awaitable raises an exception, the - expression will raise that exception and all the results will be - lost. - - If you need to get the result of each awaitable as soon as possible, - or if you need the result of some awaitables even if others produce - errors, you can use ``WaitIterator``:: - - wait_iterator = gen.WaitIterator(awaitable1, awaitable2) - while not wait_iterator.done(): - try: - result = yield wait_iterator.next() - except Exception as e: - print("Error {} from {}".format(e, wait_iterator.current_future)) - else: - print("Result {} received from {} at {}".format( - result, wait_iterator.current_future, - wait_iterator.current_index)) - - Because results are returned as soon as they are available the - output from the iterator *will not be in the same order as the - input arguments*. If you need to know which future produced the - current result, you can use the attributes - ``WaitIterator.current_future``, or ``WaitIterator.current_index`` - to get the index of the awaitable from the input list. (if keyword - arguments were used in the construction of the `WaitIterator`, - ``current_index`` will use the corresponding keyword). - - On Python 3.5, `WaitIterator` implements the async iterator - protocol, so it can be used with the ``async for`` statement (note - that in this version the entire iteration is aborted if any value - raises an exception, while the previous example can continue past - individual errors):: - - async for result in gen.WaitIterator(future1, future2): - print("Result {} received from {} at {}".format( - result, wait_iterator.current_future, - wait_iterator.current_index)) - - .. versionadded:: 4.1 - - .. versionchanged:: 4.3 - Added ``async for`` support in Python 3.5. - - """ - - _unfinished = {} # type: Dict[Future, Union[int, str]] - - def __init__(self, *args: Future, **kwargs: Future) -> None: - if args and kwargs: - raise ValueError("You must provide args or kwargs, not both") - - if kwargs: - self._unfinished = dict((f, k) for (k, f) in kwargs.items()) - futures = list(kwargs.values()) # type: Sequence[Future] - else: - self._unfinished = dict((f, i) for (i, f) in enumerate(args)) - futures = args - - self._finished = collections.deque() # type: Deque[Future] - self.current_index = None # type: Optional[Union[str, int]] - self.current_future = None # type: Optional[Future] - self._running_future = None # type: Optional[Future] - - for future in futures: - future_add_done_callback(future, self._done_callback) - - def done(self) -> bool: - """Returns True if this iterator has no more results.""" - if self._finished or self._unfinished: - return False - # Clear the 'current' values when iteration is done. - self.current_index = self.current_future = None - return True - - def next(self) -> Future: - """Returns a `.Future` that will yield the next available result. - - Note that this `.Future` will not be the same object as any of - the inputs. - """ - self._running_future = Future() - - if self._finished: - self._return_result(self._finished.popleft()) - - return self._running_future - - def _done_callback(self, done: Future) -> None: - if self._running_future and not self._running_future.done(): - self._return_result(done) - else: - self._finished.append(done) - - def _return_result(self, done: Future) -> None: - """Called set the returned future's state that of the future - we yielded, and set the current future for the iterator. - """ - if self._running_future is None: - raise Exception("no future is running") - chain_future(done, self._running_future) - - self.current_future = done - self.current_index = self._unfinished.pop(done) - - def __aiter__(self) -> typing.AsyncIterator: - return self - - def __anext__(self) -> Future: - if self.done(): - # Lookup by name to silence pyflakes on older versions. - raise getattr(builtins, "StopAsyncIteration")() - return self.next() - - -def multi( - children: Union[List[_Yieldable], Dict[Any, _Yieldable]], - quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (), -) -> "Union[Future[List], Future[Dict]]": - """Runs multiple asynchronous operations in parallel. - - ``children`` may either be a list or a dict whose values are - yieldable objects. ``multi()`` returns a new yieldable - object that resolves to a parallel structure containing their - results. If ``children`` is a list, the result is a list of - results in the same order; if it is a dict, the result is a dict - with the same keys. - - That is, ``results = yield multi(list_of_futures)`` is equivalent - to:: - - results = [] - for future in list_of_futures: - results.append(yield future) - - If any children raise exceptions, ``multi()`` will raise the first - one. All others will be logged, unless they are of types - contained in the ``quiet_exceptions`` argument. - - In a ``yield``-based coroutine, it is not normally necessary to - call this function directly, since the coroutine runner will - do it automatically when a list or dict is yielded. However, - it is necessary in ``await``-based coroutines, or to pass - the ``quiet_exceptions`` argument. - - This function is available under the names ``multi()`` and ``Multi()`` - for historical reasons. - - Cancelling a `.Future` returned by ``multi()`` does not cancel its - children. `asyncio.gather` is similar to ``multi()``, but it does - cancel its children. - - .. versionchanged:: 4.2 - If multiple yieldables fail, any exceptions after the first - (which is raised) will be logged. Added the ``quiet_exceptions`` - argument to suppress this logging for selected exception types. - - .. versionchanged:: 4.3 - Replaced the class ``Multi`` and the function ``multi_future`` - with a unified function ``multi``. Added support for yieldables - other than ``YieldPoint`` and `.Future`. - - """ - return multi_future(children, quiet_exceptions=quiet_exceptions) - - -Multi = multi - - -def multi_future( - children: Union[List[_Yieldable], Dict[Any, _Yieldable]], - quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (), -) -> "Union[Future[List], Future[Dict]]": - """Wait for multiple asynchronous futures in parallel. - - Since Tornado 6.0, this function is exactly the same as `multi`. - - .. versionadded:: 4.0 - - .. versionchanged:: 4.2 - If multiple ``Futures`` fail, any exceptions after the first (which is - raised) will be logged. Added the ``quiet_exceptions`` - argument to suppress this logging for selected exception types. - - .. deprecated:: 4.3 - Use `multi` instead. - """ - if isinstance(children, dict): - keys = list(children.keys()) # type: Optional[List] - children_seq = children.values() # type: Iterable - else: - keys = None - children_seq = children - children_futs = list(map(convert_yielded, children_seq)) - assert all(is_future(i) or isinstance(i, _NullFuture) for i in children_futs) - unfinished_children = set(children_futs) - - future = _create_future() - if not children_futs: - future_set_result_unless_cancelled(future, {} if keys is not None else []) - - def callback(fut: Future) -> None: - unfinished_children.remove(fut) - if not unfinished_children: - result_list = [] - for f in children_futs: - try: - result_list.append(f.result()) - except Exception as e: - if future.done(): - if not isinstance(e, quiet_exceptions): - app_log.error( - "Multiple exceptions in yield list", exc_info=True - ) - else: - future_set_exc_info(future, sys.exc_info()) - if not future.done(): - if keys is not None: - future_set_result_unless_cancelled( - future, dict(zip(keys, result_list)) - ) - else: - future_set_result_unless_cancelled(future, result_list) - - listening = set() # type: Set[Future] - for f in children_futs: - if f not in listening: - listening.add(f) - future_add_done_callback(f, callback) - return future - - -def maybe_future(x: Any) -> Future: - """Converts ``x`` into a `.Future`. - - If ``x`` is already a `.Future`, it is simply returned; otherwise - it is wrapped in a new `.Future`. This is suitable for use as - ``result = yield gen.maybe_future(f())`` when you don't know whether - ``f()`` returns a `.Future` or not. - - .. deprecated:: 4.3 - This function only handles ``Futures``, not other yieldable objects. - Instead of `maybe_future`, check for the non-future result types - you expect (often just ``None``), and ``yield`` anything unknown. - """ - if is_future(x): - return x - else: - fut = _create_future() - fut.set_result(x) - return fut - - -def with_timeout( - timeout: Union[float, datetime.timedelta], - future: _Yieldable, - quiet_exceptions: "Union[Type[Exception], Tuple[Type[Exception], ...]]" = (), -) -> Future: - """Wraps a `.Future` (or other yieldable object) in a timeout. - - Raises `tornado.util.TimeoutError` if the input future does not - complete before ``timeout``, which may be specified in any form - allowed by `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or - an absolute time relative to `.IOLoop.time`) - - If the wrapped `.Future` fails after it has timed out, the exception - will be logged unless it is either of a type contained in - ``quiet_exceptions`` (which may be an exception type or a sequence of - types), or an ``asyncio.CancelledError``. - - The wrapped `.Future` is not canceled when the timeout expires, - permitting it to be reused. `asyncio.wait_for` is similar to this - function but it does cancel the wrapped `.Future` on timeout. - - .. versionadded:: 4.0 - - .. versionchanged:: 4.1 - Added the ``quiet_exceptions`` argument and the logging of unhandled - exceptions. - - .. versionchanged:: 4.4 - Added support for yieldable objects other than `.Future`. - - .. versionchanged:: 6.0.3 - ``asyncio.CancelledError`` is now always considered "quiet". - - """ - # It's tempting to optimize this by cancelling the input future on timeout - # instead of creating a new one, but A) we can't know if we are the only - # one waiting on the input future, so cancelling it might disrupt other - # callers and B) concurrent futures can only be cancelled while they are - # in the queue, so cancellation cannot reliably bound our waiting time. - future_converted = convert_yielded(future) - result = _create_future() - chain_future(future_converted, result) - io_loop = IOLoop.current() - - def error_callback(future: Future) -> None: - try: - future.result() - except asyncio.CancelledError: - pass - except Exception as e: - if not isinstance(e, quiet_exceptions): - app_log.error( - "Exception in Future %r after timeout", future, exc_info=True - ) - - def timeout_callback() -> None: - if not result.done(): - result.set_exception(TimeoutError("Timeout")) - # In case the wrapped future goes on to fail, log it. - future_add_done_callback(future_converted, error_callback) - - timeout_handle = io_loop.add_timeout(timeout, timeout_callback) - if isinstance(future_converted, Future): - # We know this future will resolve on the IOLoop, so we don't - # need the extra thread-safety of IOLoop.add_future (and we also - # don't care about StackContext here. - future_add_done_callback( - future_converted, lambda future: io_loop.remove_timeout(timeout_handle) - ) - else: - # concurrent.futures.Futures may resolve on any thread, so we - # need to route them back to the IOLoop. - io_loop.add_future( - future_converted, lambda future: io_loop.remove_timeout(timeout_handle) - ) - return result - - -def sleep(duration: float) -> "Future[None]": - """Return a `.Future` that resolves after the given number of seconds. - - When used with ``yield`` in a coroutine, this is a non-blocking - analogue to `time.sleep` (which should not be used in coroutines - because it is blocking):: - - yield gen.sleep(0.5) - - Note that calling this function on its own does nothing; you must - wait on the `.Future` it returns (usually by yielding it). - - .. versionadded:: 4.1 - """ - f = _create_future() - IOLoop.current().call_later( - duration, lambda: future_set_result_unless_cancelled(f, None) - ) - return f - - -class _NullFuture(object): - """_NullFuture resembles a Future that finished with a result of None. - - It's not actually a `Future` to avoid depending on a particular event loop. - Handled as a special case in the coroutine runner. - - We lie and tell the type checker that a _NullFuture is a Future so - we don't have to leak _NullFuture into lots of public APIs. But - this means that the type checker can't warn us when we're passing - a _NullFuture into a code path that doesn't understand what to do - with it. - """ - - def result(self) -> None: - return None - - def done(self) -> bool: - return True - - -# _null_future is used as a dummy value in the coroutine runner. It differs -# from moment in that moment always adds a delay of one IOLoop iteration -# while _null_future is processed as soon as possible. -_null_future = typing.cast(Future, _NullFuture()) - -moment = typing.cast(Future, _NullFuture()) -moment.__doc__ = """A special object which may be yielded to allow the IOLoop to run for -one iteration. - -This is not needed in normal use but it can be helpful in long-running -coroutines that are likely to yield Futures that are ready instantly. - -Usage: ``yield gen.moment`` - -In native coroutines, the equivalent of ``yield gen.moment`` is -``await asyncio.sleep(0)``. - -.. versionadded:: 4.0 - -.. deprecated:: 4.5 - ``yield None`` (or ``yield`` with no argument) is now equivalent to - ``yield gen.moment``. -""" - - -class Runner(object): - """Internal implementation of `tornado.gen.coroutine`. - - Maintains information about pending callbacks and their results. - - The results of the generator are stored in ``result_future`` (a - `.Future`) - """ - - def __init__( - self, - ctx_run: Callable, - gen: "Generator[_Yieldable, Any, _T]", - result_future: "Future[_T]", - first_yielded: _Yieldable, - ) -> None: - self.ctx_run = ctx_run - self.gen = gen - self.result_future = result_future - self.future = _null_future # type: Union[None, Future] - self.running = False - self.finished = False - self.io_loop = IOLoop.current() - if self.handle_yield(first_yielded): - gen = result_future = first_yielded = None # type: ignore - self.ctx_run(self.run) - - def run(self) -> None: - """Starts or resumes the generator, running until it reaches a - yield point that is not ready. - """ - if self.running or self.finished: - return - try: - self.running = True - while True: - future = self.future - if future is None: - raise Exception("No pending future") - if not future.done(): - return - self.future = None - try: - exc_info = None - - try: - value = future.result() - except Exception: - exc_info = sys.exc_info() - future = None - - if exc_info is not None: - try: - yielded = self.gen.throw(*exc_info) # type: ignore - finally: - # Break up a reference to itself - # for faster GC on CPython. - exc_info = None - else: - yielded = self.gen.send(value) - - except (StopIteration, Return) as e: - self.finished = True - self.future = _null_future - future_set_result_unless_cancelled( - self.result_future, _value_from_stopiteration(e) - ) - self.result_future = None # type: ignore - return - except Exception: - self.finished = True - self.future = _null_future - future_set_exc_info(self.result_future, sys.exc_info()) - self.result_future = None # type: ignore - return - if not self.handle_yield(yielded): - return - yielded = None - finally: - self.running = False - - def handle_yield(self, yielded: _Yieldable) -> bool: - try: - self.future = convert_yielded(yielded) - except BadYieldError: - self.future = Future() - future_set_exc_info(self.future, sys.exc_info()) - - if self.future is moment: - self.io_loop.add_callback(self.ctx_run, self.run) - return False - elif self.future is None: - raise Exception("no pending future") - elif not self.future.done(): - - def inner(f: Any) -> None: - # Break a reference cycle to speed GC. - f = None # noqa: F841 - self.ctx_run(self.run) - - self.io_loop.add_future(self.future, inner) - return False - return True - - def handle_exception( - self, typ: Type[Exception], value: Exception, tb: types.TracebackType - ) -> bool: - if not self.running and not self.finished: - self.future = Future() - future_set_exc_info(self.future, (typ, value, tb)) - self.ctx_run(self.run) - return True - else: - return False - - -# Convert Awaitables into Futures. -try: - _wrap_awaitable = asyncio.ensure_future -except AttributeError: - # asyncio.ensure_future was introduced in Python 3.4.4, but - # Debian jessie still ships with 3.4.2 so try the old name. - _wrap_awaitable = getattr(asyncio, "async") - - -def convert_yielded(yielded: _Yieldable) -> Future: - """Convert a yielded object into a `.Future`. - - The default implementation accepts lists, dictionaries, and - Futures. This has the side effect of starting any coroutines that - did not start themselves, similar to `asyncio.ensure_future`. - - If the `~functools.singledispatch` library is available, this function - may be extended to support additional types. For example:: - - @convert_yielded.register(asyncio.Future) - def _(asyncio_future): - return tornado.platform.asyncio.to_tornado_future(asyncio_future) - - .. versionadded:: 4.1 - - """ - if yielded is None or yielded is moment: - return moment - elif yielded is _null_future: - return _null_future - elif isinstance(yielded, (list, dict)): - return multi(yielded) # type: ignore - elif is_future(yielded): - return typing.cast(Future, yielded) - elif isawaitable(yielded): - return _wrap_awaitable(yielded) # type: ignore - else: - raise BadYieldError("yielded unknown object %r" % (yielded,)) - - -convert_yielded = singledispatch(convert_yielded) diff --git a/telegramer/include/tornado/http1connection.py b/telegramer/include/tornado/http1connection.py deleted file mode 100644 index 835027b..0000000 --- a/telegramer/include/tornado/http1connection.py +++ /dev/null @@ -1,842 +0,0 @@ -# -# Copyright 2014 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Client and server implementations of HTTP/1.x. - -.. versionadded:: 4.0 -""" - -import asyncio -import logging -import re -import types - -from tornado.concurrent import ( - Future, - future_add_done_callback, - future_set_result_unless_cancelled, -) -from tornado.escape import native_str, utf8 -from tornado import gen -from tornado import httputil -from tornado import iostream -from tornado.log import gen_log, app_log -from tornado.util import GzipDecompressor - - -from typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple - - -class _QuietException(Exception): - def __init__(self) -> None: - pass - - -class _ExceptionLoggingContext(object): - """Used with the ``with`` statement when calling delegate methods to - log any exceptions with the given logger. Any exceptions caught are - converted to _QuietException - """ - - def __init__(self, logger: logging.Logger) -> None: - self.logger = logger - - def __enter__(self) -> None: - pass - - def __exit__( - self, - typ: "Optional[Type[BaseException]]", - value: Optional[BaseException], - tb: types.TracebackType, - ) -> None: - if value is not None: - assert typ is not None - self.logger.error("Uncaught exception", exc_info=(typ, value, tb)) - raise _QuietException - - -class HTTP1ConnectionParameters(object): - """Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`. - """ - - def __init__( - self, - no_keep_alive: bool = False, - chunk_size: Optional[int] = None, - max_header_size: Optional[int] = None, - header_timeout: Optional[float] = None, - max_body_size: Optional[int] = None, - body_timeout: Optional[float] = None, - decompress: bool = False, - ) -> None: - """ - :arg bool no_keep_alive: If true, always close the connection after - one request. - :arg int chunk_size: how much data to read into memory at once - :arg int max_header_size: maximum amount of data for HTTP headers - :arg float header_timeout: how long to wait for all headers (seconds) - :arg int max_body_size: maximum amount of data for body - :arg float body_timeout: how long to wait while reading body (seconds) - :arg bool decompress: if true, decode incoming - ``Content-Encoding: gzip`` - """ - self.no_keep_alive = no_keep_alive - self.chunk_size = chunk_size or 65536 - self.max_header_size = max_header_size or 65536 - self.header_timeout = header_timeout - self.max_body_size = max_body_size - self.body_timeout = body_timeout - self.decompress = decompress - - -class HTTP1Connection(httputil.HTTPConnection): - """Implements the HTTP/1.x protocol. - - This class can be on its own for clients, or via `HTTP1ServerConnection` - for servers. - """ - - def __init__( - self, - stream: iostream.IOStream, - is_client: bool, - params: Optional[HTTP1ConnectionParameters] = None, - context: Optional[object] = None, - ) -> None: - """ - :arg stream: an `.IOStream` - :arg bool is_client: client or server - :arg params: a `.HTTP1ConnectionParameters` instance or ``None`` - :arg context: an opaque application-defined object that can be accessed - as ``connection.context``. - """ - self.is_client = is_client - self.stream = stream - if params is None: - params = HTTP1ConnectionParameters() - self.params = params - self.context = context - self.no_keep_alive = params.no_keep_alive - # The body limits can be altered by the delegate, so save them - # here instead of just referencing self.params later. - self._max_body_size = self.params.max_body_size or self.stream.max_buffer_size - self._body_timeout = self.params.body_timeout - # _write_finished is set to True when finish() has been called, - # i.e. there will be no more data sent. Data may still be in the - # stream's write buffer. - self._write_finished = False - # True when we have read the entire incoming body. - self._read_finished = False - # _finish_future resolves when all data has been written and flushed - # to the IOStream. - self._finish_future = Future() # type: Future[None] - # If true, the connection should be closed after this request - # (after the response has been written in the server side, - # and after it has been read in the client) - self._disconnect_on_finish = False - self._clear_callbacks() - # Save the start lines after we read or write them; they - # affect later processing (e.g. 304 responses and HEAD methods - # have content-length but no bodies) - self._request_start_line = None # type: Optional[httputil.RequestStartLine] - self._response_start_line = None # type: Optional[httputil.ResponseStartLine] - self._request_headers = None # type: Optional[httputil.HTTPHeaders] - # True if we are writing output with chunked encoding. - self._chunking_output = False - # While reading a body with a content-length, this is the - # amount left to read. - self._expected_content_remaining = None # type: Optional[int] - # A Future for our outgoing writes, returned by IOStream.write. - self._pending_write = None # type: Optional[Future[None]] - - def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]: - """Read a single HTTP response. - - Typical client-mode usage is to write a request using `write_headers`, - `write`, and `finish`, and then call ``read_response``. - - :arg delegate: a `.HTTPMessageDelegate` - - Returns a `.Future` that resolves to a bool after the full response has - been read. The result is true if the stream is still open. - """ - if self.params.decompress: - delegate = _GzipMessageDelegate(delegate, self.params.chunk_size) - return self._read_message(delegate) - - async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool: - need_delegate_close = False - try: - header_future = self.stream.read_until_regex( - b"\r?\n\r?\n", max_bytes=self.params.max_header_size - ) - if self.params.header_timeout is None: - header_data = await header_future - else: - try: - header_data = await gen.with_timeout( - self.stream.io_loop.time() + self.params.header_timeout, - header_future, - quiet_exceptions=iostream.StreamClosedError, - ) - except gen.TimeoutError: - self.close() - return False - start_line_str, headers = self._parse_headers(header_data) - if self.is_client: - resp_start_line = httputil.parse_response_start_line(start_line_str) - self._response_start_line = resp_start_line - start_line = ( - resp_start_line - ) # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine] - # TODO: this will need to change to support client-side keepalive - self._disconnect_on_finish = False - else: - req_start_line = httputil.parse_request_start_line(start_line_str) - self._request_start_line = req_start_line - self._request_headers = headers - start_line = req_start_line - self._disconnect_on_finish = not self._can_keep_alive( - req_start_line, headers - ) - need_delegate_close = True - with _ExceptionLoggingContext(app_log): - header_recv_future = delegate.headers_received(start_line, headers) - if header_recv_future is not None: - await header_recv_future - if self.stream is None: - # We've been detached. - need_delegate_close = False - return False - skip_body = False - if self.is_client: - assert isinstance(start_line, httputil.ResponseStartLine) - if ( - self._request_start_line is not None - and self._request_start_line.method == "HEAD" - ): - skip_body = True - code = start_line.code - if code == 304: - # 304 responses may include the content-length header - # but do not actually have a body. - # http://tools.ietf.org/html/rfc7230#section-3.3 - skip_body = True - if 100 <= code < 200: - # 1xx responses should never indicate the presence of - # a body. - if "Content-Length" in headers or "Transfer-Encoding" in headers: - raise httputil.HTTPInputError( - "Response code %d cannot have body" % code - ) - # TODO: client delegates will get headers_received twice - # in the case of a 100-continue. Document or change? - await self._read_message(delegate) - else: - if headers.get("Expect") == "100-continue" and not self._write_finished: - self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n") - if not skip_body: - body_future = self._read_body( - resp_start_line.code if self.is_client else 0, headers, delegate - ) - if body_future is not None: - if self._body_timeout is None: - await body_future - else: - try: - await gen.with_timeout( - self.stream.io_loop.time() + self._body_timeout, - body_future, - quiet_exceptions=iostream.StreamClosedError, - ) - except gen.TimeoutError: - gen_log.info("Timeout reading body from %s", self.context) - self.stream.close() - return False - self._read_finished = True - if not self._write_finished or self.is_client: - need_delegate_close = False - with _ExceptionLoggingContext(app_log): - delegate.finish() - # If we're waiting for the application to produce an asynchronous - # response, and we're not detached, register a close callback - # on the stream (we didn't need one while we were reading) - if ( - not self._finish_future.done() - and self.stream is not None - and not self.stream.closed() - ): - self.stream.set_close_callback(self._on_connection_close) - await self._finish_future - if self.is_client and self._disconnect_on_finish: - self.close() - if self.stream is None: - return False - except httputil.HTTPInputError as e: - gen_log.info("Malformed HTTP message from %s: %s", self.context, e) - if not self.is_client: - await self.stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n") - self.close() - return False - finally: - if need_delegate_close: - with _ExceptionLoggingContext(app_log): - delegate.on_connection_close() - header_future = None # type: ignore - self._clear_callbacks() - return True - - def _clear_callbacks(self) -> None: - """Clears the callback attributes. - - This allows the request handler to be garbage collected more - quickly in CPython by breaking up reference cycles. - """ - self._write_callback = None - self._write_future = None # type: Optional[Future[None]] - self._close_callback = None # type: Optional[Callable[[], None]] - if self.stream is not None: - self.stream.set_close_callback(None) - - def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None: - """Sets a callback that will be run when the connection is closed. - - Note that this callback is slightly different from - `.HTTPMessageDelegate.on_connection_close`: The - `.HTTPMessageDelegate` method is called when the connection is - closed while receiving a message. This callback is used when - there is not an active delegate (for example, on the server - side this callback is used if the client closes the connection - after sending its request but before receiving all the - response. - """ - self._close_callback = callback - - def _on_connection_close(self) -> None: - # Note that this callback is only registered on the IOStream - # when we have finished reading the request and are waiting for - # the application to produce its response. - if self._close_callback is not None: - callback = self._close_callback - self._close_callback = None - callback() - if not self._finish_future.done(): - future_set_result_unless_cancelled(self._finish_future, None) - self._clear_callbacks() - - def close(self) -> None: - if self.stream is not None: - self.stream.close() - self._clear_callbacks() - if not self._finish_future.done(): - future_set_result_unless_cancelled(self._finish_future, None) - - def detach(self) -> iostream.IOStream: - """Take control of the underlying stream. - - Returns the underlying `.IOStream` object and stops all further - HTTP processing. May only be called during - `.HTTPMessageDelegate.headers_received`. Intended for implementing - protocols like websockets that tunnel over an HTTP handshake. - """ - self._clear_callbacks() - stream = self.stream - self.stream = None # type: ignore - if not self._finish_future.done(): - future_set_result_unless_cancelled(self._finish_future, None) - return stream - - def set_body_timeout(self, timeout: float) -> None: - """Sets the body timeout for a single request. - - Overrides the value from `.HTTP1ConnectionParameters`. - """ - self._body_timeout = timeout - - def set_max_body_size(self, max_body_size: int) -> None: - """Sets the body size limit for a single request. - - Overrides the value from `.HTTP1ConnectionParameters`. - """ - self._max_body_size = max_body_size - - def write_headers( - self, - start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], - headers: httputil.HTTPHeaders, - chunk: Optional[bytes] = None, - ) -> "Future[None]": - """Implements `.HTTPConnection.write_headers`.""" - lines = [] - if self.is_client: - assert isinstance(start_line, httputil.RequestStartLine) - self._request_start_line = start_line - lines.append(utf8("%s %s HTTP/1.1" % (start_line[0], start_line[1]))) - # Client requests with a non-empty body must have either a - # Content-Length or a Transfer-Encoding. - self._chunking_output = ( - start_line.method in ("POST", "PUT", "PATCH") - and "Content-Length" not in headers - and ( - "Transfer-Encoding" not in headers - or headers["Transfer-Encoding"] == "chunked" - ) - ) - else: - assert isinstance(start_line, httputil.ResponseStartLine) - assert self._request_start_line is not None - assert self._request_headers is not None - self._response_start_line = start_line - lines.append(utf8("HTTP/1.1 %d %s" % (start_line[1], start_line[2]))) - self._chunking_output = ( - # TODO: should this use - # self._request_start_line.version or - # start_line.version? - self._request_start_line.version == "HTTP/1.1" - # Omit payload header field for HEAD request. - and self._request_start_line.method != "HEAD" - # 1xx, 204 and 304 responses have no body (not even a zero-length - # body), and so should not have either Content-Length or - # Transfer-Encoding headers. - and start_line.code not in (204, 304) - and (start_line.code < 100 or start_line.code >= 200) - # No need to chunk the output if a Content-Length is specified. - and "Content-Length" not in headers - # Applications are discouraged from touching Transfer-Encoding, - # but if they do, leave it alone. - and "Transfer-Encoding" not in headers - ) - # If connection to a 1.1 client will be closed, inform client - if ( - self._request_start_line.version == "HTTP/1.1" - and self._disconnect_on_finish - ): - headers["Connection"] = "close" - # If a 1.0 client asked for keep-alive, add the header. - if ( - self._request_start_line.version == "HTTP/1.0" - and self._request_headers.get("Connection", "").lower() == "keep-alive" - ): - headers["Connection"] = "Keep-Alive" - if self._chunking_output: - headers["Transfer-Encoding"] = "chunked" - if not self.is_client and ( - self._request_start_line.method == "HEAD" - or cast(httputil.ResponseStartLine, start_line).code == 304 - ): - self._expected_content_remaining = 0 - elif "Content-Length" in headers: - self._expected_content_remaining = int(headers["Content-Length"]) - else: - self._expected_content_remaining = None - # TODO: headers are supposed to be of type str, but we still have some - # cases that let bytes slip through. Remove these native_str calls when those - # are fixed. - header_lines = ( - native_str(n) + ": " + native_str(v) for n, v in headers.get_all() - ) - lines.extend(line.encode("latin1") for line in header_lines) - for line in lines: - if b"\n" in line: - raise ValueError("Newline in header: " + repr(line)) - future = None - if self.stream.closed(): - future = self._write_future = Future() - future.set_exception(iostream.StreamClosedError()) - future.exception() - else: - future = self._write_future = Future() - data = b"\r\n".join(lines) + b"\r\n\r\n" - if chunk: - data += self._format_chunk(chunk) - self._pending_write = self.stream.write(data) - future_add_done_callback(self._pending_write, self._on_write_complete) - return future - - def _format_chunk(self, chunk: bytes) -> bytes: - if self._expected_content_remaining is not None: - self._expected_content_remaining -= len(chunk) - if self._expected_content_remaining < 0: - # Close the stream now to stop further framing errors. - self.stream.close() - raise httputil.HTTPOutputError( - "Tried to write more data than Content-Length" - ) - if self._chunking_output and chunk: - # Don't write out empty chunks because that means END-OF-STREAM - # with chunked encoding - return utf8("%x" % len(chunk)) + b"\r\n" + chunk + b"\r\n" - else: - return chunk - - def write(self, chunk: bytes) -> "Future[None]": - """Implements `.HTTPConnection.write`. - - For backwards compatibility it is allowed but deprecated to - skip `write_headers` and instead call `write()` with a - pre-encoded header block. - """ - future = None - if self.stream.closed(): - future = self._write_future = Future() - self._write_future.set_exception(iostream.StreamClosedError()) - self._write_future.exception() - else: - future = self._write_future = Future() - self._pending_write = self.stream.write(self._format_chunk(chunk)) - future_add_done_callback(self._pending_write, self._on_write_complete) - return future - - def finish(self) -> None: - """Implements `.HTTPConnection.finish`.""" - if ( - self._expected_content_remaining is not None - and self._expected_content_remaining != 0 - and not self.stream.closed() - ): - self.stream.close() - raise httputil.HTTPOutputError( - "Tried to write %d bytes less than Content-Length" - % self._expected_content_remaining - ) - if self._chunking_output: - if not self.stream.closed(): - self._pending_write = self.stream.write(b"0\r\n\r\n") - self._pending_write.add_done_callback(self._on_write_complete) - self._write_finished = True - # If the app finished the request while we're still reading, - # divert any remaining data away from the delegate and - # close the connection when we're done sending our response. - # Closing the connection is the only way to avoid reading the - # whole input body. - if not self._read_finished: - self._disconnect_on_finish = True - # No more data is coming, so instruct TCP to send any remaining - # data immediately instead of waiting for a full packet or ack. - self.stream.set_nodelay(True) - if self._pending_write is None: - self._finish_request(None) - else: - future_add_done_callback(self._pending_write, self._finish_request) - - def _on_write_complete(self, future: "Future[None]") -> None: - exc = future.exception() - if exc is not None and not isinstance(exc, iostream.StreamClosedError): - future.result() - if self._write_callback is not None: - callback = self._write_callback - self._write_callback = None - self.stream.io_loop.add_callback(callback) - if self._write_future is not None: - future = self._write_future - self._write_future = None - future_set_result_unless_cancelled(future, None) - - def _can_keep_alive( - self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders - ) -> bool: - if self.params.no_keep_alive: - return False - connection_header = headers.get("Connection") - if connection_header is not None: - connection_header = connection_header.lower() - if start_line.version == "HTTP/1.1": - return connection_header != "close" - elif ( - "Content-Length" in headers - or headers.get("Transfer-Encoding", "").lower() == "chunked" - or getattr(start_line, "method", None) in ("HEAD", "GET") - ): - # start_line may be a request or response start line; only - # the former has a method attribute. - return connection_header == "keep-alive" - return False - - def _finish_request(self, future: "Optional[Future[None]]") -> None: - self._clear_callbacks() - if not self.is_client and self._disconnect_on_finish: - self.close() - return - # Turn Nagle's algorithm back on, leaving the stream in its - # default state for the next request. - self.stream.set_nodelay(False) - if not self._finish_future.done(): - future_set_result_unless_cancelled(self._finish_future, None) - - def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]: - # The lstrip removes newlines that some implementations sometimes - # insert between messages of a reused connection. Per RFC 7230, - # we SHOULD ignore at least one empty line before the request. - # http://tools.ietf.org/html/rfc7230#section-3.5 - data_str = native_str(data.decode("latin1")).lstrip("\r\n") - # RFC 7230 section allows for both CRLF and bare LF. - eol = data_str.find("\n") - start_line = data_str[:eol].rstrip("\r") - headers = httputil.HTTPHeaders.parse(data_str[eol:]) - return start_line, headers - - def _read_body( - self, - code: int, - headers: httputil.HTTPHeaders, - delegate: httputil.HTTPMessageDelegate, - ) -> Optional[Awaitable[None]]: - if "Content-Length" in headers: - if "Transfer-Encoding" in headers: - # Response cannot contain both Content-Length and - # Transfer-Encoding headers. - # http://tools.ietf.org/html/rfc7230#section-3.3.3 - raise httputil.HTTPInputError( - "Response with both Transfer-Encoding and Content-Length" - ) - if "," in headers["Content-Length"]: - # Proxies sometimes cause Content-Length headers to get - # duplicated. If all the values are identical then we can - # use them but if they differ it's an error. - pieces = re.split(r",\s*", headers["Content-Length"]) - if any(i != pieces[0] for i in pieces): - raise httputil.HTTPInputError( - "Multiple unequal Content-Lengths: %r" - % headers["Content-Length"] - ) - headers["Content-Length"] = pieces[0] - - try: - content_length = int(headers["Content-Length"]) # type: Optional[int] - except ValueError: - # Handles non-integer Content-Length value. - raise httputil.HTTPInputError( - "Only integer Content-Length is allowed: %s" - % headers["Content-Length"] - ) - - if cast(int, content_length) > self._max_body_size: - raise httputil.HTTPInputError("Content-Length too long") - else: - content_length = None - - if code == 204: - # This response code is not allowed to have a non-empty body, - # and has an implicit length of zero instead of read-until-close. - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 - if "Transfer-Encoding" in headers or content_length not in (None, 0): - raise httputil.HTTPInputError( - "Response with code %d should not have body" % code - ) - content_length = 0 - - if content_length is not None: - return self._read_fixed_body(content_length, delegate) - if headers.get("Transfer-Encoding", "").lower() == "chunked": - return self._read_chunked_body(delegate) - if self.is_client: - return self._read_body_until_close(delegate) - return None - - async def _read_fixed_body( - self, content_length: int, delegate: httputil.HTTPMessageDelegate - ) -> None: - while content_length > 0: - body = await self.stream.read_bytes( - min(self.params.chunk_size, content_length), partial=True - ) - content_length -= len(body) - if not self._write_finished or self.is_client: - with _ExceptionLoggingContext(app_log): - ret = delegate.data_received(body) - if ret is not None: - await ret - - async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None: - # TODO: "chunk extensions" http://tools.ietf.org/html/rfc2616#section-3.6.1 - total_size = 0 - while True: - chunk_len_str = await self.stream.read_until(b"\r\n", max_bytes=64) - chunk_len = int(chunk_len_str.strip(), 16) - if chunk_len == 0: - crlf = await self.stream.read_bytes(2) - if crlf != b"\r\n": - raise httputil.HTTPInputError( - "improperly terminated chunked request" - ) - return - total_size += chunk_len - if total_size > self._max_body_size: - raise httputil.HTTPInputError("chunked body too large") - bytes_to_read = chunk_len - while bytes_to_read: - chunk = await self.stream.read_bytes( - min(bytes_to_read, self.params.chunk_size), partial=True - ) - bytes_to_read -= len(chunk) - if not self._write_finished or self.is_client: - with _ExceptionLoggingContext(app_log): - ret = delegate.data_received(chunk) - if ret is not None: - await ret - # chunk ends with \r\n - crlf = await self.stream.read_bytes(2) - assert crlf == b"\r\n" - - async def _read_body_until_close( - self, delegate: httputil.HTTPMessageDelegate - ) -> None: - body = await self.stream.read_until_close() - if not self._write_finished or self.is_client: - with _ExceptionLoggingContext(app_log): - ret = delegate.data_received(body) - if ret is not None: - await ret - - -class _GzipMessageDelegate(httputil.HTTPMessageDelegate): - """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``. - """ - - def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None: - self._delegate = delegate - self._chunk_size = chunk_size - self._decompressor = None # type: Optional[GzipDecompressor] - - def headers_received( - self, - start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], - headers: httputil.HTTPHeaders, - ) -> Optional[Awaitable[None]]: - if headers.get("Content-Encoding") == "gzip": - self._decompressor = GzipDecompressor() - # Downstream delegates will only see uncompressed data, - # so rename the content-encoding header. - # (but note that curl_httpclient doesn't do this). - headers.add("X-Consumed-Content-Encoding", headers["Content-Encoding"]) - del headers["Content-Encoding"] - return self._delegate.headers_received(start_line, headers) - - async def data_received(self, chunk: bytes) -> None: - if self._decompressor: - compressed_data = chunk - while compressed_data: - decompressed = self._decompressor.decompress( - compressed_data, self._chunk_size - ) - if decompressed: - ret = self._delegate.data_received(decompressed) - if ret is not None: - await ret - compressed_data = self._decompressor.unconsumed_tail - if compressed_data and not decompressed: - raise httputil.HTTPInputError( - "encountered unconsumed gzip data without making progress" - ) - else: - ret = self._delegate.data_received(chunk) - if ret is not None: - await ret - - def finish(self) -> None: - if self._decompressor is not None: - tail = self._decompressor.flush() - if tail: - # The tail should always be empty: decompress returned - # all that it can in data_received and the only - # purpose of the flush call is to detect errors such - # as truncated input. If we did legitimately get a new - # chunk at this point we'd need to change the - # interface to make finish() a coroutine. - raise ValueError( - "decompressor.flush returned data; possible truncated input" - ) - return self._delegate.finish() - - def on_connection_close(self) -> None: - return self._delegate.on_connection_close() - - -class HTTP1ServerConnection(object): - """An HTTP/1.x server.""" - - def __init__( - self, - stream: iostream.IOStream, - params: Optional[HTTP1ConnectionParameters] = None, - context: Optional[object] = None, - ) -> None: - """ - :arg stream: an `.IOStream` - :arg params: a `.HTTP1ConnectionParameters` or None - :arg context: an opaque application-defined object that is accessible - as ``connection.context`` - """ - self.stream = stream - if params is None: - params = HTTP1ConnectionParameters() - self.params = params - self.context = context - self._serving_future = None # type: Optional[Future[None]] - - async def close(self) -> None: - """Closes the connection. - - Returns a `.Future` that resolves after the serving loop has exited. - """ - self.stream.close() - # Block until the serving loop is done, but ignore any exceptions - # (start_serving is already responsible for logging them). - assert self._serving_future is not None - try: - await self._serving_future - except Exception: - pass - - def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None: - """Starts serving requests on this connection. - - :arg delegate: a `.HTTPServerConnectionDelegate` - """ - assert isinstance(delegate, httputil.HTTPServerConnectionDelegate) - fut = gen.convert_yielded(self._server_request_loop(delegate)) - self._serving_future = fut - # Register the future on the IOLoop so its errors get logged. - self.stream.io_loop.add_future(fut, lambda f: f.result()) - - async def _server_request_loop( - self, delegate: httputil.HTTPServerConnectionDelegate - ) -> None: - try: - while True: - conn = HTTP1Connection(self.stream, False, self.params, self.context) - request_delegate = delegate.start_request(self, conn) - try: - ret = await conn.read_response(request_delegate) - except ( - iostream.StreamClosedError, - iostream.UnsatisfiableReadError, - asyncio.CancelledError, - ): - return - except _QuietException: - # This exception was already logged. - conn.close() - return - except Exception: - gen_log.error("Uncaught exception", exc_info=True) - conn.close() - return - if not ret: - return - await asyncio.sleep(0) - finally: - delegate.on_close(self) diff --git a/telegramer/include/tornado/httpclient.py b/telegramer/include/tornado/httpclient.py deleted file mode 100644 index 3011c37..0000000 --- a/telegramer/include/tornado/httpclient.py +++ /dev/null @@ -1,790 +0,0 @@ -"""Blocking and non-blocking HTTP client interfaces. - -This module defines a common interface shared by two implementations, -``simple_httpclient`` and ``curl_httpclient``. Applications may either -instantiate their chosen implementation class directly or use the -`AsyncHTTPClient` class from this module, which selects an implementation -that can be overridden with the `AsyncHTTPClient.configure` method. - -The default implementation is ``simple_httpclient``, and this is expected -to be suitable for most users' needs. However, some applications may wish -to switch to ``curl_httpclient`` for reasons such as the following: - -* ``curl_httpclient`` has some features not found in ``simple_httpclient``, - including support for HTTP proxies and the ability to use a specified - network interface. - -* ``curl_httpclient`` is more likely to be compatible with sites that are - not-quite-compliant with the HTTP spec, or sites that use little-exercised - features of HTTP. - -* ``curl_httpclient`` is faster. - -Note that if you are using ``curl_httpclient``, it is highly -recommended that you use a recent version of ``libcurl`` and -``pycurl``. Currently the minimum supported version of libcurl is -7.22.0, and the minimum version of pycurl is 7.18.2. It is highly -recommended that your ``libcurl`` installation is built with -asynchronous DNS resolver (threaded or c-ares), otherwise you may -encounter various problems with request timeouts (for more -information, see -http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUTMS -and comments in curl_httpclient.py). - -To select ``curl_httpclient``, call `AsyncHTTPClient.configure` at startup:: - - AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient") -""" - -import datetime -import functools -from io import BytesIO -import ssl -import time -import weakref - -from tornado.concurrent import ( - Future, - future_set_result_unless_cancelled, - future_set_exception_unless_cancelled, -) -from tornado.escape import utf8, native_str -from tornado import gen, httputil -from tornado.ioloop import IOLoop -from tornado.util import Configurable - -from typing import Type, Any, Union, Dict, Callable, Optional, cast - - -class HTTPClient(object): - """A blocking HTTP client. - - This interface is provided to make it easier to share code between - synchronous and asynchronous applications. Applications that are - running an `.IOLoop` must use `AsyncHTTPClient` instead. - - Typical usage looks like this:: - - http_client = httpclient.HTTPClient() - try: - response = http_client.fetch("http://www.google.com/") - print(response.body) - except httpclient.HTTPError as e: - # HTTPError is raised for non-200 responses; the response - # can be found in e.response. - print("Error: " + str(e)) - except Exception as e: - # Other errors are possible, such as IOError. - print("Error: " + str(e)) - http_client.close() - - .. versionchanged:: 5.0 - - Due to limitations in `asyncio`, it is no longer possible to - use the synchronous ``HTTPClient`` while an `.IOLoop` is running. - Use `AsyncHTTPClient` instead. - - """ - - def __init__( - self, - async_client_class: "Optional[Type[AsyncHTTPClient]]" = None, - **kwargs: Any - ) -> None: - # Initialize self._closed at the beginning of the constructor - # so that an exception raised here doesn't lead to confusing - # failures in __del__. - self._closed = True - self._io_loop = IOLoop(make_current=False) - if async_client_class is None: - async_client_class = AsyncHTTPClient - - # Create the client while our IOLoop is "current", without - # clobbering the thread's real current IOLoop (if any). - async def make_client() -> "AsyncHTTPClient": - await gen.sleep(0) - assert async_client_class is not None - return async_client_class(**kwargs) - - self._async_client = self._io_loop.run_sync(make_client) - self._closed = False - - def __del__(self) -> None: - self.close() - - def close(self) -> None: - """Closes the HTTPClient, freeing any resources used.""" - if not self._closed: - self._async_client.close() - self._io_loop.close() - self._closed = True - - def fetch( - self, request: Union["HTTPRequest", str], **kwargs: Any - ) -> "HTTPResponse": - """Executes a request, returning an `HTTPResponse`. - - The request may be either a string URL or an `HTTPRequest` object. - If it is a string, we construct an `HTTPRequest` using any additional - kwargs: ``HTTPRequest(request, **kwargs)`` - - If an error occurs during the fetch, we raise an `HTTPError` unless - the ``raise_error`` keyword argument is set to False. - """ - response = self._io_loop.run_sync( - functools.partial(self._async_client.fetch, request, **kwargs) - ) - return response - - -class AsyncHTTPClient(Configurable): - """An non-blocking HTTP client. - - Example usage:: - - async def f(): - http_client = AsyncHTTPClient() - try: - response = await http_client.fetch("http://www.google.com") - except Exception as e: - print("Error: %s" % e) - else: - print(response.body) - - The constructor for this class is magic in several respects: It - actually creates an instance of an implementation-specific - subclass, and instances are reused as a kind of pseudo-singleton - (one per `.IOLoop`). The keyword argument ``force_instance=True`` - can be used to suppress this singleton behavior. Unless - ``force_instance=True`` is used, no arguments should be passed to - the `AsyncHTTPClient` constructor. The implementation subclass as - well as arguments to its constructor can be set with the static - method `configure()` - - All `AsyncHTTPClient` implementations support a ``defaults`` - keyword argument, which can be used to set default values for - `HTTPRequest` attributes. For example:: - - AsyncHTTPClient.configure( - None, defaults=dict(user_agent="MyUserAgent")) - # or with force_instance: - client = AsyncHTTPClient(force_instance=True, - defaults=dict(user_agent="MyUserAgent")) - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - - """ - - _instance_cache = None # type: Dict[IOLoop, AsyncHTTPClient] - - @classmethod - def configurable_base(cls) -> Type[Configurable]: - return AsyncHTTPClient - - @classmethod - def configurable_default(cls) -> Type[Configurable]: - from tornado.simple_httpclient import SimpleAsyncHTTPClient - - return SimpleAsyncHTTPClient - - @classmethod - def _async_clients(cls) -> Dict[IOLoop, "AsyncHTTPClient"]: - attr_name = "_async_client_dict_" + cls.__name__ - if not hasattr(cls, attr_name): - setattr(cls, attr_name, weakref.WeakKeyDictionary()) - return getattr(cls, attr_name) - - def __new__(cls, force_instance: bool = False, **kwargs: Any) -> "AsyncHTTPClient": - io_loop = IOLoop.current() - if force_instance: - instance_cache = None - else: - instance_cache = cls._async_clients() - if instance_cache is not None and io_loop in instance_cache: - return instance_cache[io_loop] - instance = super(AsyncHTTPClient, cls).__new__(cls, **kwargs) # type: ignore - # Make sure the instance knows which cache to remove itself from. - # It can't simply call _async_clients() because we may be in - # __new__(AsyncHTTPClient) but instance.__class__ may be - # SimpleAsyncHTTPClient. - instance._instance_cache = instance_cache - if instance_cache is not None: - instance_cache[instance.io_loop] = instance - return instance - - def initialize(self, defaults: Optional[Dict[str, Any]] = None) -> None: - self.io_loop = IOLoop.current() - self.defaults = dict(HTTPRequest._DEFAULTS) - if defaults is not None: - self.defaults.update(defaults) - self._closed = False - - def close(self) -> None: - """Destroys this HTTP client, freeing any file descriptors used. - - This method is **not needed in normal use** due to the way - that `AsyncHTTPClient` objects are transparently reused. - ``close()`` is generally only necessary when either the - `.IOLoop` is also being closed, or the ``force_instance=True`` - argument was used when creating the `AsyncHTTPClient`. - - No other methods may be called on the `AsyncHTTPClient` after - ``close()``. - - """ - if self._closed: - return - self._closed = True - if self._instance_cache is not None: - cached_val = self._instance_cache.pop(self.io_loop, None) - # If there's an object other than self in the instance - # cache for our IOLoop, something has gotten mixed up. A - # value of None appears to be possible when this is called - # from a destructor (HTTPClient.__del__) as the weakref - # gets cleared before the destructor runs. - if cached_val is not None and cached_val is not self: - raise RuntimeError("inconsistent AsyncHTTPClient cache") - - def fetch( - self, - request: Union[str, "HTTPRequest"], - raise_error: bool = True, - **kwargs: Any - ) -> "Future[HTTPResponse]": - """Executes a request, asynchronously returning an `HTTPResponse`. - - The request may be either a string URL or an `HTTPRequest` object. - If it is a string, we construct an `HTTPRequest` using any additional - kwargs: ``HTTPRequest(request, **kwargs)`` - - This method returns a `.Future` whose result is an - `HTTPResponse`. By default, the ``Future`` will raise an - `HTTPError` if the request returned a non-200 response code - (other errors may also be raised if the server could not be - contacted). Instead, if ``raise_error`` is set to False, the - response will always be returned regardless of the response - code. - - If a ``callback`` is given, it will be invoked with the `HTTPResponse`. - In the callback interface, `HTTPError` is not automatically raised. - Instead, you must check the response's ``error`` attribute or - call its `~HTTPResponse.rethrow` method. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - `.Future` instead. - - The ``raise_error=False`` argument only affects the - `HTTPError` raised when a non-200 response code is used, - instead of suppressing all errors. - """ - if self._closed: - raise RuntimeError("fetch() called on closed AsyncHTTPClient") - if not isinstance(request, HTTPRequest): - request = HTTPRequest(url=request, **kwargs) - else: - if kwargs: - raise ValueError( - "kwargs can't be used if request is an HTTPRequest object" - ) - # We may modify this (to add Host, Accept-Encoding, etc), - # so make sure we don't modify the caller's object. This is also - # where normal dicts get converted to HTTPHeaders objects. - request.headers = httputil.HTTPHeaders(request.headers) - request_proxy = _RequestProxy(request, self.defaults) - future = Future() # type: Future[HTTPResponse] - - def handle_response(response: "HTTPResponse") -> None: - if response.error: - if raise_error or not response._error_is_response_code: - future_set_exception_unless_cancelled(future, response.error) - return - future_set_result_unless_cancelled(future, response) - - self.fetch_impl(cast(HTTPRequest, request_proxy), handle_response) - return future - - def fetch_impl( - self, request: "HTTPRequest", callback: Callable[["HTTPResponse"], None] - ) -> None: - raise NotImplementedError() - - @classmethod - def configure( - cls, impl: "Union[None, str, Type[Configurable]]", **kwargs: Any - ) -> None: - """Configures the `AsyncHTTPClient` subclass to use. - - ``AsyncHTTPClient()`` actually creates an instance of a subclass. - This method may be called with either a class object or the - fully-qualified name of such a class (or ``None`` to use the default, - ``SimpleAsyncHTTPClient``) - - If additional keyword arguments are given, they will be passed - to the constructor of each subclass instance created. The - keyword argument ``max_clients`` determines the maximum number - of simultaneous `~AsyncHTTPClient.fetch()` operations that can - execute in parallel on each `.IOLoop`. Additional arguments - may be supported depending on the implementation class in use. - - Example:: - - AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient") - """ - super(AsyncHTTPClient, cls).configure(impl, **kwargs) - - -class HTTPRequest(object): - """HTTP client request object.""" - - _headers = None # type: Union[Dict[str, str], httputil.HTTPHeaders] - - # Default values for HTTPRequest parameters. - # Merged with the values on the request object by AsyncHTTPClient - # implementations. - _DEFAULTS = dict( - connect_timeout=20.0, - request_timeout=20.0, - follow_redirects=True, - max_redirects=5, - decompress_response=True, - proxy_password="", - allow_nonstandard_methods=False, - validate_cert=True, - ) - - def __init__( - self, - url: str, - method: str = "GET", - headers: Optional[Union[Dict[str, str], httputil.HTTPHeaders]] = None, - body: Optional[Union[bytes, str]] = None, - auth_username: Optional[str] = None, - auth_password: Optional[str] = None, - auth_mode: Optional[str] = None, - connect_timeout: Optional[float] = None, - request_timeout: Optional[float] = None, - if_modified_since: Optional[Union[float, datetime.datetime]] = None, - follow_redirects: Optional[bool] = None, - max_redirects: Optional[int] = None, - user_agent: Optional[str] = None, - use_gzip: Optional[bool] = None, - network_interface: Optional[str] = None, - streaming_callback: Optional[Callable[[bytes], None]] = None, - header_callback: Optional[Callable[[str], None]] = None, - prepare_curl_callback: Optional[Callable[[Any], None]] = None, - proxy_host: Optional[str] = None, - proxy_port: Optional[int] = None, - proxy_username: Optional[str] = None, - proxy_password: Optional[str] = None, - proxy_auth_mode: Optional[str] = None, - allow_nonstandard_methods: Optional[bool] = None, - validate_cert: Optional[bool] = None, - ca_certs: Optional[str] = None, - allow_ipv6: Optional[bool] = None, - client_key: Optional[str] = None, - client_cert: Optional[str] = None, - body_producer: Optional[ - Callable[[Callable[[bytes], None]], "Future[None]"] - ] = None, - expect_100_continue: bool = False, - decompress_response: Optional[bool] = None, - ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None, - ) -> None: - r"""All parameters except ``url`` are optional. - - :arg str url: URL to fetch - :arg str method: HTTP method, e.g. "GET" or "POST" - :arg headers: Additional HTTP headers to pass on the request - :type headers: `~tornado.httputil.HTTPHeaders` or `dict` - :arg body: HTTP request body as a string (byte or unicode; if unicode - the utf-8 encoding will be used) - :type body: `str` or `bytes` - :arg collections.abc.Callable body_producer: Callable used for - lazy/asynchronous request bodies. - It is called with one argument, a ``write`` function, and should - return a `.Future`. It should call the write function with new - data as it becomes available. The write function returns a - `.Future` which can be used for flow control. - Only one of ``body`` and ``body_producer`` may - be specified. ``body_producer`` is not supported on - ``curl_httpclient``. When using ``body_producer`` it is recommended - to pass a ``Content-Length`` in the headers as otherwise chunked - encoding will be used, and many servers do not support chunked - encoding on requests. New in Tornado 4.0 - :arg str auth_username: Username for HTTP authentication - :arg str auth_password: Password for HTTP authentication - :arg str auth_mode: Authentication mode; default is "basic". - Allowed values are implementation-defined; ``curl_httpclient`` - supports "basic" and "digest"; ``simple_httpclient`` only supports - "basic" - :arg float connect_timeout: Timeout for initial connection in seconds, - default 20 seconds (0 means no timeout) - :arg float request_timeout: Timeout for entire request in seconds, - default 20 seconds (0 means no timeout) - :arg if_modified_since: Timestamp for ``If-Modified-Since`` header - :type if_modified_since: `datetime` or `float` - :arg bool follow_redirects: Should redirects be followed automatically - or return the 3xx response? Default True. - :arg int max_redirects: Limit for ``follow_redirects``, default 5. - :arg str user_agent: String to send as ``User-Agent`` header - :arg bool decompress_response: Request a compressed response from - the server and decompress it after downloading. Default is True. - New in Tornado 4.0. - :arg bool use_gzip: Deprecated alias for ``decompress_response`` - since Tornado 4.0. - :arg str network_interface: Network interface or source IP to use for request. - See ``curl_httpclient`` note below. - :arg collections.abc.Callable streaming_callback: If set, ``streaming_callback`` will - be run with each chunk of data as it is received, and - ``HTTPResponse.body`` and ``HTTPResponse.buffer`` will be empty in - the final response. - :arg collections.abc.Callable header_callback: If set, ``header_callback`` will - be run with each header line as it is received (including the - first line, e.g. ``HTTP/1.0 200 OK\r\n``, and a final line - containing only ``\r\n``. All lines include the trailing newline - characters). ``HTTPResponse.headers`` will be empty in the final - response. This is most useful in conjunction with - ``streaming_callback``, because it's the only way to get access to - header data while the request is in progress. - :arg collections.abc.Callable prepare_curl_callback: If set, will be called with - a ``pycurl.Curl`` object to allow the application to make additional - ``setopt`` calls. - :arg str proxy_host: HTTP proxy hostname. To use proxies, - ``proxy_host`` and ``proxy_port`` must be set; ``proxy_username``, - ``proxy_pass`` and ``proxy_auth_mode`` are optional. Proxies are - currently only supported with ``curl_httpclient``. - :arg int proxy_port: HTTP proxy port - :arg str proxy_username: HTTP proxy username - :arg str proxy_password: HTTP proxy password - :arg str proxy_auth_mode: HTTP proxy Authentication mode; - default is "basic". supports "basic" and "digest" - :arg bool allow_nonstandard_methods: Allow unknown values for ``method`` - argument? Default is False. - :arg bool validate_cert: For HTTPS requests, validate the server's - certificate? Default is True. - :arg str ca_certs: filename of CA certificates in PEM format, - or None to use defaults. See note below when used with - ``curl_httpclient``. - :arg str client_key: Filename for client SSL key, if any. See - note below when used with ``curl_httpclient``. - :arg str client_cert: Filename for client SSL certificate, if any. - See note below when used with ``curl_httpclient``. - :arg ssl.SSLContext ssl_options: `ssl.SSLContext` object for use in - ``simple_httpclient`` (unsupported by ``curl_httpclient``). - Overrides ``validate_cert``, ``ca_certs``, ``client_key``, - and ``client_cert``. - :arg bool allow_ipv6: Use IPv6 when available? Default is True. - :arg bool expect_100_continue: If true, send the - ``Expect: 100-continue`` header and wait for a continue response - before sending the request body. Only supported with - ``simple_httpclient``. - - .. note:: - - When using ``curl_httpclient`` certain options may be - inherited by subsequent fetches because ``pycurl`` does - not allow them to be cleanly reset. This applies to the - ``ca_certs``, ``client_key``, ``client_cert``, and - ``network_interface`` arguments. If you use these - options, you should pass them on every request (you don't - have to always use the same values, but it's not possible - to mix requests that specify these options with ones that - use the defaults). - - .. versionadded:: 3.1 - The ``auth_mode`` argument. - - .. versionadded:: 4.0 - The ``body_producer`` and ``expect_100_continue`` arguments. - - .. versionadded:: 4.2 - The ``ssl_options`` argument. - - .. versionadded:: 4.5 - The ``proxy_auth_mode`` argument. - """ - # Note that some of these attributes go through property setters - # defined below. - self.headers = headers # type: ignore - if if_modified_since: - self.headers["If-Modified-Since"] = httputil.format_timestamp( - if_modified_since - ) - self.proxy_host = proxy_host - self.proxy_port = proxy_port - self.proxy_username = proxy_username - self.proxy_password = proxy_password - self.proxy_auth_mode = proxy_auth_mode - self.url = url - self.method = method - self.body = body # type: ignore - self.body_producer = body_producer - self.auth_username = auth_username - self.auth_password = auth_password - self.auth_mode = auth_mode - self.connect_timeout = connect_timeout - self.request_timeout = request_timeout - self.follow_redirects = follow_redirects - self.max_redirects = max_redirects - self.user_agent = user_agent - if decompress_response is not None: - self.decompress_response = decompress_response # type: Optional[bool] - else: - self.decompress_response = use_gzip - self.network_interface = network_interface - self.streaming_callback = streaming_callback - self.header_callback = header_callback - self.prepare_curl_callback = prepare_curl_callback - self.allow_nonstandard_methods = allow_nonstandard_methods - self.validate_cert = validate_cert - self.ca_certs = ca_certs - self.allow_ipv6 = allow_ipv6 - self.client_key = client_key - self.client_cert = client_cert - self.ssl_options = ssl_options - self.expect_100_continue = expect_100_continue - self.start_time = time.time() - - @property - def headers(self) -> httputil.HTTPHeaders: - # TODO: headers may actually be a plain dict until fairly late in - # the process (AsyncHTTPClient.fetch), but practically speaking, - # whenever the property is used they're already HTTPHeaders. - return self._headers # type: ignore - - @headers.setter - def headers(self, value: Union[Dict[str, str], httputil.HTTPHeaders]) -> None: - if value is None: - self._headers = httputil.HTTPHeaders() - else: - self._headers = value # type: ignore - - @property - def body(self) -> bytes: - return self._body - - @body.setter - def body(self, value: Union[bytes, str]) -> None: - self._body = utf8(value) - - -class HTTPResponse(object): - """HTTP Response object. - - Attributes: - - * ``request``: HTTPRequest object - - * ``code``: numeric HTTP status code, e.g. 200 or 404 - - * ``reason``: human-readable reason phrase describing the status code - - * ``headers``: `tornado.httputil.HTTPHeaders` object - - * ``effective_url``: final location of the resource after following any - redirects - - * ``buffer``: ``cStringIO`` object for response body - - * ``body``: response body as bytes (created on demand from ``self.buffer``) - - * ``error``: Exception object, if any - - * ``request_time``: seconds from request start to finish. Includes all - network operations from DNS resolution to receiving the last byte of - data. Does not include time spent in the queue (due to the - ``max_clients`` option). If redirects were followed, only includes - the final request. - - * ``start_time``: Time at which the HTTP operation started, based on - `time.time` (not the monotonic clock used by `.IOLoop.time`). May - be ``None`` if the request timed out while in the queue. - - * ``time_info``: dictionary of diagnostic timing information from the - request. Available data are subject to change, but currently uses timings - available from http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html, - plus ``queue``, which is the delay (if any) introduced by waiting for - a slot under `AsyncHTTPClient`'s ``max_clients`` setting. - - .. versionadded:: 5.1 - - Added the ``start_time`` attribute. - - .. versionchanged:: 5.1 - - The ``request_time`` attribute previously included time spent in the queue - for ``simple_httpclient``, but not in ``curl_httpclient``. Now queueing time - is excluded in both implementations. ``request_time`` is now more accurate for - ``curl_httpclient`` because it uses a monotonic clock when available. - """ - - # I'm not sure why these don't get type-inferred from the references in __init__. - error = None # type: Optional[BaseException] - _error_is_response_code = False - request = None # type: HTTPRequest - - def __init__( - self, - request: HTTPRequest, - code: int, - headers: Optional[httputil.HTTPHeaders] = None, - buffer: Optional[BytesIO] = None, - effective_url: Optional[str] = None, - error: Optional[BaseException] = None, - request_time: Optional[float] = None, - time_info: Optional[Dict[str, float]] = None, - reason: Optional[str] = None, - start_time: Optional[float] = None, - ) -> None: - if isinstance(request, _RequestProxy): - self.request = request.request - else: - self.request = request - self.code = code - self.reason = reason or httputil.responses.get(code, "Unknown") - if headers is not None: - self.headers = headers - else: - self.headers = httputil.HTTPHeaders() - self.buffer = buffer - self._body = None # type: Optional[bytes] - if effective_url is None: - self.effective_url = request.url - else: - self.effective_url = effective_url - self._error_is_response_code = False - if error is None: - if self.code < 200 or self.code >= 300: - self._error_is_response_code = True - self.error = HTTPError(self.code, message=self.reason, response=self) - else: - self.error = None - else: - self.error = error - self.start_time = start_time - self.request_time = request_time - self.time_info = time_info or {} - - @property - def body(self) -> bytes: - if self.buffer is None: - return b"" - elif self._body is None: - self._body = self.buffer.getvalue() - - return self._body - - def rethrow(self) -> None: - """If there was an error on the request, raise an `HTTPError`.""" - if self.error: - raise self.error - - def __repr__(self) -> str: - args = ",".join("%s=%r" % i for i in sorted(self.__dict__.items())) - return "%s(%s)" % (self.__class__.__name__, args) - - -class HTTPClientError(Exception): - """Exception thrown for an unsuccessful HTTP request. - - Attributes: - - * ``code`` - HTTP error integer error code, e.g. 404. Error code 599 is - used when no HTTP response was received, e.g. for a timeout. - - * ``response`` - `HTTPResponse` object, if any. - - Note that if ``follow_redirects`` is False, redirects become HTTPErrors, - and you can look at ``error.response.headers['Location']`` to see the - destination of the redirect. - - .. versionchanged:: 5.1 - - Renamed from ``HTTPError`` to ``HTTPClientError`` to avoid collisions with - `tornado.web.HTTPError`. The name ``tornado.httpclient.HTTPError`` remains - as an alias. - """ - - def __init__( - self, - code: int, - message: Optional[str] = None, - response: Optional[HTTPResponse] = None, - ) -> None: - self.code = code - self.message = message or httputil.responses.get(code, "Unknown") - self.response = response - super().__init__(code, message, response) - - def __str__(self) -> str: - return "HTTP %d: %s" % (self.code, self.message) - - # There is a cyclic reference between self and self.response, - # which breaks the default __repr__ implementation. - # (especially on pypy, which doesn't have the same recursion - # detection as cpython). - __repr__ = __str__ - - -HTTPError = HTTPClientError - - -class _RequestProxy(object): - """Combines an object with a dictionary of defaults. - - Used internally by AsyncHTTPClient implementations. - """ - - def __init__( - self, request: HTTPRequest, defaults: Optional[Dict[str, Any]] - ) -> None: - self.request = request - self.defaults = defaults - - def __getattr__(self, name: str) -> Any: - request_attr = getattr(self.request, name) - if request_attr is not None: - return request_attr - elif self.defaults is not None: - return self.defaults.get(name, None) - else: - return None - - -def main() -> None: - from tornado.options import define, options, parse_command_line - - define("print_headers", type=bool, default=False) - define("print_body", type=bool, default=True) - define("follow_redirects", type=bool, default=True) - define("validate_cert", type=bool, default=True) - define("proxy_host", type=str) - define("proxy_port", type=int) - args = parse_command_line() - client = HTTPClient() - for arg in args: - try: - response = client.fetch( - arg, - follow_redirects=options.follow_redirects, - validate_cert=options.validate_cert, - proxy_host=options.proxy_host, - proxy_port=options.proxy_port, - ) - except HTTPError as e: - if e.response is not None: - response = e.response - else: - raise - if options.print_headers: - print(response.headers) - if options.print_body: - print(native_str(response.body)) - client.close() - - -if __name__ == "__main__": - main() diff --git a/telegramer/include/tornado/httpserver.py b/telegramer/include/tornado/httpserver.py deleted file mode 100644 index cd4a468..0000000 --- a/telegramer/include/tornado/httpserver.py +++ /dev/null @@ -1,398 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A non-blocking, single-threaded HTTP server. - -Typical applications have little direct interaction with the `HTTPServer` -class except to start a server at the beginning of the process -(and even that is often done indirectly via `tornado.web.Application.listen`). - -.. versionchanged:: 4.0 - - The ``HTTPRequest`` class that used to live in this module has been moved - to `tornado.httputil.HTTPServerRequest`. The old name remains as an alias. -""" - -import socket -import ssl - -from tornado.escape import native_str -from tornado.http1connection import HTTP1ServerConnection, HTTP1ConnectionParameters -from tornado import httputil -from tornado import iostream -from tornado import netutil -from tornado.tcpserver import TCPServer -from tornado.util import Configurable - -import typing -from typing import Union, Any, Dict, Callable, List, Type, Tuple, Optional, Awaitable - -if typing.TYPE_CHECKING: - from typing import Set # noqa: F401 - - -class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate): - r"""A non-blocking, single-threaded HTTP server. - - A server is defined by a subclass of `.HTTPServerConnectionDelegate`, - or, for backwards compatibility, a callback that takes an - `.HTTPServerRequest` as an argument. The delegate is usually a - `tornado.web.Application`. - - `HTTPServer` supports keep-alive connections by default - (automatically for HTTP/1.1, or for HTTP/1.0 when the client - requests ``Connection: keep-alive``). - - If ``xheaders`` is ``True``, we support the - ``X-Real-Ip``/``X-Forwarded-For`` and - ``X-Scheme``/``X-Forwarded-Proto`` headers, which override the - remote IP and URI scheme/protocol for all requests. These headers - are useful when running Tornado behind a reverse proxy or load - balancer. The ``protocol`` argument can also be set to ``https`` - if Tornado is run behind an SSL-decoding proxy that does not set one of - the supported ``xheaders``. - - By default, when parsing the ``X-Forwarded-For`` header, Tornado will - select the last (i.e., the closest) address on the list of hosts as the - remote host IP address. To select the next server in the chain, a list of - trusted downstream hosts may be passed as the ``trusted_downstream`` - argument. These hosts will be skipped when parsing the ``X-Forwarded-For`` - header. - - To make this server serve SSL traffic, send the ``ssl_options`` keyword - argument with an `ssl.SSLContext` object. For compatibility with older - versions of Python ``ssl_options`` may also be a dictionary of keyword - arguments for the `ssl.wrap_socket` method.:: - - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"), - os.path.join(data_dir, "mydomain.key")) - HTTPServer(application, ssl_options=ssl_ctx) - - `HTTPServer` initialization follows one of three patterns (the - initialization methods are defined on `tornado.tcpserver.TCPServer`): - - 1. `~tornado.tcpserver.TCPServer.listen`: simple single-process:: - - server = HTTPServer(app) - server.listen(8888) - IOLoop.current().start() - - In many cases, `tornado.web.Application.listen` can be used to avoid - the need to explicitly create the `HTTPServer`. - - 2. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`: - simple multi-process:: - - server = HTTPServer(app) - server.bind(8888) - server.start(0) # Forks multiple sub-processes - IOLoop.current().start() - - When using this interface, an `.IOLoop` must *not* be passed - to the `HTTPServer` constructor. `~.TCPServer.start` will always start - the server on the default singleton `.IOLoop`. - - 3. `~tornado.tcpserver.TCPServer.add_sockets`: advanced multi-process:: - - sockets = tornado.netutil.bind_sockets(8888) - tornado.process.fork_processes(0) - server = HTTPServer(app) - server.add_sockets(sockets) - IOLoop.current().start() - - The `~.TCPServer.add_sockets` interface is more complicated, - but it can be used with `tornado.process.fork_processes` to - give you more flexibility in when the fork happens. - `~.TCPServer.add_sockets` can also be used in single-process - servers if you want to create your listening sockets in some - way other than `tornado.netutil.bind_sockets`. - - .. versionchanged:: 4.0 - Added ``decompress_request``, ``chunk_size``, ``max_header_size``, - ``idle_connection_timeout``, ``body_timeout``, ``max_body_size`` - arguments. Added support for `.HTTPServerConnectionDelegate` - instances as ``request_callback``. - - .. versionchanged:: 4.1 - `.HTTPServerConnectionDelegate.start_request` is now called with - two arguments ``(server_conn, request_conn)`` (in accordance with the - documentation) instead of one ``(request_conn)``. - - .. versionchanged:: 4.2 - `HTTPServer` is now a subclass of `tornado.util.Configurable`. - - .. versionchanged:: 4.5 - Added the ``trusted_downstream`` argument. - - .. versionchanged:: 5.0 - The ``io_loop`` argument has been removed. - """ - - def __init__(self, *args: Any, **kwargs: Any) -> None: - # Ignore args to __init__; real initialization belongs in - # initialize since we're Configurable. (there's something - # weird in initialization order between this class, - # Configurable, and TCPServer so we can't leave __init__ out - # completely) - pass - - def initialize( - self, - request_callback: Union[ - httputil.HTTPServerConnectionDelegate, - Callable[[httputil.HTTPServerRequest], None], - ], - no_keep_alive: bool = False, - xheaders: bool = False, - ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None, - protocol: Optional[str] = None, - decompress_request: bool = False, - chunk_size: Optional[int] = None, - max_header_size: Optional[int] = None, - idle_connection_timeout: Optional[float] = None, - body_timeout: Optional[float] = None, - max_body_size: Optional[int] = None, - max_buffer_size: Optional[int] = None, - trusted_downstream: Optional[List[str]] = None, - ) -> None: - # This method's signature is not extracted with autodoc - # because we want its arguments to appear on the class - # constructor. When changing this signature, also update the - # copy in httpserver.rst. - self.request_callback = request_callback - self.xheaders = xheaders - self.protocol = protocol - self.conn_params = HTTP1ConnectionParameters( - decompress=decompress_request, - chunk_size=chunk_size, - max_header_size=max_header_size, - header_timeout=idle_connection_timeout or 3600, - max_body_size=max_body_size, - body_timeout=body_timeout, - no_keep_alive=no_keep_alive, - ) - TCPServer.__init__( - self, - ssl_options=ssl_options, - max_buffer_size=max_buffer_size, - read_chunk_size=chunk_size, - ) - self._connections = set() # type: Set[HTTP1ServerConnection] - self.trusted_downstream = trusted_downstream - - @classmethod - def configurable_base(cls) -> Type[Configurable]: - return HTTPServer - - @classmethod - def configurable_default(cls) -> Type[Configurable]: - return HTTPServer - - async def close_all_connections(self) -> None: - """Close all open connections and asynchronously wait for them to finish. - - This method is used in combination with `~.TCPServer.stop` to - support clean shutdowns (especially for unittests). Typical - usage would call ``stop()`` first to stop accepting new - connections, then ``await close_all_connections()`` to wait for - existing connections to finish. - - This method does not currently close open websocket connections. - - Note that this method is a coroutine and must be called with ``await``. - - """ - while self._connections: - # Peek at an arbitrary element of the set - conn = next(iter(self._connections)) - await conn.close() - - def handle_stream(self, stream: iostream.IOStream, address: Tuple) -> None: - context = _HTTPRequestContext( - stream, address, self.protocol, self.trusted_downstream - ) - conn = HTTP1ServerConnection(stream, self.conn_params, context) - self._connections.add(conn) - conn.start_serving(self) - - def start_request( - self, server_conn: object, request_conn: httputil.HTTPConnection - ) -> httputil.HTTPMessageDelegate: - if isinstance(self.request_callback, httputil.HTTPServerConnectionDelegate): - delegate = self.request_callback.start_request(server_conn, request_conn) - else: - delegate = _CallableAdapter(self.request_callback, request_conn) - - if self.xheaders: - delegate = _ProxyAdapter(delegate, request_conn) - - return delegate - - def on_close(self, server_conn: object) -> None: - self._connections.remove(typing.cast(HTTP1ServerConnection, server_conn)) - - -class _CallableAdapter(httputil.HTTPMessageDelegate): - def __init__( - self, - request_callback: Callable[[httputil.HTTPServerRequest], None], - request_conn: httputil.HTTPConnection, - ) -> None: - self.connection = request_conn - self.request_callback = request_callback - self.request = None # type: Optional[httputil.HTTPServerRequest] - self.delegate = None - self._chunks = [] # type: List[bytes] - - def headers_received( - self, - start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], - headers: httputil.HTTPHeaders, - ) -> Optional[Awaitable[None]]: - self.request = httputil.HTTPServerRequest( - connection=self.connection, - start_line=typing.cast(httputil.RequestStartLine, start_line), - headers=headers, - ) - return None - - def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: - self._chunks.append(chunk) - return None - - def finish(self) -> None: - assert self.request is not None - self.request.body = b"".join(self._chunks) - self.request._parse_body() - self.request_callback(self.request) - - def on_connection_close(self) -> None: - del self._chunks - - -class _HTTPRequestContext(object): - def __init__( - self, - stream: iostream.IOStream, - address: Tuple, - protocol: Optional[str], - trusted_downstream: Optional[List[str]] = None, - ) -> None: - self.address = address - # Save the socket's address family now so we know how to - # interpret self.address even after the stream is closed - # and its socket attribute replaced with None. - if stream.socket is not None: - self.address_family = stream.socket.family - else: - self.address_family = None - # In HTTPServerRequest we want an IP, not a full socket address. - if ( - self.address_family in (socket.AF_INET, socket.AF_INET6) - and address is not None - ): - self.remote_ip = address[0] - else: - # Unix (or other) socket; fake the remote address. - self.remote_ip = "0.0.0.0" - if protocol: - self.protocol = protocol - elif isinstance(stream, iostream.SSLIOStream): - self.protocol = "https" - else: - self.protocol = "http" - self._orig_remote_ip = self.remote_ip - self._orig_protocol = self.protocol - self.trusted_downstream = set(trusted_downstream or []) - - def __str__(self) -> str: - if self.address_family in (socket.AF_INET, socket.AF_INET6): - return self.remote_ip - elif isinstance(self.address, bytes): - # Python 3 with the -bb option warns about str(bytes), - # so convert it explicitly. - # Unix socket addresses are str on mac but bytes on linux. - return native_str(self.address) - else: - return str(self.address) - - def _apply_xheaders(self, headers: httputil.HTTPHeaders) -> None: - """Rewrite the ``remote_ip`` and ``protocol`` fields.""" - # Squid uses X-Forwarded-For, others use X-Real-Ip - ip = headers.get("X-Forwarded-For", self.remote_ip) - # Skip trusted downstream hosts in X-Forwarded-For list - for ip in (cand.strip() for cand in reversed(ip.split(","))): - if ip not in self.trusted_downstream: - break - ip = headers.get("X-Real-Ip", ip) - if netutil.is_valid_ip(ip): - self.remote_ip = ip - # AWS uses X-Forwarded-Proto - proto_header = headers.get( - "X-Scheme", headers.get("X-Forwarded-Proto", self.protocol) - ) - if proto_header: - # use only the last proto entry if there is more than one - # TODO: support trusting multiple layers of proxied protocol - proto_header = proto_header.split(",")[-1].strip() - if proto_header in ("http", "https"): - self.protocol = proto_header - - def _unapply_xheaders(self) -> None: - """Undo changes from `_apply_xheaders`. - - Xheaders are per-request so they should not leak to the next - request on the same connection. - """ - self.remote_ip = self._orig_remote_ip - self.protocol = self._orig_protocol - - -class _ProxyAdapter(httputil.HTTPMessageDelegate): - def __init__( - self, - delegate: httputil.HTTPMessageDelegate, - request_conn: httputil.HTTPConnection, - ) -> None: - self.connection = request_conn - self.delegate = delegate - - def headers_received( - self, - start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], - headers: httputil.HTTPHeaders, - ) -> Optional[Awaitable[None]]: - # TODO: either make context an official part of the - # HTTPConnection interface or figure out some other way to do this. - self.connection.context._apply_xheaders(headers) # type: ignore - return self.delegate.headers_received(start_line, headers) - - def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: - return self.delegate.data_received(chunk) - - def finish(self) -> None: - self.delegate.finish() - self._cleanup() - - def on_connection_close(self) -> None: - self.delegate.on_connection_close() - self._cleanup() - - def _cleanup(self) -> None: - self.connection.context._unapply_xheaders() # type: ignore - - -HTTPRequest = httputil.HTTPServerRequest diff --git a/telegramer/include/tornado/httputil.py b/telegramer/include/tornado/httputil.py deleted file mode 100644 index bd32cd0..0000000 --- a/telegramer/include/tornado/httputil.py +++ /dev/null @@ -1,1133 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""HTTP utility code shared by clients and servers. - -This module also defines the `HTTPServerRequest` class which is exposed -via `tornado.web.RequestHandler.request`. -""" - -import calendar -import collections -import copy -import datetime -import email.utils -from functools import lru_cache -from http.client import responses -import http.cookies -import re -from ssl import SSLError -import time -import unicodedata -from urllib.parse import urlencode, urlparse, urlunparse, parse_qsl - -from tornado.escape import native_str, parse_qs_bytes, utf8 -from tornado.log import gen_log -from tornado.util import ObjectDict, unicode_type - - -# responses is unused in this file, but we re-export it to other files. -# Reference it so pyflakes doesn't complain. -responses - -import typing -from typing import ( - Tuple, - Iterable, - List, - Mapping, - Iterator, - Dict, - Union, - Optional, - Awaitable, - Generator, - AnyStr, -) - -if typing.TYPE_CHECKING: - from typing import Deque # noqa: F401 - from asyncio import Future # noqa: F401 - import unittest # noqa: F401 - - -@lru_cache(1000) -def _normalize_header(name: str) -> str: - """Map a header name to Http-Header-Case. - - >>> _normalize_header("coNtent-TYPE") - 'Content-Type' - """ - return "-".join([w.capitalize() for w in name.split("-")]) - - -class HTTPHeaders(collections.abc.MutableMapping): - """A dictionary that maintains ``Http-Header-Case`` for all keys. - - Supports multiple values per key via a pair of new methods, - `add()` and `get_list()`. The regular dictionary interface - returns a single value per key, with multiple values joined by a - comma. - - >>> h = HTTPHeaders({"content-type": "text/html"}) - >>> list(h.keys()) - ['Content-Type'] - >>> h["Content-Type"] - 'text/html' - - >>> h.add("Set-Cookie", "A=B") - >>> h.add("Set-Cookie", "C=D") - >>> h["set-cookie"] - 'A=B,C=D' - >>> h.get_list("set-cookie") - ['A=B', 'C=D'] - - >>> for (k,v) in sorted(h.get_all()): - ... print('%s: %s' % (k,v)) - ... - Content-Type: text/html - Set-Cookie: A=B - Set-Cookie: C=D - """ - - @typing.overload - def __init__(self, __arg: Mapping[str, List[str]]) -> None: - pass - - @typing.overload # noqa: F811 - def __init__(self, __arg: Mapping[str, str]) -> None: - pass - - @typing.overload # noqa: F811 - def __init__(self, *args: Tuple[str, str]) -> None: - pass - - @typing.overload # noqa: F811 - def __init__(self, **kwargs: str) -> None: - pass - - def __init__(self, *args: typing.Any, **kwargs: str) -> None: # noqa: F811 - self._dict = {} # type: typing.Dict[str, str] - self._as_list = {} # type: typing.Dict[str, typing.List[str]] - self._last_key = None # type: Optional[str] - if len(args) == 1 and len(kwargs) == 0 and isinstance(args[0], HTTPHeaders): - # Copy constructor - for k, v in args[0].get_all(): - self.add(k, v) - else: - # Dict-style initialization - self.update(*args, **kwargs) - - # new public methods - - def add(self, name: str, value: str) -> None: - """Adds a new value for the given key.""" - norm_name = _normalize_header(name) - self._last_key = norm_name - if norm_name in self: - self._dict[norm_name] = ( - native_str(self[norm_name]) + "," + native_str(value) - ) - self._as_list[norm_name].append(value) - else: - self[norm_name] = value - - def get_list(self, name: str) -> List[str]: - """Returns all values for the given header as a list.""" - norm_name = _normalize_header(name) - return self._as_list.get(norm_name, []) - - def get_all(self) -> Iterable[Tuple[str, str]]: - """Returns an iterable of all (name, value) pairs. - - If a header has multiple values, multiple pairs will be - returned with the same name. - """ - for name, values in self._as_list.items(): - for value in values: - yield (name, value) - - def parse_line(self, line: str) -> None: - """Updates the dictionary with a single header line. - - >>> h = HTTPHeaders() - >>> h.parse_line("Content-Type: text/html") - >>> h.get('content-type') - 'text/html' - """ - if line[0].isspace(): - # continuation of a multi-line header - if self._last_key is None: - raise HTTPInputError("first header line cannot start with whitespace") - new_part = " " + line.lstrip() - self._as_list[self._last_key][-1] += new_part - self._dict[self._last_key] += new_part - else: - try: - name, value = line.split(":", 1) - except ValueError: - raise HTTPInputError("no colon in header line") - self.add(name, value.strip()) - - @classmethod - def parse(cls, headers: str) -> "HTTPHeaders": - """Returns a dictionary from HTTP header text. - - >>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n") - >>> sorted(h.items()) - [('Content-Length', '42'), ('Content-Type', 'text/html')] - - .. versionchanged:: 5.1 - - Raises `HTTPInputError` on malformed headers instead of a - mix of `KeyError`, and `ValueError`. - - """ - h = cls() - # RFC 7230 section 3.5: a recipient MAY recognize a single LF as a line - # terminator and ignore any preceding CR. - for line in headers.split("\n"): - if line.endswith("\r"): - line = line[:-1] - if line: - h.parse_line(line) - return h - - # MutableMapping abstract method implementations. - - def __setitem__(self, name: str, value: str) -> None: - norm_name = _normalize_header(name) - self._dict[norm_name] = value - self._as_list[norm_name] = [value] - - def __getitem__(self, name: str) -> str: - return self._dict[_normalize_header(name)] - - def __delitem__(self, name: str) -> None: - norm_name = _normalize_header(name) - del self._dict[norm_name] - del self._as_list[norm_name] - - def __len__(self) -> int: - return len(self._dict) - - def __iter__(self) -> Iterator[typing.Any]: - return iter(self._dict) - - def copy(self) -> "HTTPHeaders": - # defined in dict but not in MutableMapping. - return HTTPHeaders(self) - - # Use our overridden copy method for the copy.copy module. - # This makes shallow copies one level deeper, but preserves - # the appearance that HTTPHeaders is a single container. - __copy__ = copy - - def __str__(self) -> str: - lines = [] - for name, value in self.get_all(): - lines.append("%s: %s\n" % (name, value)) - return "".join(lines) - - __unicode__ = __str__ - - -class HTTPServerRequest(object): - """A single HTTP request. - - All attributes are type `str` unless otherwise noted. - - .. attribute:: method - - HTTP request method, e.g. "GET" or "POST" - - .. attribute:: uri - - The requested uri. - - .. attribute:: path - - The path portion of `uri` - - .. attribute:: query - - The query portion of `uri` - - .. attribute:: version - - HTTP version specified in request, e.g. "HTTP/1.1" - - .. attribute:: headers - - `.HTTPHeaders` dictionary-like object for request headers. Acts like - a case-insensitive dictionary with additional methods for repeated - headers. - - .. attribute:: body - - Request body, if present, as a byte string. - - .. attribute:: remote_ip - - Client's IP address as a string. If ``HTTPServer.xheaders`` is set, - will pass along the real IP address provided by a load balancer - in the ``X-Real-Ip`` or ``X-Forwarded-For`` header. - - .. versionchanged:: 3.1 - The list format of ``X-Forwarded-For`` is now supported. - - .. attribute:: protocol - - The protocol used, either "http" or "https". If ``HTTPServer.xheaders`` - is set, will pass along the protocol used by a load balancer if - reported via an ``X-Scheme`` header. - - .. attribute:: host - - The requested hostname, usually taken from the ``Host`` header. - - .. attribute:: arguments - - GET/POST arguments are available in the arguments property, which - maps arguments names to lists of values (to support multiple values - for individual names). Names are of type `str`, while arguments - are byte strings. Note that this is different from - `.RequestHandler.get_argument`, which returns argument values as - unicode strings. - - .. attribute:: query_arguments - - Same format as ``arguments``, but contains only arguments extracted - from the query string. - - .. versionadded:: 3.2 - - .. attribute:: body_arguments - - Same format as ``arguments``, but contains only arguments extracted - from the request body. - - .. versionadded:: 3.2 - - .. attribute:: files - - File uploads are available in the files property, which maps file - names to lists of `.HTTPFile`. - - .. attribute:: connection - - An HTTP request is attached to a single HTTP connection, which can - be accessed through the "connection" attribute. Since connections - are typically kept open in HTTP/1.1, multiple requests can be handled - sequentially on a single connection. - - .. versionchanged:: 4.0 - Moved from ``tornado.httpserver.HTTPRequest``. - """ - - path = None # type: str - query = None # type: str - - # HACK: Used for stream_request_body - _body_future = None # type: Future[None] - - def __init__( - self, - method: Optional[str] = None, - uri: Optional[str] = None, - version: str = "HTTP/1.0", - headers: Optional[HTTPHeaders] = None, - body: Optional[bytes] = None, - host: Optional[str] = None, - files: Optional[Dict[str, List["HTTPFile"]]] = None, - connection: Optional["HTTPConnection"] = None, - start_line: Optional["RequestStartLine"] = None, - server_connection: Optional[object] = None, - ) -> None: - if start_line is not None: - method, uri, version = start_line - self.method = method - self.uri = uri - self.version = version - self.headers = headers or HTTPHeaders() - self.body = body or b"" - - # set remote IP and protocol - context = getattr(connection, "context", None) - self.remote_ip = getattr(context, "remote_ip", None) - self.protocol = getattr(context, "protocol", "http") - - self.host = host or self.headers.get("Host") or "127.0.0.1" - self.host_name = split_host_and_port(self.host.lower())[0] - self.files = files or {} - self.connection = connection - self.server_connection = server_connection - self._start_time = time.time() - self._finish_time = None - - if uri is not None: - self.path, sep, self.query = uri.partition("?") - self.arguments = parse_qs_bytes(self.query, keep_blank_values=True) - self.query_arguments = copy.deepcopy(self.arguments) - self.body_arguments = {} # type: Dict[str, List[bytes]] - - @property - def cookies(self) -> Dict[str, http.cookies.Morsel]: - """A dictionary of ``http.cookies.Morsel`` objects.""" - if not hasattr(self, "_cookies"): - self._cookies = ( - http.cookies.SimpleCookie() - ) # type: http.cookies.SimpleCookie - if "Cookie" in self.headers: - try: - parsed = parse_cookie(self.headers["Cookie"]) - except Exception: - pass - else: - for k, v in parsed.items(): - try: - self._cookies[k] = v - except Exception: - # SimpleCookie imposes some restrictions on keys; - # parse_cookie does not. Discard any cookies - # with disallowed keys. - pass - return self._cookies - - def full_url(self) -> str: - """Reconstructs the full URL for this request.""" - return self.protocol + "://" + self.host + self.uri - - def request_time(self) -> float: - """Returns the amount of time it took for this request to execute.""" - if self._finish_time is None: - return time.time() - self._start_time - else: - return self._finish_time - self._start_time - - def get_ssl_certificate( - self, binary_form: bool = False - ) -> Union[None, Dict, bytes]: - """Returns the client's SSL certificate, if any. - - To use client certificates, the HTTPServer's - `ssl.SSLContext.verify_mode` field must be set, e.g.:: - - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_ctx.load_cert_chain("foo.crt", "foo.key") - ssl_ctx.load_verify_locations("cacerts.pem") - ssl_ctx.verify_mode = ssl.CERT_REQUIRED - server = HTTPServer(app, ssl_options=ssl_ctx) - - By default, the return value is a dictionary (or None, if no - client certificate is present). If ``binary_form`` is true, a - DER-encoded form of the certificate is returned instead. See - SSLSocket.getpeercert() in the standard library for more - details. - http://docs.python.org/library/ssl.html#sslsocket-objects - """ - try: - if self.connection is None: - return None - # TODO: add a method to HTTPConnection for this so it can work with HTTP/2 - return self.connection.stream.socket.getpeercert( # type: ignore - binary_form=binary_form - ) - except SSLError: - return None - - def _parse_body(self) -> None: - parse_body_arguments( - self.headers.get("Content-Type", ""), - self.body, - self.body_arguments, - self.files, - self.headers, - ) - - for k, v in self.body_arguments.items(): - self.arguments.setdefault(k, []).extend(v) - - def __repr__(self) -> str: - attrs = ("protocol", "host", "method", "uri", "version", "remote_ip") - args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs]) - return "%s(%s)" % (self.__class__.__name__, args) - - -class HTTPInputError(Exception): - """Exception class for malformed HTTP requests or responses - from remote sources. - - .. versionadded:: 4.0 - """ - - pass - - -class HTTPOutputError(Exception): - """Exception class for errors in HTTP output. - - .. versionadded:: 4.0 - """ - - pass - - -class HTTPServerConnectionDelegate(object): - """Implement this interface to handle requests from `.HTTPServer`. - - .. versionadded:: 4.0 - """ - - def start_request( - self, server_conn: object, request_conn: "HTTPConnection" - ) -> "HTTPMessageDelegate": - """This method is called by the server when a new request has started. - - :arg server_conn: is an opaque object representing the long-lived - (e.g. tcp-level) connection. - :arg request_conn: is a `.HTTPConnection` object for a single - request/response exchange. - - This method should return a `.HTTPMessageDelegate`. - """ - raise NotImplementedError() - - def on_close(self, server_conn: object) -> None: - """This method is called when a connection has been closed. - - :arg server_conn: is a server connection that has previously been - passed to ``start_request``. - """ - pass - - -class HTTPMessageDelegate(object): - """Implement this interface to handle an HTTP request or response. - - .. versionadded:: 4.0 - """ - - # TODO: genericize this class to avoid exposing the Union. - def headers_received( - self, - start_line: Union["RequestStartLine", "ResponseStartLine"], - headers: HTTPHeaders, - ) -> Optional[Awaitable[None]]: - """Called when the HTTP headers have been received and parsed. - - :arg start_line: a `.RequestStartLine` or `.ResponseStartLine` - depending on whether this is a client or server message. - :arg headers: a `.HTTPHeaders` instance. - - Some `.HTTPConnection` methods can only be called during - ``headers_received``. - - May return a `.Future`; if it does the body will not be read - until it is done. - """ - pass - - def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: - """Called when a chunk of data has been received. - - May return a `.Future` for flow control. - """ - pass - - def finish(self) -> None: - """Called after the last chunk of data has been received.""" - pass - - def on_connection_close(self) -> None: - """Called if the connection is closed without finishing the request. - - If ``headers_received`` is called, either ``finish`` or - ``on_connection_close`` will be called, but not both. - """ - pass - - -class HTTPConnection(object): - """Applications use this interface to write their responses. - - .. versionadded:: 4.0 - """ - - def write_headers( - self, - start_line: Union["RequestStartLine", "ResponseStartLine"], - headers: HTTPHeaders, - chunk: Optional[bytes] = None, - ) -> "Future[None]": - """Write an HTTP header block. - - :arg start_line: a `.RequestStartLine` or `.ResponseStartLine`. - :arg headers: a `.HTTPHeaders` instance. - :arg chunk: the first (optional) chunk of data. This is an optimization - so that small responses can be written in the same call as their - headers. - - The ``version`` field of ``start_line`` is ignored. - - Returns a future for flow control. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. - """ - raise NotImplementedError() - - def write(self, chunk: bytes) -> "Future[None]": - """Writes a chunk of body data. - - Returns a future for flow control. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. - """ - raise NotImplementedError() - - def finish(self) -> None: - """Indicates that the last body data has been written. - """ - raise NotImplementedError() - - -def url_concat( - url: str, - args: Union[ - None, Dict[str, str], List[Tuple[str, str]], Tuple[Tuple[str, str], ...] - ], -) -> str: - """Concatenate url and arguments regardless of whether - url has existing query parameters. - - ``args`` may be either a dictionary or a list of key-value pairs - (the latter allows for multiple values with the same key. - - >>> url_concat("http://example.com/foo", dict(c="d")) - 'http://example.com/foo?c=d' - >>> url_concat("http://example.com/foo?a=b", dict(c="d")) - 'http://example.com/foo?a=b&c=d' - >>> url_concat("http://example.com/foo?a=b", [("c", "d"), ("c", "d2")]) - 'http://example.com/foo?a=b&c=d&c=d2' - """ - if args is None: - return url - parsed_url = urlparse(url) - if isinstance(args, dict): - parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True) - parsed_query.extend(args.items()) - elif isinstance(args, list) or isinstance(args, tuple): - parsed_query = parse_qsl(parsed_url.query, keep_blank_values=True) - parsed_query.extend(args) - else: - err = "'args' parameter should be dict, list or tuple. Not {0}".format( - type(args) - ) - raise TypeError(err) - final_query = urlencode(parsed_query) - url = urlunparse( - ( - parsed_url[0], - parsed_url[1], - parsed_url[2], - parsed_url[3], - final_query, - parsed_url[5], - ) - ) - return url - - -class HTTPFile(ObjectDict): - """Represents a file uploaded via a form. - - For backwards compatibility, its instance attributes are also - accessible as dictionary keys. - - * ``filename`` - * ``body`` - * ``content_type`` - """ - - pass - - -def _parse_request_range( - range_header: str, -) -> Optional[Tuple[Optional[int], Optional[int]]]: - """Parses a Range header. - - Returns either ``None`` or tuple ``(start, end)``. - Note that while the HTTP headers use inclusive byte positions, - this method returns indexes suitable for use in slices. - - >>> start, end = _parse_request_range("bytes=1-2") - >>> start, end - (1, 3) - >>> [0, 1, 2, 3, 4][start:end] - [1, 2] - >>> _parse_request_range("bytes=6-") - (6, None) - >>> _parse_request_range("bytes=-6") - (-6, None) - >>> _parse_request_range("bytes=-0") - (None, 0) - >>> _parse_request_range("bytes=") - (None, None) - >>> _parse_request_range("foo=42") - >>> _parse_request_range("bytes=1-2,6-10") - - Note: only supports one range (ex, ``bytes=1-2,6-10`` is not allowed). - - See [0] for the details of the range header. - - [0]: http://greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#byte.ranges - """ - unit, _, value = range_header.partition("=") - unit, value = unit.strip(), value.strip() - if unit != "bytes": - return None - start_b, _, end_b = value.partition("-") - try: - start = _int_or_none(start_b) - end = _int_or_none(end_b) - except ValueError: - return None - if end is not None: - if start is None: - if end != 0: - start = -end - end = None - else: - end += 1 - return (start, end) - - -def _get_content_range(start: Optional[int], end: Optional[int], total: int) -> str: - """Returns a suitable Content-Range header: - - >>> print(_get_content_range(None, 1, 4)) - bytes 0-0/4 - >>> print(_get_content_range(1, 3, 4)) - bytes 1-2/4 - >>> print(_get_content_range(None, None, 4)) - bytes 0-3/4 - """ - start = start or 0 - end = (end or total) - 1 - return "bytes %s-%s/%s" % (start, end, total) - - -def _int_or_none(val: str) -> Optional[int]: - val = val.strip() - if val == "": - return None - return int(val) - - -def parse_body_arguments( - content_type: str, - body: bytes, - arguments: Dict[str, List[bytes]], - files: Dict[str, List[HTTPFile]], - headers: Optional[HTTPHeaders] = None, -) -> None: - """Parses a form request body. - - Supports ``application/x-www-form-urlencoded`` and - ``multipart/form-data``. The ``content_type`` parameter should be - a string and ``body`` should be a byte string. The ``arguments`` - and ``files`` parameters are dictionaries that will be updated - with the parsed contents. - """ - if content_type.startswith("application/x-www-form-urlencoded"): - if headers and "Content-Encoding" in headers: - gen_log.warning( - "Unsupported Content-Encoding: %s", headers["Content-Encoding"] - ) - return - try: - # real charset decoding will happen in RequestHandler.decode_argument() - uri_arguments = parse_qs_bytes(body, keep_blank_values=True) - except Exception as e: - gen_log.warning("Invalid x-www-form-urlencoded body: %s", e) - uri_arguments = {} - for name, values in uri_arguments.items(): - if values: - arguments.setdefault(name, []).extend(values) - elif content_type.startswith("multipart/form-data"): - if headers and "Content-Encoding" in headers: - gen_log.warning( - "Unsupported Content-Encoding: %s", headers["Content-Encoding"] - ) - return - try: - fields = content_type.split(";") - for field in fields: - k, sep, v = field.strip().partition("=") - if k == "boundary" and v: - parse_multipart_form_data(utf8(v), body, arguments, files) - break - else: - raise ValueError("multipart boundary not found") - except Exception as e: - gen_log.warning("Invalid multipart/form-data: %s", e) - - -def parse_multipart_form_data( - boundary: bytes, - data: bytes, - arguments: Dict[str, List[bytes]], - files: Dict[str, List[HTTPFile]], -) -> None: - """Parses a ``multipart/form-data`` body. - - The ``boundary`` and ``data`` parameters are both byte strings. - The dictionaries given in the arguments and files parameters - will be updated with the contents of the body. - - .. versionchanged:: 5.1 - - Now recognizes non-ASCII filenames in RFC 2231/5987 - (``filename*=``) format. - """ - # The standard allows for the boundary to be quoted in the header, - # although it's rare (it happens at least for google app engine - # xmpp). I think we're also supposed to handle backslash-escapes - # here but I'll save that until we see a client that uses them - # in the wild. - if boundary.startswith(b'"') and boundary.endswith(b'"'): - boundary = boundary[1:-1] - final_boundary_index = data.rfind(b"--" + boundary + b"--") - if final_boundary_index == -1: - gen_log.warning("Invalid multipart/form-data: no final boundary") - return - parts = data[:final_boundary_index].split(b"--" + boundary + b"\r\n") - for part in parts: - if not part: - continue - eoh = part.find(b"\r\n\r\n") - if eoh == -1: - gen_log.warning("multipart/form-data missing headers") - continue - headers = HTTPHeaders.parse(part[:eoh].decode("utf-8")) - disp_header = headers.get("Content-Disposition", "") - disposition, disp_params = _parse_header(disp_header) - if disposition != "form-data" or not part.endswith(b"\r\n"): - gen_log.warning("Invalid multipart/form-data") - continue - value = part[eoh + 4 : -2] - if not disp_params.get("name"): - gen_log.warning("multipart/form-data value missing name") - continue - name = disp_params["name"] - if disp_params.get("filename"): - ctype = headers.get("Content-Type", "application/unknown") - files.setdefault(name, []).append( - HTTPFile( - filename=disp_params["filename"], body=value, content_type=ctype - ) - ) - else: - arguments.setdefault(name, []).append(value) - - -def format_timestamp( - ts: Union[int, float, tuple, time.struct_time, datetime.datetime] -) -> str: - """Formats a timestamp in the format used by HTTP. - - The argument may be a numeric timestamp as returned by `time.time`, - a time tuple as returned by `time.gmtime`, or a `datetime.datetime` - object. - - >>> format_timestamp(1359312200) - 'Sun, 27 Jan 2013 18:43:20 GMT' - """ - if isinstance(ts, (int, float)): - time_num = ts - elif isinstance(ts, (tuple, time.struct_time)): - time_num = calendar.timegm(ts) - elif isinstance(ts, datetime.datetime): - time_num = calendar.timegm(ts.utctimetuple()) - else: - raise TypeError("unknown timestamp type: %r" % ts) - return email.utils.formatdate(time_num, usegmt=True) - - -RequestStartLine = collections.namedtuple( - "RequestStartLine", ["method", "path", "version"] -) - - -_http_version_re = re.compile(r"^HTTP/1\.[0-9]$") - - -def parse_request_start_line(line: str) -> RequestStartLine: - """Returns a (method, path, version) tuple for an HTTP 1.x request line. - - The response is a `collections.namedtuple`. - - >>> parse_request_start_line("GET /foo HTTP/1.1") - RequestStartLine(method='GET', path='/foo', version='HTTP/1.1') - """ - try: - method, path, version = line.split(" ") - except ValueError: - # https://tools.ietf.org/html/rfc7230#section-3.1.1 - # invalid request-line SHOULD respond with a 400 (Bad Request) - raise HTTPInputError("Malformed HTTP request line") - if not _http_version_re.match(version): - raise HTTPInputError( - "Malformed HTTP version in HTTP Request-Line: %r" % version - ) - return RequestStartLine(method, path, version) - - -ResponseStartLine = collections.namedtuple( - "ResponseStartLine", ["version", "code", "reason"] -) - - -_http_response_line_re = re.compile(r"(HTTP/1.[0-9]) ([0-9]+) ([^\r]*)") - - -def parse_response_start_line(line: str) -> ResponseStartLine: - """Returns a (version, code, reason) tuple for an HTTP 1.x response line. - - The response is a `collections.namedtuple`. - - >>> parse_response_start_line("HTTP/1.1 200 OK") - ResponseStartLine(version='HTTP/1.1', code=200, reason='OK') - """ - line = native_str(line) - match = _http_response_line_re.match(line) - if not match: - raise HTTPInputError("Error parsing response start line") - return ResponseStartLine(match.group(1), int(match.group(2)), match.group(3)) - - -# _parseparam and _parse_header are copied and modified from python2.7's cgi.py -# The original 2.7 version of this code did not correctly support some -# combinations of semicolons and double quotes. -# It has also been modified to support valueless parameters as seen in -# websocket extension negotiations, and to support non-ascii values in -# RFC 2231/5987 format. - - -def _parseparam(s: str) -> Generator[str, None, None]: - while s[:1] == ";": - s = s[1:] - end = s.find(";") - while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2: - end = s.find(";", end + 1) - if end < 0: - end = len(s) - f = s[:end] - yield f.strip() - s = s[end:] - - -def _parse_header(line: str) -> Tuple[str, Dict[str, str]]: - r"""Parse a Content-type like header. - - Return the main content-type and a dictionary of options. - - >>> d = "form-data; foo=\"b\\\\a\\\"r\"; file*=utf-8''T%C3%A4st" - >>> ct, d = _parse_header(d) - >>> ct - 'form-data' - >>> d['file'] == r'T\u00e4st'.encode('ascii').decode('unicode_escape') - True - >>> d['foo'] - 'b\\a"r' - """ - parts = _parseparam(";" + line) - key = next(parts) - # decode_params treats first argument special, but we already stripped key - params = [("Dummy", "value")] - for p in parts: - i = p.find("=") - if i >= 0: - name = p[:i].strip().lower() - value = p[i + 1 :].strip() - params.append((name, native_str(value))) - decoded_params = email.utils.decode_params(params) - decoded_params.pop(0) # get rid of the dummy again - pdict = {} - for name, decoded_value in decoded_params: - value = email.utils.collapse_rfc2231_value(decoded_value) - if len(value) >= 2 and value[0] == '"' and value[-1] == '"': - value = value[1:-1] - pdict[name] = value - return key, pdict - - -def _encode_header(key: str, pdict: Dict[str, str]) -> str: - """Inverse of _parse_header. - - >>> _encode_header('permessage-deflate', - ... {'client_max_window_bits': 15, 'client_no_context_takeover': None}) - 'permessage-deflate; client_max_window_bits=15; client_no_context_takeover' - """ - if not pdict: - return key - out = [key] - # Sort the parameters just to make it easy to test. - for k, v in sorted(pdict.items()): - if v is None: - out.append(k) - else: - # TODO: quote if necessary. - out.append("%s=%s" % (k, v)) - return "; ".join(out) - - -def encode_username_password( - username: Union[str, bytes], password: Union[str, bytes] -) -> bytes: - """Encodes a username/password pair in the format used by HTTP auth. - - The return value is a byte string in the form ``username:password``. - - .. versionadded:: 5.1 - """ - if isinstance(username, unicode_type): - username = unicodedata.normalize("NFC", username) - if isinstance(password, unicode_type): - password = unicodedata.normalize("NFC", password) - return utf8(username) + b":" + utf8(password) - - -def doctests(): - # type: () -> unittest.TestSuite - import doctest - - return doctest.DocTestSuite() - - -_netloc_re = re.compile(r"^(.+):(\d+)$") - - -def split_host_and_port(netloc: str) -> Tuple[str, Optional[int]]: - """Returns ``(host, port)`` tuple from ``netloc``. - - Returned ``port`` will be ``None`` if not present. - - .. versionadded:: 4.1 - """ - match = _netloc_re.match(netloc) - if match: - host = match.group(1) - port = int(match.group(2)) # type: Optional[int] - else: - host = netloc - port = None - return (host, port) - - -def qs_to_qsl(qs: Dict[str, List[AnyStr]]) -> Iterable[Tuple[str, AnyStr]]: - """Generator converting a result of ``parse_qs`` back to name-value pairs. - - .. versionadded:: 5.0 - """ - for k, vs in qs.items(): - for v in vs: - yield (k, v) - - -_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") -_QuotePatt = re.compile(r"[\\].") -_nulljoin = "".join - - -def _unquote_cookie(s: str) -> str: - """Handle double quotes and escaping in cookie values. - - This method is copied verbatim from the Python 3.5 standard - library (http.cookies._unquote) so we don't have to depend on - non-public interfaces. - """ - # If there aren't any doublequotes, - # then there can't be any special characters. See RFC 2109. - if s is None or len(s) < 2: - return s - if s[0] != '"' or s[-1] != '"': - return s - - # We have to assume that we must decode this string. - # Down to work. - - # Remove the "s - s = s[1:-1] - - # Check for special sequences. Examples: - # \012 --> \n - # \" --> " - # - i = 0 - n = len(s) - res = [] - while 0 <= i < n: - o_match = _OctalPatt.search(s, i) - q_match = _QuotePatt.search(s, i) - if not o_match and not q_match: # Neither matched - res.append(s[i:]) - break - # else: - j = k = -1 - if o_match: - j = o_match.start(0) - if q_match: - k = q_match.start(0) - if q_match and (not o_match or k < j): # QuotePatt matched - res.append(s[i:k]) - res.append(s[k + 1]) - i = k + 2 - else: # OctalPatt matched - res.append(s[i:j]) - res.append(chr(int(s[j + 1 : j + 4], 8))) - i = j + 4 - return _nulljoin(res) - - -def parse_cookie(cookie: str) -> Dict[str, str]: - """Parse a ``Cookie`` HTTP header into a dict of name/value pairs. - - This function attempts to mimic browser cookie parsing behavior; - it specifically does not follow any of the cookie-related RFCs - (because browsers don't either). - - The algorithm used is identical to that used by Django version 1.9.10. - - .. versionadded:: 4.4.2 - """ - cookiedict = {} - for chunk in cookie.split(str(";")): - if str("=") in chunk: - key, val = chunk.split(str("="), 1) - else: - # Assume an empty name per - # https://bugzilla.mozilla.org/show_bug.cgi?id=169091 - key, val = str(""), chunk - key, val = key.strip(), val.strip() - if key or val: - # unquote using Python's algorithm. - cookiedict[key] = _unquote_cookie(val) - return cookiedict diff --git a/telegramer/include/tornado/ioloop.py b/telegramer/include/tornado/ioloop.py deleted file mode 100644 index 2cf8844..0000000 --- a/telegramer/include/tornado/ioloop.py +++ /dev/null @@ -1,944 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""An I/O event loop for non-blocking sockets. - -In Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event -loop, with a slightly different interface for historical reasons. -Applications can use either the `.IOLoop` interface or the underlying -`asyncio` event loop directly (unless compatibility with older -versions of Tornado is desired, in which case `.IOLoop` must be used). - -Typical applications will use a single `IOLoop` object, accessed via -`IOLoop.current` class method. The `IOLoop.start` method (or -equivalently, `asyncio.AbstractEventLoop.run_forever`) should usually -be called at the end of the ``main()`` function. Atypical applications -may use more than one `IOLoop`, such as one `IOLoop` per thread, or -per `unittest` case. - -""" - -import asyncio -import concurrent.futures -import datetime -import functools -import logging -import numbers -import os -import sys -import time -import math -import random - -from tornado.concurrent import ( - Future, - is_future, - chain_future, - future_set_exc_info, - future_add_done_callback, -) -from tornado.log import app_log -from tornado.util import Configurable, TimeoutError, import_object - -import typing -from typing import Union, Any, Type, Optional, Callable, TypeVar, Tuple, Awaitable - -if typing.TYPE_CHECKING: - from typing import Dict, List # noqa: F401 - - from typing_extensions import Protocol -else: - Protocol = object - - -class _Selectable(Protocol): - def fileno(self) -> int: - pass - - def close(self) -> None: - pass - - -_T = TypeVar("_T") -_S = TypeVar("_S", bound=_Selectable) - - -class IOLoop(Configurable): - """An I/O event loop. - - As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event - loop. - - Example usage for a simple TCP server: - - .. testcode:: - - import errno - import functools - import socket - - import tornado.ioloop - from tornado.iostream import IOStream - - async def handle_connection(connection, address): - stream = IOStream(connection) - message = await stream.read_until_close() - print("message from client:", message.decode().strip()) - - def connection_ready(sock, fd, events): - while True: - try: - connection, address = sock.accept() - except BlockingIOError: - return - connection.setblocking(0) - io_loop = tornado.ioloop.IOLoop.current() - io_loop.spawn_callback(handle_connection, connection, address) - - if __name__ == '__main__': - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.setblocking(0) - sock.bind(("", 8888)) - sock.listen(128) - - io_loop = tornado.ioloop.IOLoop.current() - callback = functools.partial(connection_ready, sock) - io_loop.add_handler(sock.fileno(), callback, io_loop.READ) - io_loop.start() - - .. testoutput:: - :hide: - - By default, a newly-constructed `IOLoop` becomes the thread's current - `IOLoop`, unless there already is a current `IOLoop`. This behavior - can be controlled with the ``make_current`` argument to the `IOLoop` - constructor: if ``make_current=True``, the new `IOLoop` will always - try to become current and it raises an error if there is already a - current instance. If ``make_current=False``, the new `IOLoop` will - not try to become current. - - In general, an `IOLoop` cannot survive a fork or be shared across - processes in any way. When multiple processes are being used, each - process should create its own `IOLoop`, which also implies that - any objects which depend on the `IOLoop` (such as - `.AsyncHTTPClient`) must also be created in the child processes. - As a guideline, anything that starts processes (including the - `tornado.process` and `multiprocessing` modules) should do so as - early as possible, ideally the first thing the application does - after loading its configuration in ``main()``. - - .. versionchanged:: 4.2 - Added the ``make_current`` keyword argument to the `IOLoop` - constructor. - - .. versionchanged:: 5.0 - - Uses the `asyncio` event loop by default. The - ``IOLoop.configure`` method cannot be used on Python 3 except - to redundantly specify the `asyncio` event loop. - - """ - - # These constants were originally based on constants from the epoll module. - NONE = 0 - READ = 0x001 - WRITE = 0x004 - ERROR = 0x018 - - # In Python 3, _ioloop_for_asyncio maps from asyncio loops to IOLoops. - _ioloop_for_asyncio = dict() # type: Dict[asyncio.AbstractEventLoop, IOLoop] - - @classmethod - def configure( - cls, impl: "Union[None, str, Type[Configurable]]", **kwargs: Any - ) -> None: - if asyncio is not None: - from tornado.platform.asyncio import BaseAsyncIOLoop - - if isinstance(impl, str): - impl = import_object(impl) - if isinstance(impl, type) and not issubclass(impl, BaseAsyncIOLoop): - raise RuntimeError( - "only AsyncIOLoop is allowed when asyncio is available" - ) - super(IOLoop, cls).configure(impl, **kwargs) - - @staticmethod - def instance() -> "IOLoop": - """Deprecated alias for `IOLoop.current()`. - - .. versionchanged:: 5.0 - - Previously, this method returned a global singleton - `IOLoop`, in contrast with the per-thread `IOLoop` returned - by `current()`. In nearly all cases the two were the same - (when they differed, it was generally used from non-Tornado - threads to communicate back to the main thread's `IOLoop`). - This distinction is not present in `asyncio`, so in order - to facilitate integration with that package `instance()` - was changed to be an alias to `current()`. Applications - using the cross-thread communications aspect of - `instance()` should instead set their own global variable - to point to the `IOLoop` they want to use. - - .. deprecated:: 5.0 - """ - return IOLoop.current() - - def install(self) -> None: - """Deprecated alias for `make_current()`. - - .. versionchanged:: 5.0 - - Previously, this method would set this `IOLoop` as the - global singleton used by `IOLoop.instance()`. Now that - `instance()` is an alias for `current()`, `install()` - is an alias for `make_current()`. - - .. deprecated:: 5.0 - """ - self.make_current() - - @staticmethod - def clear_instance() -> None: - """Deprecated alias for `clear_current()`. - - .. versionchanged:: 5.0 - - Previously, this method would clear the `IOLoop` used as - the global singleton by `IOLoop.instance()`. Now that - `instance()` is an alias for `current()`, - `clear_instance()` is an alias for `clear_current()`. - - .. deprecated:: 5.0 - - """ - IOLoop.clear_current() - - @typing.overload - @staticmethod - def current() -> "IOLoop": - pass - - @typing.overload - @staticmethod - def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811 - pass - - @staticmethod - def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811 - """Returns the current thread's `IOLoop`. - - If an `IOLoop` is currently running or has been marked as - current by `make_current`, returns that instance. If there is - no current `IOLoop` and ``instance`` is true, creates one. - - .. versionchanged:: 4.1 - Added ``instance`` argument to control the fallback to - `IOLoop.instance()`. - .. versionchanged:: 5.0 - On Python 3, control of the current `IOLoop` is delegated - to `asyncio`, with this and other methods as pass-through accessors. - The ``instance`` argument now controls whether an `IOLoop` - is created automatically when there is none, instead of - whether we fall back to `IOLoop.instance()` (which is now - an alias for this method). ``instance=False`` is deprecated, - since even if we do not create an `IOLoop`, this method - may initialize the asyncio loop. - """ - try: - loop = asyncio.get_event_loop() - except (RuntimeError, AssertionError): - if not instance: - return None - raise - try: - return IOLoop._ioloop_for_asyncio[loop] - except KeyError: - if instance: - from tornado.platform.asyncio import AsyncIOMainLoop - - current = AsyncIOMainLoop(make_current=True) # type: Optional[IOLoop] - else: - current = None - return current - - def make_current(self) -> None: - """Makes this the `IOLoop` for the current thread. - - An `IOLoop` automatically becomes current for its thread - when it is started, but it is sometimes useful to call - `make_current` explicitly before starting the `IOLoop`, - so that code run at startup time can find the right - instance. - - .. versionchanged:: 4.1 - An `IOLoop` created while there is no current `IOLoop` - will automatically become current. - - .. versionchanged:: 5.0 - This method also sets the current `asyncio` event loop. - """ - # The asyncio event loops override this method. - raise NotImplementedError() - - @staticmethod - def clear_current() -> None: - """Clears the `IOLoop` for the current thread. - - Intended primarily for use by test frameworks in between tests. - - .. versionchanged:: 5.0 - This method also clears the current `asyncio` event loop. - """ - old = IOLoop.current(instance=False) - if old is not None: - old._clear_current_hook() - if asyncio is None: - IOLoop._current.instance = None - - def _clear_current_hook(self) -> None: - """Instance method called when an IOLoop ceases to be current. - - May be overridden by subclasses as a counterpart to make_current. - """ - pass - - @classmethod - def configurable_base(cls) -> Type[Configurable]: - return IOLoop - - @classmethod - def configurable_default(cls) -> Type[Configurable]: - from tornado.platform.asyncio import AsyncIOLoop - - return AsyncIOLoop - - def initialize(self, make_current: Optional[bool] = None) -> None: - if make_current is None: - if IOLoop.current(instance=False) is None: - self.make_current() - elif make_current: - current = IOLoop.current(instance=False) - # AsyncIO loops can already be current by this point. - if current is not None and current is not self: - raise RuntimeError("current IOLoop already exists") - self.make_current() - - def close(self, all_fds: bool = False) -> None: - """Closes the `IOLoop`, freeing any resources used. - - If ``all_fds`` is true, all file descriptors registered on the - IOLoop will be closed (not just the ones created by the - `IOLoop` itself). - - Many applications will only use a single `IOLoop` that runs for the - entire lifetime of the process. In that case closing the `IOLoop` - is not necessary since everything will be cleaned up when the - process exits. `IOLoop.close` is provided mainly for scenarios - such as unit tests, which create and destroy a large number of - ``IOLoops``. - - An `IOLoop` must be completely stopped before it can be closed. This - means that `IOLoop.stop()` must be called *and* `IOLoop.start()` must - be allowed to return before attempting to call `IOLoop.close()`. - Therefore the call to `close` will usually appear just after - the call to `start` rather than near the call to `stop`. - - .. versionchanged:: 3.1 - If the `IOLoop` implementation supports non-integer objects - for "file descriptors", those objects will have their - ``close`` method when ``all_fds`` is true. - """ - raise NotImplementedError() - - @typing.overload - def add_handler( - self, fd: int, handler: Callable[[int, int], None], events: int - ) -> None: - pass - - @typing.overload # noqa: F811 - def add_handler( - self, fd: _S, handler: Callable[[_S, int], None], events: int - ) -> None: - pass - - def add_handler( # noqa: F811 - self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int - ) -> None: - """Registers the given handler to receive the given events for ``fd``. - - The ``fd`` argument may either be an integer file descriptor or - a file-like object with a ``fileno()`` and ``close()`` method. - - The ``events`` argument is a bitwise or of the constants - ``IOLoop.READ``, ``IOLoop.WRITE``, and ``IOLoop.ERROR``. - - When an event occurs, ``handler(fd, events)`` will be run. - - .. versionchanged:: 4.0 - Added the ability to pass file-like objects in addition to - raw file descriptors. - """ - raise NotImplementedError() - - def update_handler(self, fd: Union[int, _Selectable], events: int) -> None: - """Changes the events we listen for ``fd``. - - .. versionchanged:: 4.0 - Added the ability to pass file-like objects in addition to - raw file descriptors. - """ - raise NotImplementedError() - - def remove_handler(self, fd: Union[int, _Selectable]) -> None: - """Stop listening for events on ``fd``. - - .. versionchanged:: 4.0 - Added the ability to pass file-like objects in addition to - raw file descriptors. - """ - raise NotImplementedError() - - def start(self) -> None: - """Starts the I/O loop. - - The loop will run until one of the callbacks calls `stop()`, which - will make the loop stop after the current event iteration completes. - """ - raise NotImplementedError() - - def _setup_logging(self) -> None: - """The IOLoop catches and logs exceptions, so it's - important that log output be visible. However, python's - default behavior for non-root loggers (prior to python - 3.2) is to print an unhelpful "no handlers could be - found" message rather than the actual log entry, so we - must explicitly configure logging if we've made it this - far without anything. - - This method should be called from start() in subclasses. - """ - if not any( - [ - logging.getLogger().handlers, - logging.getLogger("tornado").handlers, - logging.getLogger("tornado.application").handlers, - ] - ): - logging.basicConfig() - - def stop(self) -> None: - """Stop the I/O loop. - - If the event loop is not currently running, the next call to `start()` - will return immediately. - - Note that even after `stop` has been called, the `IOLoop` is not - completely stopped until `IOLoop.start` has also returned. - Some work that was scheduled before the call to `stop` may still - be run before the `IOLoop` shuts down. - """ - raise NotImplementedError() - - def run_sync(self, func: Callable, timeout: Optional[float] = None) -> Any: - """Starts the `IOLoop`, runs the given function, and stops the loop. - - The function must return either an awaitable object or - ``None``. If the function returns an awaitable object, the - `IOLoop` will run until the awaitable is resolved (and - `run_sync()` will return the awaitable's result). If it raises - an exception, the `IOLoop` will stop and the exception will be - re-raised to the caller. - - The keyword-only argument ``timeout`` may be used to set - a maximum duration for the function. If the timeout expires, - a `tornado.util.TimeoutError` is raised. - - This method is useful to allow asynchronous calls in a - ``main()`` function:: - - async def main(): - # do stuff... - - if __name__ == '__main__': - IOLoop.current().run_sync(main) - - .. versionchanged:: 4.3 - Returning a non-``None``, non-awaitable value is now an error. - - .. versionchanged:: 5.0 - If a timeout occurs, the ``func`` coroutine will be cancelled. - - """ - future_cell = [None] # type: List[Optional[Future]] - - def run() -> None: - try: - result = func() - if result is not None: - from tornado.gen import convert_yielded - - result = convert_yielded(result) - except Exception: - fut = Future() # type: Future[Any] - future_cell[0] = fut - future_set_exc_info(fut, sys.exc_info()) - else: - if is_future(result): - future_cell[0] = result - else: - fut = Future() - future_cell[0] = fut - fut.set_result(result) - assert future_cell[0] is not None - self.add_future(future_cell[0], lambda future: self.stop()) - - self.add_callback(run) - if timeout is not None: - - def timeout_callback() -> None: - # If we can cancel the future, do so and wait on it. If not, - # Just stop the loop and return with the task still pending. - # (If we neither cancel nor wait for the task, a warning - # will be logged). - assert future_cell[0] is not None - if not future_cell[0].cancel(): - self.stop() - - timeout_handle = self.add_timeout(self.time() + timeout, timeout_callback) - self.start() - if timeout is not None: - self.remove_timeout(timeout_handle) - assert future_cell[0] is not None - if future_cell[0].cancelled() or not future_cell[0].done(): - raise TimeoutError("Operation timed out after %s seconds" % timeout) - return future_cell[0].result() - - def time(self) -> float: - """Returns the current time according to the `IOLoop`'s clock. - - The return value is a floating-point number relative to an - unspecified time in the past. - - Historically, the IOLoop could be customized to use e.g. - `time.monotonic` instead of `time.time`, but this is not - currently supported and so this method is equivalent to - `time.time`. - - """ - return time.time() - - def add_timeout( - self, - deadline: Union[float, datetime.timedelta], - callback: Callable[..., None], - *args: Any, - **kwargs: Any - ) -> object: - """Runs the ``callback`` at the time ``deadline`` from the I/O loop. - - Returns an opaque handle that may be passed to - `remove_timeout` to cancel. - - ``deadline`` may be a number denoting a time (on the same - scale as `IOLoop.time`, normally `time.time`), or a - `datetime.timedelta` object for a deadline relative to the - current time. Since Tornado 4.0, `call_later` is a more - convenient alternative for the relative case since it does not - require a timedelta object. - - Note that it is not safe to call `add_timeout` from other threads. - Instead, you must use `add_callback` to transfer control to the - `IOLoop`'s thread, and then call `add_timeout` from there. - - Subclasses of IOLoop must implement either `add_timeout` or - `call_at`; the default implementations of each will call - the other. `call_at` is usually easier to implement, but - subclasses that wish to maintain compatibility with Tornado - versions prior to 4.0 must use `add_timeout` instead. - - .. versionchanged:: 4.0 - Now passes through ``*args`` and ``**kwargs`` to the callback. - """ - if isinstance(deadline, numbers.Real): - return self.call_at(deadline, callback, *args, **kwargs) - elif isinstance(deadline, datetime.timedelta): - return self.call_at( - self.time() + deadline.total_seconds(), callback, *args, **kwargs - ) - else: - raise TypeError("Unsupported deadline %r" % deadline) - - def call_later( - self, delay: float, callback: Callable[..., None], *args: Any, **kwargs: Any - ) -> object: - """Runs the ``callback`` after ``delay`` seconds have passed. - - Returns an opaque handle that may be passed to `remove_timeout` - to cancel. Note that unlike the `asyncio` method of the same - name, the returned object does not have a ``cancel()`` method. - - See `add_timeout` for comments on thread-safety and subclassing. - - .. versionadded:: 4.0 - """ - return self.call_at(self.time() + delay, callback, *args, **kwargs) - - def call_at( - self, when: float, callback: Callable[..., None], *args: Any, **kwargs: Any - ) -> object: - """Runs the ``callback`` at the absolute time designated by ``when``. - - ``when`` must be a number using the same reference point as - `IOLoop.time`. - - Returns an opaque handle that may be passed to `remove_timeout` - to cancel. Note that unlike the `asyncio` method of the same - name, the returned object does not have a ``cancel()`` method. - - See `add_timeout` for comments on thread-safety and subclassing. - - .. versionadded:: 4.0 - """ - return self.add_timeout(when, callback, *args, **kwargs) - - def remove_timeout(self, timeout: object) -> None: - """Cancels a pending timeout. - - The argument is a handle as returned by `add_timeout`. It is - safe to call `remove_timeout` even if the callback has already - been run. - """ - raise NotImplementedError() - - def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None: - """Calls the given callback on the next I/O loop iteration. - - It is safe to call this method from any thread at any time, - except from a signal handler. Note that this is the **only** - method in `IOLoop` that makes this thread-safety guarantee; all - other interaction with the `IOLoop` must be done from that - `IOLoop`'s thread. `add_callback()` may be used to transfer - control from other threads to the `IOLoop`'s thread. - - To add a callback from a signal handler, see - `add_callback_from_signal`. - """ - raise NotImplementedError() - - def add_callback_from_signal( - self, callback: Callable, *args: Any, **kwargs: Any - ) -> None: - """Calls the given callback on the next I/O loop iteration. - - Safe for use from a Python signal handler; should not be used - otherwise. - """ - raise NotImplementedError() - - def spawn_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None: - """Calls the given callback on the next IOLoop iteration. - - As of Tornado 6.0, this method is equivalent to `add_callback`. - - .. versionadded:: 4.0 - """ - self.add_callback(callback, *args, **kwargs) - - def add_future( - self, - future: "Union[Future[_T], concurrent.futures.Future[_T]]", - callback: Callable[["Future[_T]"], None], - ) -> None: - """Schedules a callback on the ``IOLoop`` when the given - `.Future` is finished. - - The callback is invoked with one argument, the - `.Future`. - - This method only accepts `.Future` objects and not other - awaitables (unlike most of Tornado where the two are - interchangeable). - """ - if isinstance(future, Future): - # Note that we specifically do not want the inline behavior of - # tornado.concurrent.future_add_done_callback. We always want - # this callback scheduled on the next IOLoop iteration (which - # asyncio.Future always does). - # - # Wrap the callback in self._run_callback so we control - # the error logging (i.e. it goes to tornado.log.app_log - # instead of asyncio's log). - future.add_done_callback( - lambda f: self._run_callback(functools.partial(callback, future)) - ) - else: - assert is_future(future) - # For concurrent futures, we use self.add_callback, so - # it's fine if future_add_done_callback inlines that call. - future_add_done_callback( - future, lambda f: self.add_callback(callback, future) - ) - - def run_in_executor( - self, - executor: Optional[concurrent.futures.Executor], - func: Callable[..., _T], - *args: Any - ) -> Awaitable[_T]: - """Runs a function in a ``concurrent.futures.Executor``. If - ``executor`` is ``None``, the IO loop's default executor will be used. - - Use `functools.partial` to pass keyword arguments to ``func``. - - .. versionadded:: 5.0 - """ - if executor is None: - if not hasattr(self, "_executor"): - from tornado.process import cpu_count - - self._executor = concurrent.futures.ThreadPoolExecutor( - max_workers=(cpu_count() * 5) - ) # type: concurrent.futures.Executor - executor = self._executor - c_future = executor.submit(func, *args) - # Concurrent Futures are not usable with await. Wrap this in a - # Tornado Future instead, using self.add_future for thread-safety. - t_future = Future() # type: Future[_T] - self.add_future(c_future, lambda f: chain_future(f, t_future)) - return t_future - - def set_default_executor(self, executor: concurrent.futures.Executor) -> None: - """Sets the default executor to use with :meth:`run_in_executor`. - - .. versionadded:: 5.0 - """ - self._executor = executor - - def _run_callback(self, callback: Callable[[], Any]) -> None: - """Runs a callback with error handling. - - .. versionchanged:: 6.0 - - CancelledErrors are no longer logged. - """ - try: - ret = callback() - if ret is not None: - from tornado import gen - - # Functions that return Futures typically swallow all - # exceptions and store them in the Future. If a Future - # makes it out to the IOLoop, ensure its exception (if any) - # gets logged too. - try: - ret = gen.convert_yielded(ret) - except gen.BadYieldError: - # It's not unusual for add_callback to be used with - # methods returning a non-None and non-yieldable - # result, which should just be ignored. - pass - else: - self.add_future(ret, self._discard_future_result) - except asyncio.CancelledError: - pass - except Exception: - app_log.error("Exception in callback %r", callback, exc_info=True) - - def _discard_future_result(self, future: Future) -> None: - """Avoid unhandled-exception warnings from spawned coroutines.""" - future.result() - - def split_fd( - self, fd: Union[int, _Selectable] - ) -> Tuple[int, Union[int, _Selectable]]: - # """Returns an (fd, obj) pair from an ``fd`` parameter. - - # We accept both raw file descriptors and file-like objects as - # input to `add_handler` and related methods. When a file-like - # object is passed, we must retain the object itself so we can - # close it correctly when the `IOLoop` shuts down, but the - # poller interfaces favor file descriptors (they will accept - # file-like objects and call ``fileno()`` for you, but they - # always return the descriptor itself). - - # This method is provided for use by `IOLoop` subclasses and should - # not generally be used by application code. - - # .. versionadded:: 4.0 - # """ - if isinstance(fd, int): - return fd, fd - return fd.fileno(), fd - - def close_fd(self, fd: Union[int, _Selectable]) -> None: - # """Utility method to close an ``fd``. - - # If ``fd`` is a file-like object, we close it directly; otherwise - # we use `os.close`. - - # This method is provided for use by `IOLoop` subclasses (in - # implementations of ``IOLoop.close(all_fds=True)`` and should - # not generally be used by application code. - - # .. versionadded:: 4.0 - # """ - try: - if isinstance(fd, int): - os.close(fd) - else: - fd.close() - except OSError: - pass - - -class _Timeout(object): - """An IOLoop timeout, a UNIX timestamp and a callback""" - - # Reduce memory overhead when there are lots of pending callbacks - __slots__ = ["deadline", "callback", "tdeadline"] - - def __init__( - self, deadline: float, callback: Callable[[], None], io_loop: IOLoop - ) -> None: - if not isinstance(deadline, numbers.Real): - raise TypeError("Unsupported deadline %r" % deadline) - self.deadline = deadline - self.callback = callback - self.tdeadline = ( - deadline, - next(io_loop._timeout_counter), - ) # type: Tuple[float, int] - - # Comparison methods to sort by deadline, with object id as a tiebreaker - # to guarantee a consistent ordering. The heapq module uses __le__ - # in python2.5, and __lt__ in 2.6+ (sort() and most other comparisons - # use __lt__). - def __lt__(self, other: "_Timeout") -> bool: - return self.tdeadline < other.tdeadline - - def __le__(self, other: "_Timeout") -> bool: - return self.tdeadline <= other.tdeadline - - -class PeriodicCallback(object): - """Schedules the given callback to be called periodically. - - The callback is called every ``callback_time`` milliseconds. - Note that the timeout is given in milliseconds, while most other - time-related functions in Tornado use seconds. - - If ``jitter`` is specified, each callback time will be randomly selected - within a window of ``jitter * callback_time`` milliseconds. - Jitter can be used to reduce alignment of events with similar periods. - A jitter of 0.1 means allowing a 10% variation in callback time. - The window is centered on ``callback_time`` so the total number of calls - within a given interval should not be significantly affected by adding - jitter. - - If the callback runs for longer than ``callback_time`` milliseconds, - subsequent invocations will be skipped to get back on schedule. - - `start` must be called after the `PeriodicCallback` is created. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - - .. versionchanged:: 5.1 - The ``jitter`` argument is added. - """ - - def __init__( - self, callback: Callable[[], None], callback_time: float, jitter: float = 0 - ) -> None: - self.callback = callback - if callback_time <= 0: - raise ValueError("Periodic callback must have a positive callback_time") - self.callback_time = callback_time - self.jitter = jitter - self._running = False - self._timeout = None # type: object - - def start(self) -> None: - """Starts the timer.""" - # Looking up the IOLoop here allows to first instantiate the - # PeriodicCallback in another thread, then start it using - # IOLoop.add_callback(). - self.io_loop = IOLoop.current() - self._running = True - self._next_timeout = self.io_loop.time() - self._schedule_next() - - def stop(self) -> None: - """Stops the timer.""" - self._running = False - if self._timeout is not None: - self.io_loop.remove_timeout(self._timeout) - self._timeout = None - - def is_running(self) -> bool: - """Returns ``True`` if this `.PeriodicCallback` has been started. - - .. versionadded:: 4.1 - """ - return self._running - - def _run(self) -> None: - if not self._running: - return - try: - return self.callback() - except Exception: - app_log.error("Exception in callback %r", self.callback, exc_info=True) - finally: - self._schedule_next() - - def _schedule_next(self) -> None: - if self._running: - self._update_next(self.io_loop.time()) - self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run) - - def _update_next(self, current_time: float) -> None: - callback_time_sec = self.callback_time / 1000.0 - if self.jitter: - # apply jitter fraction - callback_time_sec *= 1 + (self.jitter * (random.random() - 0.5)) - if self._next_timeout <= current_time: - # The period should be measured from the start of one call - # to the start of the next. If one call takes too long, - # skip cycles to get back to a multiple of the original - # schedule. - self._next_timeout += ( - math.floor((current_time - self._next_timeout) / callback_time_sec) + 1 - ) * callback_time_sec - else: - # If the clock moved backwards, ensure we advance the next - # timeout instead of recomputing the same value again. - # This may result in long gaps between callbacks if the - # clock jumps backwards by a lot, but the far more common - # scenario is a small NTP adjustment that should just be - # ignored. - # - # Note that on some systems if time.time() runs slower - # than time.monotonic() (most common on windows), we - # effectively experience a small backwards time jump on - # every iteration because PeriodicCallback uses - # time.time() while asyncio schedules callbacks using - # time.monotonic(). - # https://github.com/tornadoweb/tornado/issues/2333 - self._next_timeout += callback_time_sec diff --git a/telegramer/include/tornado/iostream.py b/telegramer/include/tornado/iostream.py deleted file mode 100644 index 19c5485..0000000 --- a/telegramer/include/tornado/iostream.py +++ /dev/null @@ -1,1660 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Utility classes to write to and read from non-blocking files and sockets. - -Contents: - -* `BaseIOStream`: Generic interface for reading and writing. -* `IOStream`: Implementation of BaseIOStream using non-blocking sockets. -* `SSLIOStream`: SSL-aware version of IOStream. -* `PipeIOStream`: Pipe-based IOStream implementation. -""" - -import asyncio -import collections -import errno -import io -import numbers -import os -import socket -import ssl -import sys -import re - -from tornado.concurrent import Future, future_set_result_unless_cancelled -from tornado import ioloop -from tornado.log import gen_log -from tornado.netutil import ssl_wrap_socket, _client_ssl_defaults, _server_ssl_defaults -from tornado.util import errno_from_exception - -import typing -from typing import ( - Union, - Optional, - Awaitable, - Callable, - Pattern, - Any, - Dict, - TypeVar, - Tuple, -) -from types import TracebackType - -if typing.TYPE_CHECKING: - from typing import Deque, List, Type # noqa: F401 - -_IOStreamType = TypeVar("_IOStreamType", bound="IOStream") - -# These errnos indicate that a connection has been abruptly terminated. -# They should be caught and handled less noisily than other errors. -_ERRNO_CONNRESET = (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE, errno.ETIMEDOUT) - -if hasattr(errno, "WSAECONNRESET"): - _ERRNO_CONNRESET += ( # type: ignore - errno.WSAECONNRESET, # type: ignore - errno.WSAECONNABORTED, # type: ignore - errno.WSAETIMEDOUT, # type: ignore - ) - -if sys.platform == "darwin": - # OSX appears to have a race condition that causes send(2) to return - # EPROTOTYPE if called while a socket is being torn down: - # http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ - # Since the socket is being closed anyway, treat this as an ECONNRESET - # instead of an unexpected error. - _ERRNO_CONNRESET += (errno.EPROTOTYPE,) # type: ignore - -_WINDOWS = sys.platform.startswith("win") - - -class StreamClosedError(IOError): - """Exception raised by `IOStream` methods when the stream is closed. - - Note that the close callback is scheduled to run *after* other - callbacks on the stream (to allow for buffered data to be processed), - so you may see this error before you see the close callback. - - The ``real_error`` attribute contains the underlying error that caused - the stream to close (if any). - - .. versionchanged:: 4.3 - Added the ``real_error`` attribute. - """ - - def __init__(self, real_error: Optional[BaseException] = None) -> None: - super().__init__("Stream is closed") - self.real_error = real_error - - -class UnsatisfiableReadError(Exception): - """Exception raised when a read cannot be satisfied. - - Raised by ``read_until`` and ``read_until_regex`` with a ``max_bytes`` - argument. - """ - - pass - - -class StreamBufferFullError(Exception): - """Exception raised by `IOStream` methods when the buffer is full. - """ - - -class _StreamBuffer(object): - """ - A specialized buffer that tries to avoid copies when large pieces - of data are encountered. - """ - - def __init__(self) -> None: - # A sequence of (False, bytearray) and (True, memoryview) objects - self._buffers = ( - collections.deque() - ) # type: Deque[Tuple[bool, Union[bytearray, memoryview]]] - # Position in the first buffer - self._first_pos = 0 - self._size = 0 - - def __len__(self) -> int: - return self._size - - # Data above this size will be appended separately instead - # of extending an existing bytearray - _large_buf_threshold = 2048 - - def append(self, data: Union[bytes, bytearray, memoryview]) -> None: - """ - Append the given piece of data (should be a buffer-compatible object). - """ - size = len(data) - if size > self._large_buf_threshold: - if not isinstance(data, memoryview): - data = memoryview(data) - self._buffers.append((True, data)) - elif size > 0: - if self._buffers: - is_memview, b = self._buffers[-1] - new_buf = is_memview or len(b) >= self._large_buf_threshold - else: - new_buf = True - if new_buf: - self._buffers.append((False, bytearray(data))) - else: - b += data # type: ignore - - self._size += size - - def peek(self, size: int) -> memoryview: - """ - Get a view over at most ``size`` bytes (possibly fewer) at the - current buffer position. - """ - assert size > 0 - try: - is_memview, b = self._buffers[0] - except IndexError: - return memoryview(b"") - - pos = self._first_pos - if is_memview: - return typing.cast(memoryview, b[pos : pos + size]) - else: - return memoryview(b)[pos : pos + size] - - def advance(self, size: int) -> None: - """ - Advance the current buffer position by ``size`` bytes. - """ - assert 0 < size <= self._size - self._size -= size - pos = self._first_pos - - buffers = self._buffers - while buffers and size > 0: - is_large, b = buffers[0] - b_remain = len(b) - size - pos - if b_remain <= 0: - buffers.popleft() - size -= len(b) - pos - pos = 0 - elif is_large: - pos += size - size = 0 - else: - # Amortized O(1) shrink for Python 2 - pos += size - if len(b) <= 2 * pos: - del typing.cast(bytearray, b)[:pos] - pos = 0 - size = 0 - - assert size == 0 - self._first_pos = pos - - -class BaseIOStream(object): - """A utility class to write to and read from a non-blocking file or socket. - - We support a non-blocking ``write()`` and a family of ``read_*()`` - methods. When the operation completes, the ``Awaitable`` will resolve - with the data read (or ``None`` for ``write()``). All outstanding - ``Awaitables`` will resolve with a `StreamClosedError` when the - stream is closed; `.BaseIOStream.set_close_callback` can also be used - to be notified of a closed stream. - - When a stream is closed due to an error, the IOStream's ``error`` - attribute contains the exception object. - - Subclasses must implement `fileno`, `close_fd`, `write_to_fd`, - `read_from_fd`, and optionally `get_fd_error`. - - """ - - def __init__( - self, - max_buffer_size: Optional[int] = None, - read_chunk_size: Optional[int] = None, - max_write_buffer_size: Optional[int] = None, - ) -> None: - """`BaseIOStream` constructor. - - :arg max_buffer_size: Maximum amount of incoming data to buffer; - defaults to 100MB. - :arg read_chunk_size: Amount of data to read at one time from the - underlying transport; defaults to 64KB. - :arg max_write_buffer_size: Amount of outgoing data to buffer; - defaults to unlimited. - - .. versionchanged:: 4.0 - Add the ``max_write_buffer_size`` parameter. Changed default - ``read_chunk_size`` to 64KB. - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been - removed. - """ - self.io_loop = ioloop.IOLoop.current() - self.max_buffer_size = max_buffer_size or 104857600 - # A chunk size that is too close to max_buffer_size can cause - # spurious failures. - self.read_chunk_size = min(read_chunk_size or 65536, self.max_buffer_size // 2) - self.max_write_buffer_size = max_write_buffer_size - self.error = None # type: Optional[BaseException] - self._read_buffer = bytearray() - self._read_buffer_pos = 0 - self._read_buffer_size = 0 - self._user_read_buffer = False - self._after_user_read_buffer = None # type: Optional[bytearray] - self._write_buffer = _StreamBuffer() - self._total_write_index = 0 - self._total_write_done_index = 0 - self._read_delimiter = None # type: Optional[bytes] - self._read_regex = None # type: Optional[Pattern] - self._read_max_bytes = None # type: Optional[int] - self._read_bytes = None # type: Optional[int] - self._read_partial = False - self._read_until_close = False - self._read_future = None # type: Optional[Future] - self._write_futures = ( - collections.deque() - ) # type: Deque[Tuple[int, Future[None]]] - self._close_callback = None # type: Optional[Callable[[], None]] - self._connect_future = None # type: Optional[Future[IOStream]] - # _ssl_connect_future should be defined in SSLIOStream - # but it's here so we can clean it up in _signal_closed - # TODO: refactor that so subclasses can add additional futures - # to be cancelled. - self._ssl_connect_future = None # type: Optional[Future[SSLIOStream]] - self._connecting = False - self._state = None # type: Optional[int] - self._closed = False - - def fileno(self) -> Union[int, ioloop._Selectable]: - """Returns the file descriptor for this stream.""" - raise NotImplementedError() - - def close_fd(self) -> None: - """Closes the file underlying this stream. - - ``close_fd`` is called by `BaseIOStream` and should not be called - elsewhere; other users should call `close` instead. - """ - raise NotImplementedError() - - def write_to_fd(self, data: memoryview) -> int: - """Attempts to write ``data`` to the underlying file. - - Returns the number of bytes written. - """ - raise NotImplementedError() - - def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: - """Attempts to read from the underlying file. - - Reads up to ``len(buf)`` bytes, storing them in the buffer. - Returns the number of bytes read. Returns None if there was - nothing to read (the socket returned `~errno.EWOULDBLOCK` or - equivalent), and zero on EOF. - - .. versionchanged:: 5.0 - - Interface redesigned to take a buffer and return a number - of bytes instead of a freshly-allocated object. - """ - raise NotImplementedError() - - def get_fd_error(self) -> Optional[Exception]: - """Returns information about any error on the underlying file. - - This method is called after the `.IOLoop` has signaled an error on the - file descriptor, and should return an Exception (such as `socket.error` - with additional information, or None if no such information is - available. - """ - return None - - def read_until_regex( - self, regex: bytes, max_bytes: Optional[int] = None - ) -> Awaitable[bytes]: - """Asynchronously read until we have matched the given regex. - - The result includes the data that matches the regex and anything - that came before it. - - If ``max_bytes`` is not None, the connection will be closed - if more than ``max_bytes`` bytes have been read and the regex is - not satisfied. - - .. versionchanged:: 4.0 - Added the ``max_bytes`` argument. The ``callback`` argument is - now optional and a `.Future` will be returned if it is omitted. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - `.Future` instead. - - """ - future = self._start_read() - self._read_regex = re.compile(regex) - self._read_max_bytes = max_bytes - try: - self._try_inline_read() - except UnsatisfiableReadError as e: - # Handle this the same way as in _handle_events. - gen_log.info("Unsatisfiable read, closing connection: %s" % e) - self.close(exc_info=e) - return future - except: - # Ensure that the future doesn't log an error because its - # failure was never examined. - future.add_done_callback(lambda f: f.exception()) - raise - return future - - def read_until( - self, delimiter: bytes, max_bytes: Optional[int] = None - ) -> Awaitable[bytes]: - """Asynchronously read until we have found the given delimiter. - - The result includes all the data read including the delimiter. - - If ``max_bytes`` is not None, the connection will be closed - if more than ``max_bytes`` bytes have been read and the delimiter - is not found. - - .. versionchanged:: 4.0 - Added the ``max_bytes`` argument. The ``callback`` argument is - now optional and a `.Future` will be returned if it is omitted. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - `.Future` instead. - """ - future = self._start_read() - self._read_delimiter = delimiter - self._read_max_bytes = max_bytes - try: - self._try_inline_read() - except UnsatisfiableReadError as e: - # Handle this the same way as in _handle_events. - gen_log.info("Unsatisfiable read, closing connection: %s" % e) - self.close(exc_info=e) - return future - except: - future.add_done_callback(lambda f: f.exception()) - raise - return future - - def read_bytes(self, num_bytes: int, partial: bool = False) -> Awaitable[bytes]: - """Asynchronously read a number of bytes. - - If ``partial`` is true, data is returned as soon as we have - any bytes to return (but never more than ``num_bytes``) - - .. versionchanged:: 4.0 - Added the ``partial`` argument. The callback argument is now - optional and a `.Future` will be returned if it is omitted. - - .. versionchanged:: 6.0 - - The ``callback`` and ``streaming_callback`` arguments have - been removed. Use the returned `.Future` (and - ``partial=True`` for ``streaming_callback``) instead. - - """ - future = self._start_read() - assert isinstance(num_bytes, numbers.Integral) - self._read_bytes = num_bytes - self._read_partial = partial - try: - self._try_inline_read() - except: - future.add_done_callback(lambda f: f.exception()) - raise - return future - - def read_into(self, buf: bytearray, partial: bool = False) -> Awaitable[int]: - """Asynchronously read a number of bytes. - - ``buf`` must be a writable buffer into which data will be read. - - If ``partial`` is true, the callback is run as soon as any bytes - have been read. Otherwise, it is run when the ``buf`` has been - entirely filled with read data. - - .. versionadded:: 5.0 - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - `.Future` instead. - - """ - future = self._start_read() - - # First copy data already in read buffer - available_bytes = self._read_buffer_size - n = len(buf) - if available_bytes >= n: - end = self._read_buffer_pos + n - buf[:] = memoryview(self._read_buffer)[self._read_buffer_pos : end] - del self._read_buffer[:end] - self._after_user_read_buffer = self._read_buffer - elif available_bytes > 0: - buf[:available_bytes] = memoryview(self._read_buffer)[ - self._read_buffer_pos : - ] - - # Set up the supplied buffer as our temporary read buffer. - # The original (if it had any data remaining) has been - # saved for later. - self._user_read_buffer = True - self._read_buffer = buf - self._read_buffer_pos = 0 - self._read_buffer_size = available_bytes - self._read_bytes = n - self._read_partial = partial - - try: - self._try_inline_read() - except: - future.add_done_callback(lambda f: f.exception()) - raise - return future - - def read_until_close(self) -> Awaitable[bytes]: - """Asynchronously reads all data from the socket until it is closed. - - This will buffer all available data until ``max_buffer_size`` - is reached. If flow control or cancellation are desired, use a - loop with `read_bytes(partial=True) <.read_bytes>` instead. - - .. versionchanged:: 4.0 - The callback argument is now optional and a `.Future` will - be returned if it is omitted. - - .. versionchanged:: 6.0 - - The ``callback`` and ``streaming_callback`` arguments have - been removed. Use the returned `.Future` (and `read_bytes` - with ``partial=True`` for ``streaming_callback``) instead. - - """ - future = self._start_read() - if self.closed(): - self._finish_read(self._read_buffer_size, False) - return future - self._read_until_close = True - try: - self._try_inline_read() - except: - future.add_done_callback(lambda f: f.exception()) - raise - return future - - def write(self, data: Union[bytes, memoryview]) -> "Future[None]": - """Asynchronously write the given data to this stream. - - This method returns a `.Future` that resolves (with a result - of ``None``) when the write has been completed. - - The ``data`` argument may be of type `bytes` or `memoryview`. - - .. versionchanged:: 4.0 - Now returns a `.Future` if no callback is given. - - .. versionchanged:: 4.5 - Added support for `memoryview` arguments. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - `.Future` instead. - - """ - self._check_closed() - if data: - if ( - self.max_write_buffer_size is not None - and len(self._write_buffer) + len(data) > self.max_write_buffer_size - ): - raise StreamBufferFullError("Reached maximum write buffer size") - self._write_buffer.append(data) - self._total_write_index += len(data) - future = Future() # type: Future[None] - future.add_done_callback(lambda f: f.exception()) - self._write_futures.append((self._total_write_index, future)) - if not self._connecting: - self._handle_write() - if self._write_buffer: - self._add_io_state(self.io_loop.WRITE) - self._maybe_add_error_listener() - return future - - def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None: - """Call the given callback when the stream is closed. - - This mostly is not necessary for applications that use the - `.Future` interface; all outstanding ``Futures`` will resolve - with a `StreamClosedError` when the stream is closed. However, - it is still useful as a way to signal that the stream has been - closed while no other read or write is in progress. - - Unlike other callback-based interfaces, ``set_close_callback`` - was not removed in Tornado 6.0. - """ - self._close_callback = callback - self._maybe_add_error_listener() - - def close( - self, - exc_info: Union[ - None, - bool, - BaseException, - Tuple[ - "Optional[Type[BaseException]]", - Optional[BaseException], - Optional[TracebackType], - ], - ] = False, - ) -> None: - """Close this stream. - - If ``exc_info`` is true, set the ``error`` attribute to the current - exception from `sys.exc_info` (or if ``exc_info`` is a tuple, - use that instead of `sys.exc_info`). - """ - if not self.closed(): - if exc_info: - if isinstance(exc_info, tuple): - self.error = exc_info[1] - elif isinstance(exc_info, BaseException): - self.error = exc_info - else: - exc_info = sys.exc_info() - if any(exc_info): - self.error = exc_info[1] - if self._read_until_close: - self._read_until_close = False - self._finish_read(self._read_buffer_size, False) - elif self._read_future is not None: - # resolve reads that are pending and ready to complete - try: - pos = self._find_read_pos() - except UnsatisfiableReadError: - pass - else: - if pos is not None: - self._read_from_buffer(pos) - if self._state is not None: - self.io_loop.remove_handler(self.fileno()) - self._state = None - self.close_fd() - self._closed = True - self._signal_closed() - - def _signal_closed(self) -> None: - futures = [] # type: List[Future] - if self._read_future is not None: - futures.append(self._read_future) - self._read_future = None - futures += [future for _, future in self._write_futures] - self._write_futures.clear() - if self._connect_future is not None: - futures.append(self._connect_future) - self._connect_future = None - for future in futures: - if not future.done(): - future.set_exception(StreamClosedError(real_error=self.error)) - # Reference the exception to silence warnings. Annoyingly, - # this raises if the future was cancelled, but just - # returns any other error. - try: - future.exception() - except asyncio.CancelledError: - pass - if self._ssl_connect_future is not None: - # _ssl_connect_future expects to see the real exception (typically - # an ssl.SSLError), not just StreamClosedError. - if not self._ssl_connect_future.done(): - if self.error is not None: - self._ssl_connect_future.set_exception(self.error) - else: - self._ssl_connect_future.set_exception(StreamClosedError()) - self._ssl_connect_future.exception() - self._ssl_connect_future = None - if self._close_callback is not None: - cb = self._close_callback - self._close_callback = None - self.io_loop.add_callback(cb) - # Clear the buffers so they can be cleared immediately even - # if the IOStream object is kept alive by a reference cycle. - # TODO: Clear the read buffer too; it currently breaks some tests. - self._write_buffer = None # type: ignore - - def reading(self) -> bool: - """Returns ``True`` if we are currently reading from the stream.""" - return self._read_future is not None - - def writing(self) -> bool: - """Returns ``True`` if we are currently writing to the stream.""" - return bool(self._write_buffer) - - def closed(self) -> bool: - """Returns ``True`` if the stream has been closed.""" - return self._closed - - def set_nodelay(self, value: bool) -> None: - """Sets the no-delay flag for this stream. - - By default, data written to TCP streams may be held for a time - to make the most efficient use of bandwidth (according to - Nagle's algorithm). The no-delay flag requests that data be - written as soon as possible, even if doing so would consume - additional bandwidth. - - This flag is currently defined only for TCP-based ``IOStreams``. - - .. versionadded:: 3.1 - """ - pass - - def _handle_connect(self) -> None: - raise NotImplementedError() - - def _handle_events(self, fd: Union[int, ioloop._Selectable], events: int) -> None: - if self.closed(): - gen_log.warning("Got events for closed stream %s", fd) - return - try: - if self._connecting: - # Most IOLoops will report a write failed connect - # with the WRITE event, but SelectIOLoop reports a - # READ as well so we must check for connecting before - # either. - self._handle_connect() - if self.closed(): - return - if events & self.io_loop.READ: - self._handle_read() - if self.closed(): - return - if events & self.io_loop.WRITE: - self._handle_write() - if self.closed(): - return - if events & self.io_loop.ERROR: - self.error = self.get_fd_error() - # We may have queued up a user callback in _handle_read or - # _handle_write, so don't close the IOStream until those - # callbacks have had a chance to run. - self.io_loop.add_callback(self.close) - return - state = self.io_loop.ERROR - if self.reading(): - state |= self.io_loop.READ - if self.writing(): - state |= self.io_loop.WRITE - if state == self.io_loop.ERROR and self._read_buffer_size == 0: - # If the connection is idle, listen for reads too so - # we can tell if the connection is closed. If there is - # data in the read buffer we won't run the close callback - # yet anyway, so we don't need to listen in this case. - state |= self.io_loop.READ - if state != self._state: - assert ( - self._state is not None - ), "shouldn't happen: _handle_events without self._state" - self._state = state - self.io_loop.update_handler(self.fileno(), self._state) - except UnsatisfiableReadError as e: - gen_log.info("Unsatisfiable read, closing connection: %s" % e) - self.close(exc_info=e) - except Exception as e: - gen_log.error("Uncaught exception, closing connection.", exc_info=True) - self.close(exc_info=e) - raise - - def _read_to_buffer_loop(self) -> Optional[int]: - # This method is called from _handle_read and _try_inline_read. - if self._read_bytes is not None: - target_bytes = self._read_bytes # type: Optional[int] - elif self._read_max_bytes is not None: - target_bytes = self._read_max_bytes - elif self.reading(): - # For read_until without max_bytes, or - # read_until_close, read as much as we can before - # scanning for the delimiter. - target_bytes = None - else: - target_bytes = 0 - next_find_pos = 0 - while not self.closed(): - # Read from the socket until we get EWOULDBLOCK or equivalent. - # SSL sockets do some internal buffering, and if the data is - # sitting in the SSL object's buffer select() and friends - # can't see it; the only way to find out if it's there is to - # try to read it. - if self._read_to_buffer() == 0: - break - - # If we've read all the bytes we can use, break out of - # this loop. - - # If we've reached target_bytes, we know we're done. - if target_bytes is not None and self._read_buffer_size >= target_bytes: - break - - # Otherwise, we need to call the more expensive find_read_pos. - # It's inefficient to do this on every read, so instead - # do it on the first read and whenever the read buffer - # size has doubled. - if self._read_buffer_size >= next_find_pos: - pos = self._find_read_pos() - if pos is not None: - return pos - next_find_pos = self._read_buffer_size * 2 - return self._find_read_pos() - - def _handle_read(self) -> None: - try: - pos = self._read_to_buffer_loop() - except UnsatisfiableReadError: - raise - except asyncio.CancelledError: - raise - except Exception as e: - gen_log.warning("error on read: %s" % e) - self.close(exc_info=e) - return - if pos is not None: - self._read_from_buffer(pos) - - def _start_read(self) -> Future: - if self._read_future is not None: - # It is an error to start a read while a prior read is unresolved. - # However, if the prior read is unresolved because the stream was - # closed without satisfying it, it's better to raise - # StreamClosedError instead of AssertionError. In particular, this - # situation occurs in harmless situations in http1connection.py and - # an AssertionError would be logged noisily. - # - # On the other hand, it is legal to start a new read while the - # stream is closed, in case the read can be satisfied from the - # read buffer. So we only want to check the closed status of the - # stream if we need to decide what kind of error to raise for - # "already reading". - # - # These conditions have proven difficult to test; we have no - # unittests that reliably verify this behavior so be careful - # when making changes here. See #2651 and #2719. - self._check_closed() - assert self._read_future is None, "Already reading" - self._read_future = Future() - return self._read_future - - def _finish_read(self, size: int, streaming: bool) -> None: - if self._user_read_buffer: - self._read_buffer = self._after_user_read_buffer or bytearray() - self._after_user_read_buffer = None - self._read_buffer_pos = 0 - self._read_buffer_size = len(self._read_buffer) - self._user_read_buffer = False - result = size # type: Union[int, bytes] - else: - result = self._consume(size) - if self._read_future is not None: - future = self._read_future - self._read_future = None - future_set_result_unless_cancelled(future, result) - self._maybe_add_error_listener() - - def _try_inline_read(self) -> None: - """Attempt to complete the current read operation from buffered data. - - If the read can be completed without blocking, schedules the - read callback on the next IOLoop iteration; otherwise starts - listening for reads on the socket. - """ - # See if we've already got the data from a previous read - pos = self._find_read_pos() - if pos is not None: - self._read_from_buffer(pos) - return - self._check_closed() - pos = self._read_to_buffer_loop() - if pos is not None: - self._read_from_buffer(pos) - return - # We couldn't satisfy the read inline, so make sure we're - # listening for new data unless the stream is closed. - if not self.closed(): - self._add_io_state(ioloop.IOLoop.READ) - - def _read_to_buffer(self) -> Optional[int]: - """Reads from the socket and appends the result to the read buffer. - - Returns the number of bytes read. Returns 0 if there is nothing - to read (i.e. the read returns EWOULDBLOCK or equivalent). On - error closes the socket and raises an exception. - """ - try: - while True: - try: - if self._user_read_buffer: - buf = memoryview(self._read_buffer)[ - self._read_buffer_size : - ] # type: Union[memoryview, bytearray] - else: - buf = bytearray(self.read_chunk_size) - bytes_read = self.read_from_fd(buf) - except (socket.error, IOError, OSError) as e: - # ssl.SSLError is a subclass of socket.error - if self._is_connreset(e): - # Treat ECONNRESET as a connection close rather than - # an error to minimize log spam (the exception will - # be available on self.error for apps that care). - self.close(exc_info=e) - return None - self.close(exc_info=e) - raise - break - if bytes_read is None: - return 0 - elif bytes_read == 0: - self.close() - return 0 - if not self._user_read_buffer: - self._read_buffer += memoryview(buf)[:bytes_read] - self._read_buffer_size += bytes_read - finally: - # Break the reference to buf so we don't waste a chunk's worth of - # memory in case an exception hangs on to our stack frame. - del buf - if self._read_buffer_size > self.max_buffer_size: - gen_log.error("Reached maximum read buffer size") - self.close() - raise StreamBufferFullError("Reached maximum read buffer size") - return bytes_read - - def _read_from_buffer(self, pos: int) -> None: - """Attempts to complete the currently-pending read from the buffer. - - The argument is either a position in the read buffer or None, - as returned by _find_read_pos. - """ - self._read_bytes = self._read_delimiter = self._read_regex = None - self._read_partial = False - self._finish_read(pos, False) - - def _find_read_pos(self) -> Optional[int]: - """Attempts to find a position in the read buffer that satisfies - the currently-pending read. - - Returns a position in the buffer if the current read can be satisfied, - or None if it cannot. - """ - if self._read_bytes is not None and ( - self._read_buffer_size >= self._read_bytes - or (self._read_partial and self._read_buffer_size > 0) - ): - num_bytes = min(self._read_bytes, self._read_buffer_size) - return num_bytes - elif self._read_delimiter is not None: - # Multi-byte delimiters (e.g. '\r\n') may straddle two - # chunks in the read buffer, so we can't easily find them - # without collapsing the buffer. However, since protocols - # using delimited reads (as opposed to reads of a known - # length) tend to be "line" oriented, the delimiter is likely - # to be in the first few chunks. Merge the buffer gradually - # since large merges are relatively expensive and get undone in - # _consume(). - if self._read_buffer: - loc = self._read_buffer.find( - self._read_delimiter, self._read_buffer_pos - ) - if loc != -1: - loc -= self._read_buffer_pos - delimiter_len = len(self._read_delimiter) - self._check_max_bytes(self._read_delimiter, loc + delimiter_len) - return loc + delimiter_len - self._check_max_bytes(self._read_delimiter, self._read_buffer_size) - elif self._read_regex is not None: - if self._read_buffer: - m = self._read_regex.search(self._read_buffer, self._read_buffer_pos) - if m is not None: - loc = m.end() - self._read_buffer_pos - self._check_max_bytes(self._read_regex, loc) - return loc - self._check_max_bytes(self._read_regex, self._read_buffer_size) - return None - - def _check_max_bytes(self, delimiter: Union[bytes, Pattern], size: int) -> None: - if self._read_max_bytes is not None and size > self._read_max_bytes: - raise UnsatisfiableReadError( - "delimiter %r not found within %d bytes" - % (delimiter, self._read_max_bytes) - ) - - def _handle_write(self) -> None: - while True: - size = len(self._write_buffer) - if not size: - break - assert size > 0 - try: - if _WINDOWS: - # On windows, socket.send blows up if given a - # write buffer that's too large, instead of just - # returning the number of bytes it was able to - # process. Therefore we must not call socket.send - # with more than 128KB at a time. - size = 128 * 1024 - - num_bytes = self.write_to_fd(self._write_buffer.peek(size)) - if num_bytes == 0: - break - self._write_buffer.advance(num_bytes) - self._total_write_done_index += num_bytes - except BlockingIOError: - break - except (socket.error, IOError, OSError) as e: - if not self._is_connreset(e): - # Broken pipe errors are usually caused by connection - # reset, and its better to not log EPIPE errors to - # minimize log spam - gen_log.warning("Write error on %s: %s", self.fileno(), e) - self.close(exc_info=e) - return - - while self._write_futures: - index, future = self._write_futures[0] - if index > self._total_write_done_index: - break - self._write_futures.popleft() - future_set_result_unless_cancelled(future, None) - - def _consume(self, loc: int) -> bytes: - # Consume loc bytes from the read buffer and return them - if loc == 0: - return b"" - assert loc <= self._read_buffer_size - # Slice the bytearray buffer into bytes, without intermediate copying - b = ( - memoryview(self._read_buffer)[ - self._read_buffer_pos : self._read_buffer_pos + loc - ] - ).tobytes() - self._read_buffer_pos += loc - self._read_buffer_size -= loc - # Amortized O(1) shrink - # (this heuristic is implemented natively in Python 3.4+ - # but is replicated here for Python 2) - if self._read_buffer_pos > self._read_buffer_size: - del self._read_buffer[: self._read_buffer_pos] - self._read_buffer_pos = 0 - return b - - def _check_closed(self) -> None: - if self.closed(): - raise StreamClosedError(real_error=self.error) - - def _maybe_add_error_listener(self) -> None: - # This method is part of an optimization: to detect a connection that - # is closed when we're not actively reading or writing, we must listen - # for read events. However, it is inefficient to do this when the - # connection is first established because we are going to read or write - # immediately anyway. Instead, we insert checks at various times to - # see if the connection is idle and add the read listener then. - if self._state is None or self._state == ioloop.IOLoop.ERROR: - if ( - not self.closed() - and self._read_buffer_size == 0 - and self._close_callback is not None - ): - self._add_io_state(ioloop.IOLoop.READ) - - def _add_io_state(self, state: int) -> None: - """Adds `state` (IOLoop.{READ,WRITE} flags) to our event handler. - - Implementation notes: Reads and writes have a fast path and a - slow path. The fast path reads synchronously from socket - buffers, while the slow path uses `_add_io_state` to schedule - an IOLoop callback. - - To detect closed connections, we must have called - `_add_io_state` at some point, but we want to delay this as - much as possible so we don't have to set an `IOLoop.ERROR` - listener that will be overwritten by the next slow-path - operation. If a sequence of fast-path ops do not end in a - slow-path op, (e.g. for an @asynchronous long-poll request), - we must add the error handler. - - TODO: reevaluate this now that callbacks are gone. - - """ - if self.closed(): - # connection has been closed, so there can be no future events - return - if self._state is None: - self._state = ioloop.IOLoop.ERROR | state - self.io_loop.add_handler(self.fileno(), self._handle_events, self._state) - elif not self._state & state: - self._state = self._state | state - self.io_loop.update_handler(self.fileno(), self._state) - - def _is_connreset(self, exc: BaseException) -> bool: - """Return ``True`` if exc is ECONNRESET or equivalent. - - May be overridden in subclasses. - """ - return ( - isinstance(exc, (socket.error, IOError)) - and errno_from_exception(exc) in _ERRNO_CONNRESET - ) - - -class IOStream(BaseIOStream): - r"""Socket-based `IOStream` implementation. - - This class supports the read and write methods from `BaseIOStream` - plus a `connect` method. - - The ``socket`` parameter may either be connected or unconnected. - For server operations the socket is the result of calling - `socket.accept `. For client operations the - socket is created with `socket.socket`, and may either be - connected before passing it to the `IOStream` or connected with - `IOStream.connect`. - - A very simple (and broken) HTTP client using this class: - - .. testcode:: - - import tornado.ioloop - import tornado.iostream - import socket - - async def main(): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - stream = tornado.iostream.IOStream(s) - await stream.connect(("friendfeed.com", 80)) - await stream.write(b"GET / HTTP/1.0\r\nHost: friendfeed.com\r\n\r\n") - header_data = await stream.read_until(b"\r\n\r\n") - headers = {} - for line in header_data.split(b"\r\n"): - parts = line.split(b":") - if len(parts) == 2: - headers[parts[0].strip()] = parts[1].strip() - body_data = await stream.read_bytes(int(headers[b"Content-Length"])) - print(body_data) - stream.close() - - if __name__ == '__main__': - tornado.ioloop.IOLoop.current().run_sync(main) - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - stream = tornado.iostream.IOStream(s) - stream.connect(("friendfeed.com", 80), send_request) - tornado.ioloop.IOLoop.current().start() - - .. testoutput:: - :hide: - - """ - - def __init__(self, socket: socket.socket, *args: Any, **kwargs: Any) -> None: - self.socket = socket - self.socket.setblocking(False) - super().__init__(*args, **kwargs) - - def fileno(self) -> Union[int, ioloop._Selectable]: - return self.socket - - def close_fd(self) -> None: - self.socket.close() - self.socket = None # type: ignore - - def get_fd_error(self) -> Optional[Exception]: - errno = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - return socket.error(errno, os.strerror(errno)) - - def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: - try: - return self.socket.recv_into(buf, len(buf)) - except BlockingIOError: - return None - finally: - del buf - - def write_to_fd(self, data: memoryview) -> int: - try: - return self.socket.send(data) # type: ignore - finally: - # Avoid keeping to data, which can be a memoryview. - # See https://github.com/tornadoweb/tornado/pull/2008 - del data - - def connect( - self: _IOStreamType, address: Any, server_hostname: Optional[str] = None - ) -> "Future[_IOStreamType]": - """Connects the socket to a remote address without blocking. - - May only be called if the socket passed to the constructor was - not previously connected. The address parameter is in the - same format as for `socket.connect ` for - the type of socket passed to the IOStream constructor, - e.g. an ``(ip, port)`` tuple. Hostnames are accepted here, - but will be resolved synchronously and block the IOLoop. - If you have a hostname instead of an IP address, the `.TCPClient` - class is recommended instead of calling this method directly. - `.TCPClient` will do asynchronous DNS resolution and handle - both IPv4 and IPv6. - - If ``callback`` is specified, it will be called with no - arguments when the connection is completed; if not this method - returns a `.Future` (whose result after a successful - connection will be the stream itself). - - In SSL mode, the ``server_hostname`` parameter will be used - for certificate validation (unless disabled in the - ``ssl_options``) and SNI (if supported; requires Python - 2.7.9+). - - Note that it is safe to call `IOStream.write - ` while the connection is pending, in - which case the data will be written as soon as the connection - is ready. Calling `IOStream` read methods before the socket is - connected works on some platforms but is non-portable. - - .. versionchanged:: 4.0 - If no callback is given, returns a `.Future`. - - .. versionchanged:: 4.2 - SSL certificates are validated by default; pass - ``ssl_options=dict(cert_reqs=ssl.CERT_NONE)`` or a - suitably-configured `ssl.SSLContext` to the - `SSLIOStream` constructor to disable. - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - `.Future` instead. - - """ - self._connecting = True - future = Future() # type: Future[_IOStreamType] - self._connect_future = typing.cast("Future[IOStream]", future) - try: - self.socket.connect(address) - except BlockingIOError: - # In non-blocking mode we expect connect() to raise an - # exception with EINPROGRESS or EWOULDBLOCK. - pass - except socket.error as e: - # On freebsd, other errors such as ECONNREFUSED may be - # returned immediately when attempting to connect to - # localhost, so handle them the same way as an error - # reported later in _handle_connect. - if future is None: - gen_log.warning("Connect error on fd %s: %s", self.socket.fileno(), e) - self.close(exc_info=e) - return future - self._add_io_state(self.io_loop.WRITE) - return future - - def start_tls( - self, - server_side: bool, - ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None, - server_hostname: Optional[str] = None, - ) -> Awaitable["SSLIOStream"]: - """Convert this `IOStream` to an `SSLIOStream`. - - This enables protocols that begin in clear-text mode and - switch to SSL after some initial negotiation (such as the - ``STARTTLS`` extension to SMTP and IMAP). - - This method cannot be used if there are outstanding reads - or writes on the stream, or if there is any data in the - IOStream's buffer (data in the operating system's socket - buffer is allowed). This means it must generally be used - immediately after reading or writing the last clear-text - data. It can also be used immediately after connecting, - before any reads or writes. - - The ``ssl_options`` argument may be either an `ssl.SSLContext` - object or a dictionary of keyword arguments for the - `ssl.wrap_socket` function. The ``server_hostname`` argument - will be used for certificate validation unless disabled - in the ``ssl_options``. - - This method returns a `.Future` whose result is the new - `SSLIOStream`. After this method has been called, - any other operation on the original stream is undefined. - - If a close callback is defined on this stream, it will be - transferred to the new stream. - - .. versionadded:: 4.0 - - .. versionchanged:: 4.2 - SSL certificates are validated by default; pass - ``ssl_options=dict(cert_reqs=ssl.CERT_NONE)`` or a - suitably-configured `ssl.SSLContext` to disable. - """ - if ( - self._read_future - or self._write_futures - or self._connect_future - or self._closed - or self._read_buffer - or self._write_buffer - ): - raise ValueError("IOStream is not idle; cannot convert to SSL") - if ssl_options is None: - if server_side: - ssl_options = _server_ssl_defaults - else: - ssl_options = _client_ssl_defaults - - socket = self.socket - self.io_loop.remove_handler(socket) - self.socket = None # type: ignore - socket = ssl_wrap_socket( - socket, - ssl_options, - server_hostname=server_hostname, - server_side=server_side, - do_handshake_on_connect=False, - ) - orig_close_callback = self._close_callback - self._close_callback = None - - future = Future() # type: Future[SSLIOStream] - ssl_stream = SSLIOStream(socket, ssl_options=ssl_options) - ssl_stream.set_close_callback(orig_close_callback) - ssl_stream._ssl_connect_future = future - ssl_stream.max_buffer_size = self.max_buffer_size - ssl_stream.read_chunk_size = self.read_chunk_size - return future - - def _handle_connect(self) -> None: - try: - err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - except socket.error as e: - # Hurd doesn't allow SO_ERROR for loopback sockets because all - # errors for such sockets are reported synchronously. - if errno_from_exception(e) == errno.ENOPROTOOPT: - err = 0 - if err != 0: - self.error = socket.error(err, os.strerror(err)) - # IOLoop implementations may vary: some of them return - # an error state before the socket becomes writable, so - # in that case a connection failure would be handled by the - # error path in _handle_events instead of here. - if self._connect_future is None: - gen_log.warning( - "Connect error on fd %s: %s", - self.socket.fileno(), - errno.errorcode[err], - ) - self.close() - return - if self._connect_future is not None: - future = self._connect_future - self._connect_future = None - future_set_result_unless_cancelled(future, self) - self._connecting = False - - def set_nodelay(self, value: bool) -> None: - if self.socket is not None and self.socket.family in ( - socket.AF_INET, - socket.AF_INET6, - ): - try: - self.socket.setsockopt( - socket.IPPROTO_TCP, socket.TCP_NODELAY, 1 if value else 0 - ) - except socket.error as e: - # Sometimes setsockopt will fail if the socket is closed - # at the wrong time. This can happen with HTTPServer - # resetting the value to ``False`` between requests. - if e.errno != errno.EINVAL and not self._is_connreset(e): - raise - - -class SSLIOStream(IOStream): - """A utility class to write to and read from a non-blocking SSL socket. - - If the socket passed to the constructor is already connected, - it should be wrapped with:: - - ssl.wrap_socket(sock, do_handshake_on_connect=False, **kwargs) - - before constructing the `SSLIOStream`. Unconnected sockets will be - wrapped when `IOStream.connect` is finished. - """ - - socket = None # type: ssl.SSLSocket - - def __init__(self, *args: Any, **kwargs: Any) -> None: - """The ``ssl_options`` keyword argument may either be an - `ssl.SSLContext` object or a dictionary of keywords arguments - for `ssl.wrap_socket` - """ - self._ssl_options = kwargs.pop("ssl_options", _client_ssl_defaults) - super().__init__(*args, **kwargs) - self._ssl_accepting = True - self._handshake_reading = False - self._handshake_writing = False - self._server_hostname = None # type: Optional[str] - - # If the socket is already connected, attempt to start the handshake. - try: - self.socket.getpeername() - except socket.error: - pass - else: - # Indirectly start the handshake, which will run on the next - # IOLoop iteration and then the real IO state will be set in - # _handle_events. - self._add_io_state(self.io_loop.WRITE) - - def reading(self) -> bool: - return self._handshake_reading or super().reading() - - def writing(self) -> bool: - return self._handshake_writing or super().writing() - - def _do_ssl_handshake(self) -> None: - # Based on code from test_ssl.py in the python stdlib - try: - self._handshake_reading = False - self._handshake_writing = False - self.socket.do_handshake() - except ssl.SSLError as err: - if err.args[0] == ssl.SSL_ERROR_WANT_READ: - self._handshake_reading = True - return - elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: - self._handshake_writing = True - return - elif err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): - return self.close(exc_info=err) - elif err.args[0] == ssl.SSL_ERROR_SSL: - try: - peer = self.socket.getpeername() - except Exception: - peer = "(not connected)" - gen_log.warning( - "SSL Error on %s %s: %s", self.socket.fileno(), peer, err - ) - return self.close(exc_info=err) - raise - except ssl.CertificateError as err: - # CertificateError can happen during handshake (hostname - # verification) and should be passed to user. Starting - # in Python 3.7, this error is a subclass of SSLError - # and will be handled by the previous block instead. - return self.close(exc_info=err) - except socket.error as err: - # Some port scans (e.g. nmap in -sT mode) have been known - # to cause do_handshake to raise EBADF and ENOTCONN, so make - # those errors quiet as well. - # https://groups.google.com/forum/?fromgroups#!topic/python-tornado/ApucKJat1_0 - # Errno 0 is also possible in some cases (nc -z). - # https://github.com/tornadoweb/tornado/issues/2504 - if self._is_connreset(err) or err.args[0] in ( - 0, - errno.EBADF, - errno.ENOTCONN, - ): - return self.close(exc_info=err) - raise - except AttributeError as err: - # On Linux, if the connection was reset before the call to - # wrap_socket, do_handshake will fail with an - # AttributeError. - return self.close(exc_info=err) - else: - self._ssl_accepting = False - if not self._verify_cert(self.socket.getpeercert()): - self.close() - return - self._finish_ssl_connect() - - def _finish_ssl_connect(self) -> None: - if self._ssl_connect_future is not None: - future = self._ssl_connect_future - self._ssl_connect_future = None - future_set_result_unless_cancelled(future, self) - - def _verify_cert(self, peercert: Any) -> bool: - """Returns ``True`` if peercert is valid according to the configured - validation mode and hostname. - - The ssl handshake already tested the certificate for a valid - CA signature; the only thing that remains is to check - the hostname. - """ - if isinstance(self._ssl_options, dict): - verify_mode = self._ssl_options.get("cert_reqs", ssl.CERT_NONE) - elif isinstance(self._ssl_options, ssl.SSLContext): - verify_mode = self._ssl_options.verify_mode - assert verify_mode in (ssl.CERT_NONE, ssl.CERT_REQUIRED, ssl.CERT_OPTIONAL) - if verify_mode == ssl.CERT_NONE or self._server_hostname is None: - return True - cert = self.socket.getpeercert() - if cert is None and verify_mode == ssl.CERT_REQUIRED: - gen_log.warning("No SSL certificate given") - return False - try: - ssl.match_hostname(peercert, self._server_hostname) - except ssl.CertificateError as e: - gen_log.warning("Invalid SSL certificate: %s" % e) - return False - else: - return True - - def _handle_read(self) -> None: - if self._ssl_accepting: - self._do_ssl_handshake() - return - super()._handle_read() - - def _handle_write(self) -> None: - if self._ssl_accepting: - self._do_ssl_handshake() - return - super()._handle_write() - - def connect( - self, address: Tuple, server_hostname: Optional[str] = None - ) -> "Future[SSLIOStream]": - self._server_hostname = server_hostname - # Ignore the result of connect(). If it fails, - # wait_for_handshake will raise an error too. This is - # necessary for the old semantics of the connect callback - # (which takes no arguments). In 6.0 this can be refactored to - # be a regular coroutine. - # TODO: This is trickier than it looks, since if write() - # is called with a connect() pending, we want the connect - # to resolve before the write. Or do we care about this? - # (There's a test for it, but I think in practice users - # either wait for the connect before performing a write or - # they don't care about the connect Future at all) - fut = super().connect(address) - fut.add_done_callback(lambda f: f.exception()) - return self.wait_for_handshake() - - def _handle_connect(self) -> None: - # Call the superclass method to check for errors. - super()._handle_connect() - if self.closed(): - return - # When the connection is complete, wrap the socket for SSL - # traffic. Note that we do this by overriding _handle_connect - # instead of by passing a callback to super().connect because - # user callbacks are enqueued asynchronously on the IOLoop, - # but since _handle_events calls _handle_connect immediately - # followed by _handle_write we need this to be synchronous. - # - # The IOLoop will get confused if we swap out self.socket while the - # fd is registered, so remove it now and re-register after - # wrap_socket(). - self.io_loop.remove_handler(self.socket) - old_state = self._state - assert old_state is not None - self._state = None - self.socket = ssl_wrap_socket( - self.socket, - self._ssl_options, - server_hostname=self._server_hostname, - do_handshake_on_connect=False, - ) - self._add_io_state(old_state) - - def wait_for_handshake(self) -> "Future[SSLIOStream]": - """Wait for the initial SSL handshake to complete. - - If a ``callback`` is given, it will be called with no - arguments once the handshake is complete; otherwise this - method returns a `.Future` which will resolve to the - stream itself after the handshake is complete. - - Once the handshake is complete, information such as - the peer's certificate and NPN/ALPN selections may be - accessed on ``self.socket``. - - This method is intended for use on server-side streams - or after using `IOStream.start_tls`; it should not be used - with `IOStream.connect` (which already waits for the - handshake to complete). It may only be called once per stream. - - .. versionadded:: 4.2 - - .. versionchanged:: 6.0 - - The ``callback`` argument was removed. Use the returned - `.Future` instead. - - """ - if self._ssl_connect_future is not None: - raise RuntimeError("Already waiting") - future = self._ssl_connect_future = Future() - if not self._ssl_accepting: - self._finish_ssl_connect() - return future - - def write_to_fd(self, data: memoryview) -> int: - try: - return self.socket.send(data) # type: ignore - except ssl.SSLError as e: - if e.args[0] == ssl.SSL_ERROR_WANT_WRITE: - # In Python 3.5+, SSLSocket.send raises a WANT_WRITE error if - # the socket is not writeable; we need to transform this into - # an EWOULDBLOCK socket.error or a zero return value, - # either of which will be recognized by the caller of this - # method. Prior to Python 3.5, an unwriteable socket would - # simply return 0 bytes written. - return 0 - raise - finally: - # Avoid keeping to data, which can be a memoryview. - # See https://github.com/tornadoweb/tornado/pull/2008 - del data - - def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: - try: - if self._ssl_accepting: - # If the handshake hasn't finished yet, there can't be anything - # to read (attempting to read may or may not raise an exception - # depending on the SSL version) - return None - try: - return self.socket.recv_into(buf, len(buf)) - except ssl.SSLError as e: - # SSLError is a subclass of socket.error, so this except - # block must come first. - if e.args[0] == ssl.SSL_ERROR_WANT_READ: - return None - else: - raise - except BlockingIOError: - return None - finally: - del buf - - def _is_connreset(self, e: BaseException) -> bool: - if isinstance(e, ssl.SSLError) and e.args[0] == ssl.SSL_ERROR_EOF: - return True - return super()._is_connreset(e) - - -class PipeIOStream(BaseIOStream): - """Pipe-based `IOStream` implementation. - - The constructor takes an integer file descriptor (such as one returned - by `os.pipe`) rather than an open file object. Pipes are generally - one-way, so a `PipeIOStream` can be used for reading or writing but not - both. - - ``PipeIOStream`` is only available on Unix-based platforms. - """ - - def __init__(self, fd: int, *args: Any, **kwargs: Any) -> None: - self.fd = fd - self._fio = io.FileIO(self.fd, "r+") - os.set_blocking(fd, False) - super().__init__(*args, **kwargs) - - def fileno(self) -> int: - return self.fd - - def close_fd(self) -> None: - self._fio.close() - - def write_to_fd(self, data: memoryview) -> int: - try: - return os.write(self.fd, data) # type: ignore - finally: - # Avoid keeping to data, which can be a memoryview. - # See https://github.com/tornadoweb/tornado/pull/2008 - del data - - def read_from_fd(self, buf: Union[bytearray, memoryview]) -> Optional[int]: - try: - return self._fio.readinto(buf) # type: ignore - except (IOError, OSError) as e: - if errno_from_exception(e) == errno.EBADF: - # If the writing half of a pipe is closed, select will - # report it as readable but reads will fail with EBADF. - self.close(exc_info=e) - return None - else: - raise - finally: - del buf - - -def doctests() -> Any: - import doctest - - return doctest.DocTestSuite() diff --git a/telegramer/include/tornado/locale.py b/telegramer/include/tornado/locale.py deleted file mode 100644 index adb1f77..0000000 --- a/telegramer/include/tornado/locale.py +++ /dev/null @@ -1,581 +0,0 @@ -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Translation methods for generating localized strings. - -To load a locale and generate a translated string:: - - user_locale = tornado.locale.get("es_LA") - print(user_locale.translate("Sign out")) - -`tornado.locale.get()` returns the closest matching locale, not necessarily the -specific locale you requested. You can support pluralization with -additional arguments to `~Locale.translate()`, e.g.:: - - people = [...] - message = user_locale.translate( - "%(list)s is online", "%(list)s are online", len(people)) - print(message % {"list": user_locale.list(people)}) - -The first string is chosen if ``len(people) == 1``, otherwise the second -string is chosen. - -Applications should call one of `load_translations` (which uses a simple -CSV format) or `load_gettext_translations` (which uses the ``.mo`` format -supported by `gettext` and related tools). If neither method is called, -the `Locale.translate` method will simply return the original string. -""" - -import codecs -import csv -import datetime -import gettext -import os -import re - -from tornado import escape -from tornado.log import gen_log - -from tornado._locale_data import LOCALE_NAMES - -from typing import Iterable, Any, Union, Dict, Optional - -_default_locale = "en_US" -_translations = {} # type: Dict[str, Any] -_supported_locales = frozenset([_default_locale]) -_use_gettext = False -CONTEXT_SEPARATOR = "\x04" - - -def get(*locale_codes: str) -> "Locale": - """Returns the closest match for the given locale codes. - - We iterate over all given locale codes in order. If we have a tight - or a loose match for the code (e.g., "en" for "en_US"), we return - the locale. Otherwise we move to the next code in the list. - - By default we return ``en_US`` if no translations are found for any of - the specified locales. You can change the default locale with - `set_default_locale()`. - """ - return Locale.get_closest(*locale_codes) - - -def set_default_locale(code: str) -> None: - """Sets the default locale. - - The default locale is assumed to be the language used for all strings - in the system. The translations loaded from disk are mappings from - the default locale to the destination locale. Consequently, you don't - need to create a translation file for the default locale. - """ - global _default_locale - global _supported_locales - _default_locale = code - _supported_locales = frozenset(list(_translations.keys()) + [_default_locale]) - - -def load_translations(directory: str, encoding: Optional[str] = None) -> None: - """Loads translations from CSV files in a directory. - - Translations are strings with optional Python-style named placeholders - (e.g., ``My name is %(name)s``) and their associated translations. - - The directory should have translation files of the form ``LOCALE.csv``, - e.g. ``es_GT.csv``. The CSV files should have two or three columns: string, - translation, and an optional plural indicator. Plural indicators should - be one of "plural" or "singular". A given string can have both singular - and plural forms. For example ``%(name)s liked this`` may have a - different verb conjugation depending on whether %(name)s is one - name or a list of names. There should be two rows in the CSV file for - that string, one with plural indicator "singular", and one "plural". - For strings with no verbs that would change on translation, simply - use "unknown" or the empty string (or don't include the column at all). - - The file is read using the `csv` module in the default "excel" dialect. - In this format there should not be spaces after the commas. - - If no ``encoding`` parameter is given, the encoding will be - detected automatically (among UTF-8 and UTF-16) if the file - contains a byte-order marker (BOM), defaulting to UTF-8 if no BOM - is present. - - Example translation ``es_LA.csv``:: - - "I love you","Te amo" - "%(name)s liked this","A %(name)s les gustó esto","plural" - "%(name)s liked this","A %(name)s le gustó esto","singular" - - .. versionchanged:: 4.3 - Added ``encoding`` parameter. Added support for BOM-based encoding - detection, UTF-16, and UTF-8-with-BOM. - """ - global _translations - global _supported_locales - _translations = {} - for path in os.listdir(directory): - if not path.endswith(".csv"): - continue - locale, extension = path.split(".") - if not re.match("[a-z]+(_[A-Z]+)?$", locale): - gen_log.error( - "Unrecognized locale %r (path: %s)", - locale, - os.path.join(directory, path), - ) - continue - full_path = os.path.join(directory, path) - if encoding is None: - # Try to autodetect encoding based on the BOM. - with open(full_path, "rb") as bf: - data = bf.read(len(codecs.BOM_UTF16_LE)) - if data in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): - encoding = "utf-16" - else: - # utf-8-sig is "utf-8 with optional BOM". It's discouraged - # in most cases but is common with CSV files because Excel - # cannot read utf-8 files without a BOM. - encoding = "utf-8-sig" - # python 3: csv.reader requires a file open in text mode. - # Specify an encoding to avoid dependence on $LANG environment variable. - with open(full_path, encoding=encoding) as f: - _translations[locale] = {} - for i, row in enumerate(csv.reader(f)): - if not row or len(row) < 2: - continue - row = [escape.to_unicode(c).strip() for c in row] - english, translation = row[:2] - if len(row) > 2: - plural = row[2] or "unknown" - else: - plural = "unknown" - if plural not in ("plural", "singular", "unknown"): - gen_log.error( - "Unrecognized plural indicator %r in %s line %d", - plural, - path, - i + 1, - ) - continue - _translations[locale].setdefault(plural, {})[english] = translation - _supported_locales = frozenset(list(_translations.keys()) + [_default_locale]) - gen_log.debug("Supported locales: %s", sorted(_supported_locales)) - - -def load_gettext_translations(directory: str, domain: str) -> None: - """Loads translations from `gettext`'s locale tree - - Locale tree is similar to system's ``/usr/share/locale``, like:: - - {directory}/{lang}/LC_MESSAGES/{domain}.mo - - Three steps are required to have your app translated: - - 1. Generate POT translation file:: - - xgettext --language=Python --keyword=_:1,2 -d mydomain file1.py file2.html etc - - 2. Merge against existing POT file:: - - msgmerge old.po mydomain.po > new.po - - 3. Compile:: - - msgfmt mydomain.po -o {directory}/pt_BR/LC_MESSAGES/mydomain.mo - """ - global _translations - global _supported_locales - global _use_gettext - _translations = {} - for lang in os.listdir(directory): - if lang.startswith("."): - continue # skip .svn, etc - if os.path.isfile(os.path.join(directory, lang)): - continue - try: - os.stat(os.path.join(directory, lang, "LC_MESSAGES", domain + ".mo")) - _translations[lang] = gettext.translation( - domain, directory, languages=[lang] - ) - except Exception as e: - gen_log.error("Cannot load translation for '%s': %s", lang, str(e)) - continue - _supported_locales = frozenset(list(_translations.keys()) + [_default_locale]) - _use_gettext = True - gen_log.debug("Supported locales: %s", sorted(_supported_locales)) - - -def get_supported_locales() -> Iterable[str]: - """Returns a list of all the supported locale codes.""" - return _supported_locales - - -class Locale(object): - """Object representing a locale. - - After calling one of `load_translations` or `load_gettext_translations`, - call `get` or `get_closest` to get a Locale object. - """ - - _cache = {} # type: Dict[str, Locale] - - @classmethod - def get_closest(cls, *locale_codes: str) -> "Locale": - """Returns the closest match for the given locale code.""" - for code in locale_codes: - if not code: - continue - code = code.replace("-", "_") - parts = code.split("_") - if len(parts) > 2: - continue - elif len(parts) == 2: - code = parts[0].lower() + "_" + parts[1].upper() - if code in _supported_locales: - return cls.get(code) - if parts[0].lower() in _supported_locales: - return cls.get(parts[0].lower()) - return cls.get(_default_locale) - - @classmethod - def get(cls, code: str) -> "Locale": - """Returns the Locale for the given locale code. - - If it is not supported, we raise an exception. - """ - if code not in cls._cache: - assert code in _supported_locales - translations = _translations.get(code, None) - if translations is None: - locale = CSVLocale(code, {}) # type: Locale - elif _use_gettext: - locale = GettextLocale(code, translations) - else: - locale = CSVLocale(code, translations) - cls._cache[code] = locale - return cls._cache[code] - - def __init__(self, code: str) -> None: - self.code = code - self.name = LOCALE_NAMES.get(code, {}).get("name", u"Unknown") - self.rtl = False - for prefix in ["fa", "ar", "he"]: - if self.code.startswith(prefix): - self.rtl = True - break - - # Initialize strings for date formatting - _ = self.translate - self._months = [ - _("January"), - _("February"), - _("March"), - _("April"), - _("May"), - _("June"), - _("July"), - _("August"), - _("September"), - _("October"), - _("November"), - _("December"), - ] - self._weekdays = [ - _("Monday"), - _("Tuesday"), - _("Wednesday"), - _("Thursday"), - _("Friday"), - _("Saturday"), - _("Sunday"), - ] - - def translate( - self, - message: str, - plural_message: Optional[str] = None, - count: Optional[int] = None, - ) -> str: - """Returns the translation for the given message for this locale. - - If ``plural_message`` is given, you must also provide - ``count``. We return ``plural_message`` when ``count != 1``, - and we return the singular form for the given message when - ``count == 1``. - """ - raise NotImplementedError() - - def pgettext( - self, - context: str, - message: str, - plural_message: Optional[str] = None, - count: Optional[int] = None, - ) -> str: - raise NotImplementedError() - - def format_date( - self, - date: Union[int, float, datetime.datetime], - gmt_offset: int = 0, - relative: bool = True, - shorter: bool = False, - full_format: bool = False, - ) -> str: - """Formats the given date (which should be GMT). - - By default, we return a relative time (e.g., "2 minutes ago"). You - can return an absolute date string with ``relative=False``. - - You can force a full format date ("July 10, 1980") with - ``full_format=True``. - - This method is primarily intended for dates in the past. - For dates in the future, we fall back to full format. - """ - if isinstance(date, (int, float)): - date = datetime.datetime.utcfromtimestamp(date) - now = datetime.datetime.utcnow() - if date > now: - if relative and (date - now).seconds < 60: - # Due to click skew, things are some things slightly - # in the future. Round timestamps in the immediate - # future down to now in relative mode. - date = now - else: - # Otherwise, future dates always use the full format. - full_format = True - local_date = date - datetime.timedelta(minutes=gmt_offset) - local_now = now - datetime.timedelta(minutes=gmt_offset) - local_yesterday = local_now - datetime.timedelta(hours=24) - difference = now - date - seconds = difference.seconds - days = difference.days - - _ = self.translate - format = None - if not full_format: - if relative and days == 0: - if seconds < 50: - return _("1 second ago", "%(seconds)d seconds ago", seconds) % { - "seconds": seconds - } - - if seconds < 50 * 60: - minutes = round(seconds / 60.0) - return _("1 minute ago", "%(minutes)d minutes ago", minutes) % { - "minutes": minutes - } - - hours = round(seconds / (60.0 * 60)) - return _("1 hour ago", "%(hours)d hours ago", hours) % {"hours": hours} - - if days == 0: - format = _("%(time)s") - elif days == 1 and local_date.day == local_yesterday.day and relative: - format = _("yesterday") if shorter else _("yesterday at %(time)s") - elif days < 5: - format = _("%(weekday)s") if shorter else _("%(weekday)s at %(time)s") - elif days < 334: # 11mo, since confusing for same month last year - format = ( - _("%(month_name)s %(day)s") - if shorter - else _("%(month_name)s %(day)s at %(time)s") - ) - - if format is None: - format = ( - _("%(month_name)s %(day)s, %(year)s") - if shorter - else _("%(month_name)s %(day)s, %(year)s at %(time)s") - ) - - tfhour_clock = self.code not in ("en", "en_US", "zh_CN") - if tfhour_clock: - str_time = "%d:%02d" % (local_date.hour, local_date.minute) - elif self.code == "zh_CN": - str_time = "%s%d:%02d" % ( - (u"\u4e0a\u5348", u"\u4e0b\u5348")[local_date.hour >= 12], - local_date.hour % 12 or 12, - local_date.minute, - ) - else: - str_time = "%d:%02d %s" % ( - local_date.hour % 12 or 12, - local_date.minute, - ("am", "pm")[local_date.hour >= 12], - ) - - return format % { - "month_name": self._months[local_date.month - 1], - "weekday": self._weekdays[local_date.weekday()], - "day": str(local_date.day), - "year": str(local_date.year), - "time": str_time, - } - - def format_day( - self, date: datetime.datetime, gmt_offset: int = 0, dow: bool = True - ) -> bool: - """Formats the given date as a day of week. - - Example: "Monday, January 22". You can remove the day of week with - ``dow=False``. - """ - local_date = date - datetime.timedelta(minutes=gmt_offset) - _ = self.translate - if dow: - return _("%(weekday)s, %(month_name)s %(day)s") % { - "month_name": self._months[local_date.month - 1], - "weekday": self._weekdays[local_date.weekday()], - "day": str(local_date.day), - } - else: - return _("%(month_name)s %(day)s") % { - "month_name": self._months[local_date.month - 1], - "day": str(local_date.day), - } - - def list(self, parts: Any) -> str: - """Returns a comma-separated list for the given list of parts. - - The format is, e.g., "A, B and C", "A and B" or just "A" for lists - of size 1. - """ - _ = self.translate - if len(parts) == 0: - return "" - if len(parts) == 1: - return parts[0] - comma = u" \u0648 " if self.code.startswith("fa") else u", " - return _("%(commas)s and %(last)s") % { - "commas": comma.join(parts[:-1]), - "last": parts[len(parts) - 1], - } - - def friendly_number(self, value: int) -> str: - """Returns a comma-separated number for the given integer.""" - if self.code not in ("en", "en_US"): - return str(value) - s = str(value) - parts = [] - while s: - parts.append(s[-3:]) - s = s[:-3] - return ",".join(reversed(parts)) - - -class CSVLocale(Locale): - """Locale implementation using tornado's CSV translation format.""" - - def __init__(self, code: str, translations: Dict[str, Dict[str, str]]) -> None: - self.translations = translations - super().__init__(code) - - def translate( - self, - message: str, - plural_message: Optional[str] = None, - count: Optional[int] = None, - ) -> str: - if plural_message is not None: - assert count is not None - if count != 1: - message = plural_message - message_dict = self.translations.get("plural", {}) - else: - message_dict = self.translations.get("singular", {}) - else: - message_dict = self.translations.get("unknown", {}) - return message_dict.get(message, message) - - def pgettext( - self, - context: str, - message: str, - plural_message: Optional[str] = None, - count: Optional[int] = None, - ) -> str: - if self.translations: - gen_log.warning("pgettext is not supported by CSVLocale") - return self.translate(message, plural_message, count) - - -class GettextLocale(Locale): - """Locale implementation using the `gettext` module.""" - - def __init__(self, code: str, translations: gettext.NullTranslations) -> None: - self.ngettext = translations.ngettext - self.gettext = translations.gettext - # self.gettext must exist before __init__ is called, since it - # calls into self.translate - super().__init__(code) - - def translate( - self, - message: str, - plural_message: Optional[str] = None, - count: Optional[int] = None, - ) -> str: - if plural_message is not None: - assert count is not None - return self.ngettext(message, plural_message, count) - else: - return self.gettext(message) - - def pgettext( - self, - context: str, - message: str, - plural_message: Optional[str] = None, - count: Optional[int] = None, - ) -> str: - """Allows to set context for translation, accepts plural forms. - - Usage example:: - - pgettext("law", "right") - pgettext("good", "right") - - Plural message example:: - - pgettext("organization", "club", "clubs", len(clubs)) - pgettext("stick", "club", "clubs", len(clubs)) - - To generate POT file with context, add following options to step 1 - of `load_gettext_translations` sequence:: - - xgettext [basic options] --keyword=pgettext:1c,2 --keyword=pgettext:1c,2,3 - - .. versionadded:: 4.2 - """ - if plural_message is not None: - assert count is not None - msgs_with_ctxt = ( - "%s%s%s" % (context, CONTEXT_SEPARATOR, message), - "%s%s%s" % (context, CONTEXT_SEPARATOR, plural_message), - count, - ) - result = self.ngettext(*msgs_with_ctxt) - if CONTEXT_SEPARATOR in result: - # Translation not found - result = self.ngettext(message, plural_message, count) - return result - else: - msg_with_ctxt = "%s%s%s" % (context, CONTEXT_SEPARATOR, message) - result = self.gettext(msg_with_ctxt) - if CONTEXT_SEPARATOR in result: - # Translation not found - result = message - return result diff --git a/telegramer/include/tornado/locks.py b/telegramer/include/tornado/locks.py deleted file mode 100644 index 0898eba..0000000 --- a/telegramer/include/tornado/locks.py +++ /dev/null @@ -1,571 +0,0 @@ -# Copyright 2015 The Tornado Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import collections -import datetime -import types - -from tornado import gen, ioloop -from tornado.concurrent import Future, future_set_result_unless_cancelled - -from typing import Union, Optional, Type, Any, Awaitable -import typing - -if typing.TYPE_CHECKING: - from typing import Deque, Set # noqa: F401 - -__all__ = ["Condition", "Event", "Semaphore", "BoundedSemaphore", "Lock"] - - -class _TimeoutGarbageCollector(object): - """Base class for objects that periodically clean up timed-out waiters. - - Avoids memory leak in a common pattern like: - - while True: - yield condition.wait(short_timeout) - print('looping....') - """ - - def __init__(self) -> None: - self._waiters = collections.deque() # type: Deque[Future] - self._timeouts = 0 - - def _garbage_collect(self) -> None: - # Occasionally clear timed-out waiters. - self._timeouts += 1 - if self._timeouts > 100: - self._timeouts = 0 - self._waiters = collections.deque(w for w in self._waiters if not w.done()) - - -class Condition(_TimeoutGarbageCollector): - """A condition allows one or more coroutines to wait until notified. - - Like a standard `threading.Condition`, but does not need an underlying lock - that is acquired and released. - - With a `Condition`, coroutines can wait to be notified by other coroutines: - - .. testcode:: - - from tornado import gen - from tornado.ioloop import IOLoop - from tornado.locks import Condition - - condition = Condition() - - async def waiter(): - print("I'll wait right here") - await condition.wait() - print("I'm done waiting") - - async def notifier(): - print("About to notify") - condition.notify() - print("Done notifying") - - async def runner(): - # Wait for waiter() and notifier() in parallel - await gen.multi([waiter(), notifier()]) - - IOLoop.current().run_sync(runner) - - .. testoutput:: - - I'll wait right here - About to notify - Done notifying - I'm done waiting - - `wait` takes an optional ``timeout`` argument, which is either an absolute - timestamp:: - - io_loop = IOLoop.current() - - # Wait up to 1 second for a notification. - await condition.wait(timeout=io_loop.time() + 1) - - ...or a `datetime.timedelta` for a timeout relative to the current time:: - - # Wait up to 1 second. - await condition.wait(timeout=datetime.timedelta(seconds=1)) - - The method returns False if there's no notification before the deadline. - - .. versionchanged:: 5.0 - Previously, waiters could be notified synchronously from within - `notify`. Now, the notification will always be received on the - next iteration of the `.IOLoop`. - """ - - def __init__(self) -> None: - super().__init__() - self.io_loop = ioloop.IOLoop.current() - - def __repr__(self) -> str: - result = "<%s" % (self.__class__.__name__,) - if self._waiters: - result += " waiters[%s]" % len(self._waiters) - return result + ">" - - def wait( - self, timeout: Optional[Union[float, datetime.timedelta]] = None - ) -> Awaitable[bool]: - """Wait for `.notify`. - - Returns a `.Future` that resolves ``True`` if the condition is notified, - or ``False`` after a timeout. - """ - waiter = Future() # type: Future[bool] - self._waiters.append(waiter) - if timeout: - - def on_timeout() -> None: - if not waiter.done(): - future_set_result_unless_cancelled(waiter, False) - self._garbage_collect() - - io_loop = ioloop.IOLoop.current() - timeout_handle = io_loop.add_timeout(timeout, on_timeout) - waiter.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle)) - return waiter - - def notify(self, n: int = 1) -> None: - """Wake ``n`` waiters.""" - waiters = [] # Waiters we plan to run right now. - while n and self._waiters: - waiter = self._waiters.popleft() - if not waiter.done(): # Might have timed out. - n -= 1 - waiters.append(waiter) - - for waiter in waiters: - future_set_result_unless_cancelled(waiter, True) - - def notify_all(self) -> None: - """Wake all waiters.""" - self.notify(len(self._waiters)) - - -class Event(object): - """An event blocks coroutines until its internal flag is set to True. - - Similar to `threading.Event`. - - A coroutine can wait for an event to be set. Once it is set, calls to - ``yield event.wait()`` will not block unless the event has been cleared: - - .. testcode:: - - from tornado import gen - from tornado.ioloop import IOLoop - from tornado.locks import Event - - event = Event() - - async def waiter(): - print("Waiting for event") - await event.wait() - print("Not waiting this time") - await event.wait() - print("Done") - - async def setter(): - print("About to set the event") - event.set() - - async def runner(): - await gen.multi([waiter(), setter()]) - - IOLoop.current().run_sync(runner) - - .. testoutput:: - - Waiting for event - About to set the event - Not waiting this time - Done - """ - - def __init__(self) -> None: - self._value = False - self._waiters = set() # type: Set[Future[None]] - - def __repr__(self) -> str: - return "<%s %s>" % ( - self.__class__.__name__, - "set" if self.is_set() else "clear", - ) - - def is_set(self) -> bool: - """Return ``True`` if the internal flag is true.""" - return self._value - - def set(self) -> None: - """Set the internal flag to ``True``. All waiters are awakened. - - Calling `.wait` once the flag is set will not block. - """ - if not self._value: - self._value = True - - for fut in self._waiters: - if not fut.done(): - fut.set_result(None) - - def clear(self) -> None: - """Reset the internal flag to ``False``. - - Calls to `.wait` will block until `.set` is called. - """ - self._value = False - - def wait( - self, timeout: Optional[Union[float, datetime.timedelta]] = None - ) -> Awaitable[None]: - """Block until the internal flag is true. - - Returns an awaitable, which raises `tornado.util.TimeoutError` after a - timeout. - """ - fut = Future() # type: Future[None] - if self._value: - fut.set_result(None) - return fut - self._waiters.add(fut) - fut.add_done_callback(lambda fut: self._waiters.remove(fut)) - if timeout is None: - return fut - else: - timeout_fut = gen.with_timeout(timeout, fut) - # This is a slightly clumsy workaround for the fact that - # gen.with_timeout doesn't cancel its futures. Cancelling - # fut will remove it from the waiters list. - timeout_fut.add_done_callback( - lambda tf: fut.cancel() if not fut.done() else None - ) - return timeout_fut - - -class _ReleasingContextManager(object): - """Releases a Lock or Semaphore at the end of a "with" statement. - - with (yield semaphore.acquire()): - pass - - # Now semaphore.release() has been called. - """ - - def __init__(self, obj: Any) -> None: - self._obj = obj - - def __enter__(self) -> None: - pass - - def __exit__( - self, - exc_type: "Optional[Type[BaseException]]", - exc_val: Optional[BaseException], - exc_tb: Optional[types.TracebackType], - ) -> None: - self._obj.release() - - -class Semaphore(_TimeoutGarbageCollector): - """A lock that can be acquired a fixed number of times before blocking. - - A Semaphore manages a counter representing the number of `.release` calls - minus the number of `.acquire` calls, plus an initial value. The `.acquire` - method blocks if necessary until it can return without making the counter - negative. - - Semaphores limit access to a shared resource. To allow access for two - workers at a time: - - .. testsetup:: semaphore - - from collections import deque - - from tornado import gen - from tornado.ioloop import IOLoop - from tornado.concurrent import Future - - # Ensure reliable doctest output: resolve Futures one at a time. - futures_q = deque([Future() for _ in range(3)]) - - async def simulator(futures): - for f in futures: - # simulate the asynchronous passage of time - await gen.sleep(0) - await gen.sleep(0) - f.set_result(None) - - IOLoop.current().add_callback(simulator, list(futures_q)) - - def use_some_resource(): - return futures_q.popleft() - - .. testcode:: semaphore - - from tornado import gen - from tornado.ioloop import IOLoop - from tornado.locks import Semaphore - - sem = Semaphore(2) - - async def worker(worker_id): - await sem.acquire() - try: - print("Worker %d is working" % worker_id) - await use_some_resource() - finally: - print("Worker %d is done" % worker_id) - sem.release() - - async def runner(): - # Join all workers. - await gen.multi([worker(i) for i in range(3)]) - - IOLoop.current().run_sync(runner) - - .. testoutput:: semaphore - - Worker 0 is working - Worker 1 is working - Worker 0 is done - Worker 2 is working - Worker 1 is done - Worker 2 is done - - Workers 0 and 1 are allowed to run concurrently, but worker 2 waits until - the semaphore has been released once, by worker 0. - - The semaphore can be used as an async context manager:: - - async def worker(worker_id): - async with sem: - print("Worker %d is working" % worker_id) - await use_some_resource() - - # Now the semaphore has been released. - print("Worker %d is done" % worker_id) - - For compatibility with older versions of Python, `.acquire` is a - context manager, so ``worker`` could also be written as:: - - @gen.coroutine - def worker(worker_id): - with (yield sem.acquire()): - print("Worker %d is working" % worker_id) - yield use_some_resource() - - # Now the semaphore has been released. - print("Worker %d is done" % worker_id) - - .. versionchanged:: 4.3 - Added ``async with`` support in Python 3.5. - - """ - - def __init__(self, value: int = 1) -> None: - super().__init__() - if value < 0: - raise ValueError("semaphore initial value must be >= 0") - - self._value = value - - def __repr__(self) -> str: - res = super().__repr__() - extra = ( - "locked" if self._value == 0 else "unlocked,value:{0}".format(self._value) - ) - if self._waiters: - extra = "{0},waiters:{1}".format(extra, len(self._waiters)) - return "<{0} [{1}]>".format(res[1:-1], extra) - - def release(self) -> None: - """Increment the counter and wake one waiter.""" - self._value += 1 - while self._waiters: - waiter = self._waiters.popleft() - if not waiter.done(): - self._value -= 1 - - # If the waiter is a coroutine paused at - # - # with (yield semaphore.acquire()): - # - # then the context manager's __exit__ calls release() at the end - # of the "with" block. - waiter.set_result(_ReleasingContextManager(self)) - break - - def acquire( - self, timeout: Optional[Union[float, datetime.timedelta]] = None - ) -> Awaitable[_ReleasingContextManager]: - """Decrement the counter. Returns an awaitable. - - Block if the counter is zero and wait for a `.release`. The awaitable - raises `.TimeoutError` after the deadline. - """ - waiter = Future() # type: Future[_ReleasingContextManager] - if self._value > 0: - self._value -= 1 - waiter.set_result(_ReleasingContextManager(self)) - else: - self._waiters.append(waiter) - if timeout: - - def on_timeout() -> None: - if not waiter.done(): - waiter.set_exception(gen.TimeoutError()) - self._garbage_collect() - - io_loop = ioloop.IOLoop.current() - timeout_handle = io_loop.add_timeout(timeout, on_timeout) - waiter.add_done_callback( - lambda _: io_loop.remove_timeout(timeout_handle) - ) - return waiter - - def __enter__(self) -> None: - raise RuntimeError("Use 'async with' instead of 'with' for Semaphore") - - def __exit__( - self, - typ: "Optional[Type[BaseException]]", - value: Optional[BaseException], - traceback: Optional[types.TracebackType], - ) -> None: - self.__enter__() - - async def __aenter__(self) -> None: - await self.acquire() - - async def __aexit__( - self, - typ: "Optional[Type[BaseException]]", - value: Optional[BaseException], - tb: Optional[types.TracebackType], - ) -> None: - self.release() - - -class BoundedSemaphore(Semaphore): - """A semaphore that prevents release() being called too many times. - - If `.release` would increment the semaphore's value past the initial - value, it raises `ValueError`. Semaphores are mostly used to guard - resources with limited capacity, so a semaphore released too many times - is a sign of a bug. - """ - - def __init__(self, value: int = 1) -> None: - super().__init__(value=value) - self._initial_value = value - - def release(self) -> None: - """Increment the counter and wake one waiter.""" - if self._value >= self._initial_value: - raise ValueError("Semaphore released too many times") - super().release() - - -class Lock(object): - """A lock for coroutines. - - A Lock begins unlocked, and `acquire` locks it immediately. While it is - locked, a coroutine that yields `acquire` waits until another coroutine - calls `release`. - - Releasing an unlocked lock raises `RuntimeError`. - - A Lock can be used as an async context manager with the ``async - with`` statement: - - >>> from tornado import locks - >>> lock = locks.Lock() - >>> - >>> async def f(): - ... async with lock: - ... # Do something holding the lock. - ... pass - ... - ... # Now the lock is released. - - For compatibility with older versions of Python, the `.acquire` - method asynchronously returns a regular context manager: - - >>> async def f2(): - ... with (yield lock.acquire()): - ... # Do something holding the lock. - ... pass - ... - ... # Now the lock is released. - - .. versionchanged:: 4.3 - Added ``async with`` support in Python 3.5. - - """ - - def __init__(self) -> None: - self._block = BoundedSemaphore(value=1) - - def __repr__(self) -> str: - return "<%s _block=%s>" % (self.__class__.__name__, self._block) - - def acquire( - self, timeout: Optional[Union[float, datetime.timedelta]] = None - ) -> Awaitable[_ReleasingContextManager]: - """Attempt to lock. Returns an awaitable. - - Returns an awaitable, which raises `tornado.util.TimeoutError` after a - timeout. - """ - return self._block.acquire(timeout) - - def release(self) -> None: - """Unlock. - - The first coroutine in line waiting for `acquire` gets the lock. - - If not locked, raise a `RuntimeError`. - """ - try: - self._block.release() - except ValueError: - raise RuntimeError("release unlocked lock") - - def __enter__(self) -> None: - raise RuntimeError("Use `async with` instead of `with` for Lock") - - def __exit__( - self, - typ: "Optional[Type[BaseException]]", - value: Optional[BaseException], - tb: Optional[types.TracebackType], - ) -> None: - self.__enter__() - - async def __aenter__(self) -> None: - await self.acquire() - - async def __aexit__( - self, - typ: "Optional[Type[BaseException]]", - value: Optional[BaseException], - tb: Optional[types.TracebackType], - ) -> None: - self.release() diff --git a/telegramer/include/tornado/log.py b/telegramer/include/tornado/log.py deleted file mode 100644 index 810a037..0000000 --- a/telegramer/include/tornado/log.py +++ /dev/null @@ -1,339 +0,0 @@ -# -# Copyright 2012 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -"""Logging support for Tornado. - -Tornado uses three logger streams: - -* ``tornado.access``: Per-request logging for Tornado's HTTP servers (and - potentially other servers in the future) -* ``tornado.application``: Logging of errors from application code (i.e. - uncaught exceptions from callbacks) -* ``tornado.general``: General-purpose logging, including any errors - or warnings from Tornado itself. - -These streams may be configured independently using the standard library's -`logging` module. For example, you may wish to send ``tornado.access`` logs -to a separate file for analysis. -""" -import logging -import logging.handlers -import sys - -from tornado.escape import _unicode -from tornado.util import unicode_type, basestring_type - -try: - import colorama # type: ignore -except ImportError: - colorama = None - -try: - import curses -except ImportError: - curses = None # type: ignore - -from typing import Dict, Any, cast, Optional - -# Logger objects for internal tornado use -access_log = logging.getLogger("tornado.access") -app_log = logging.getLogger("tornado.application") -gen_log = logging.getLogger("tornado.general") - - -def _stderr_supports_color() -> bool: - try: - if hasattr(sys.stderr, "isatty") and sys.stderr.isatty(): - if curses: - curses.setupterm() - if curses.tigetnum("colors") > 0: - return True - elif colorama: - if sys.stderr is getattr( - colorama.initialise, "wrapped_stderr", object() - ): - return True - except Exception: - # Very broad exception handling because it's always better to - # fall back to non-colored logs than to break at startup. - pass - return False - - -def _safe_unicode(s: Any) -> str: - try: - return _unicode(s) - except UnicodeDecodeError: - return repr(s) - - -class LogFormatter(logging.Formatter): - """Log formatter used in Tornado. - - Key features of this formatter are: - - * Color support when logging to a terminal that supports it. - * Timestamps on every log line. - * Robust against str/bytes encoding problems. - - This formatter is enabled automatically by - `tornado.options.parse_command_line` or `tornado.options.parse_config_file` - (unless ``--logging=none`` is used). - - Color support on Windows versions that do not support ANSI color codes is - enabled by use of the colorama__ library. Applications that wish to use - this must first initialize colorama with a call to ``colorama.init``. - See the colorama documentation for details. - - __ https://pypi.python.org/pypi/colorama - - .. versionchanged:: 4.5 - Added support for ``colorama``. Changed the constructor - signature to be compatible with `logging.config.dictConfig`. - """ - - DEFAULT_FORMAT = "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s" # noqa: E501 - DEFAULT_DATE_FORMAT = "%y%m%d %H:%M:%S" - DEFAULT_COLORS = { - logging.DEBUG: 4, # Blue - logging.INFO: 2, # Green - logging.WARNING: 3, # Yellow - logging.ERROR: 1, # Red - logging.CRITICAL: 5, # Magenta - } - - def __init__( - self, - fmt: str = DEFAULT_FORMAT, - datefmt: str = DEFAULT_DATE_FORMAT, - style: str = "%", - color: bool = True, - colors: Dict[int, int] = DEFAULT_COLORS, - ) -> None: - r""" - :arg bool color: Enables color support. - :arg str fmt: Log message format. - It will be applied to the attributes dict of log records. The - text between ``%(color)s`` and ``%(end_color)s`` will be colored - depending on the level if color support is on. - :arg dict colors: color mappings from logging level to terminal color - code - :arg str datefmt: Datetime format. - Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``. - - .. versionchanged:: 3.2 - - Added ``fmt`` and ``datefmt`` arguments. - """ - logging.Formatter.__init__(self, datefmt=datefmt) - self._fmt = fmt - - self._colors = {} # type: Dict[int, str] - if color and _stderr_supports_color(): - if curses is not None: - fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or b"" - - for levelno, code in colors.items(): - # Convert the terminal control characters from - # bytes to unicode strings for easier use with the - # logging module. - self._colors[levelno] = unicode_type( - curses.tparm(fg_color, code), "ascii" - ) - self._normal = unicode_type(curses.tigetstr("sgr0"), "ascii") - else: - # If curses is not present (currently we'll only get here for - # colorama on windows), assume hard-coded ANSI color codes. - for levelno, code in colors.items(): - self._colors[levelno] = "\033[2;3%dm" % code - self._normal = "\033[0m" - else: - self._normal = "" - - def format(self, record: Any) -> str: - try: - message = record.getMessage() - assert isinstance(message, basestring_type) # guaranteed by logging - # Encoding notes: The logging module prefers to work with character - # strings, but only enforces that log messages are instances of - # basestring. In python 2, non-ascii bytestrings will make - # their way through the logging framework until they blow up with - # an unhelpful decoding error (with this formatter it happens - # when we attach the prefix, but there are other opportunities for - # exceptions further along in the framework). - # - # If a byte string makes it this far, convert it to unicode to - # ensure it will make it out to the logs. Use repr() as a fallback - # to ensure that all byte strings can be converted successfully, - # but don't do it by default so we don't add extra quotes to ascii - # bytestrings. This is a bit of a hacky place to do this, but - # it's worth it since the encoding errors that would otherwise - # result are so useless (and tornado is fond of using utf8-encoded - # byte strings wherever possible). - record.message = _safe_unicode(message) - except Exception as e: - record.message = "Bad message (%r): %r" % (e, record.__dict__) - - record.asctime = self.formatTime(record, cast(str, self.datefmt)) - - if record.levelno in self._colors: - record.color = self._colors[record.levelno] - record.end_color = self._normal - else: - record.color = record.end_color = "" - - formatted = self._fmt % record.__dict__ - - if record.exc_info: - if not record.exc_text: - record.exc_text = self.formatException(record.exc_info) - if record.exc_text: - # exc_text contains multiple lines. We need to _safe_unicode - # each line separately so that non-utf8 bytes don't cause - # all the newlines to turn into '\n'. - lines = [formatted.rstrip()] - lines.extend(_safe_unicode(ln) for ln in record.exc_text.split("\n")) - formatted = "\n".join(lines) - return formatted.replace("\n", "\n ") - - -def enable_pretty_logging( - options: Any = None, logger: Optional[logging.Logger] = None -) -> None: - """Turns on formatted logging output as configured. - - This is called automatically by `tornado.options.parse_command_line` - and `tornado.options.parse_config_file`. - """ - if options is None: - import tornado.options - - options = tornado.options.options - if options.logging is None or options.logging.lower() == "none": - return - if logger is None: - logger = logging.getLogger() - logger.setLevel(getattr(logging, options.logging.upper())) - if options.log_file_prefix: - rotate_mode = options.log_rotate_mode - if rotate_mode == "size": - channel = logging.handlers.RotatingFileHandler( - filename=options.log_file_prefix, - maxBytes=options.log_file_max_size, - backupCount=options.log_file_num_backups, - encoding="utf-8", - ) # type: logging.Handler - elif rotate_mode == "time": - channel = logging.handlers.TimedRotatingFileHandler( - filename=options.log_file_prefix, - when=options.log_rotate_when, - interval=options.log_rotate_interval, - backupCount=options.log_file_num_backups, - encoding="utf-8", - ) - else: - error_message = ( - "The value of log_rotate_mode option should be " - + '"size" or "time", not "%s".' % rotate_mode - ) - raise ValueError(error_message) - channel.setFormatter(LogFormatter(color=False)) - logger.addHandler(channel) - - if options.log_to_stderr or (options.log_to_stderr is None and not logger.handlers): - # Set up color if we are in a tty and curses is installed - channel = logging.StreamHandler() - channel.setFormatter(LogFormatter()) - logger.addHandler(channel) - - -def define_logging_options(options: Any = None) -> None: - """Add logging-related flags to ``options``. - - These options are present automatically on the default options instance; - this method is only necessary if you have created your own `.OptionParser`. - - .. versionadded:: 4.2 - This function existed in prior versions but was broken and undocumented until 4.2. - """ - if options is None: - # late import to prevent cycle - import tornado.options - - options = tornado.options.options - options.define( - "logging", - default="info", - help=( - "Set the Python log level. If 'none', tornado won't touch the " - "logging configuration." - ), - metavar="debug|info|warning|error|none", - ) - options.define( - "log_to_stderr", - type=bool, - default=None, - help=( - "Send log output to stderr (colorized if possible). " - "By default use stderr if --log_file_prefix is not set and " - "no other logging is configured." - ), - ) - options.define( - "log_file_prefix", - type=str, - default=None, - metavar="PATH", - help=( - "Path prefix for log files. " - "Note that if you are running multiple tornado processes, " - "log_file_prefix must be different for each of them (e.g. " - "include the port number)" - ), - ) - options.define( - "log_file_max_size", - type=int, - default=100 * 1000 * 1000, - help="max size of log files before rollover", - ) - options.define( - "log_file_num_backups", type=int, default=10, help="number of log files to keep" - ) - - options.define( - "log_rotate_when", - type=str, - default="midnight", - help=( - "specify the type of TimedRotatingFileHandler interval " - "other options:('S', 'M', 'H', 'D', 'W0'-'W6')" - ), - ) - options.define( - "log_rotate_interval", - type=int, - default=1, - help="The interval value of timed rotating", - ) - - options.define( - "log_rotate_mode", - type=str, - default="size", - help="The mode of rotating files(time or size)", - ) - - options.add_parse_callback(lambda: enable_pretty_logging(options)) diff --git a/telegramer/include/tornado/netutil.py b/telegramer/include/tornado/netutil.py deleted file mode 100644 index 868d3e9..0000000 --- a/telegramer/include/tornado/netutil.py +++ /dev/null @@ -1,617 +0,0 @@ -# -# Copyright 2011 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Miscellaneous network utility code.""" - -import concurrent.futures -import errno -import os -import sys -import socket -import ssl -import stat - -from tornado.concurrent import dummy_executor, run_on_executor -from tornado.ioloop import IOLoop -from tornado.util import Configurable, errno_from_exception - -from typing import List, Callable, Any, Type, Dict, Union, Tuple, Awaitable, Optional - -# Note that the naming of ssl.Purpose is confusing; the purpose -# of a context is to authentiate the opposite side of the connection. -_client_ssl_defaults = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) -_server_ssl_defaults = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) -if hasattr(ssl, "OP_NO_COMPRESSION"): - # See netutil.ssl_options_to_context - _client_ssl_defaults.options |= ssl.OP_NO_COMPRESSION - _server_ssl_defaults.options |= ssl.OP_NO_COMPRESSION - -# ThreadedResolver runs getaddrinfo on a thread. If the hostname is unicode, -# getaddrinfo attempts to import encodings.idna. If this is done at -# module-import time, the import lock is already held by the main thread, -# leading to deadlock. Avoid it by caching the idna encoder on the main -# thread now. -u"foo".encode("idna") - -# For undiagnosed reasons, 'latin1' codec may also need to be preloaded. -u"foo".encode("latin1") - -# Default backlog used when calling sock.listen() -_DEFAULT_BACKLOG = 128 - - -def bind_sockets( - port: int, - address: Optional[str] = None, - family: socket.AddressFamily = socket.AF_UNSPEC, - backlog: int = _DEFAULT_BACKLOG, - flags: Optional[int] = None, - reuse_port: bool = False, -) -> List[socket.socket]: - """Creates listening sockets bound to the given port and address. - - Returns a list of socket objects (multiple sockets are returned if - the given address maps to multiple IP addresses, which is most common - for mixed IPv4 and IPv6 use). - - Address may be either an IP address or hostname. If it's a hostname, - the server will listen on all IP addresses associated with the - name. Address may be an empty string or None to listen on all - available interfaces. Family may be set to either `socket.AF_INET` - or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise - both will be used if available. - - The ``backlog`` argument has the same meaning as for - `socket.listen() `. - - ``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like - ``socket.AI_PASSIVE | socket.AI_NUMERICHOST``. - - ``reuse_port`` option sets ``SO_REUSEPORT`` option for every socket - in the list. If your platform doesn't support this option ValueError will - be raised. - """ - if reuse_port and not hasattr(socket, "SO_REUSEPORT"): - raise ValueError("the platform doesn't support SO_REUSEPORT") - - sockets = [] - if address == "": - address = None - if not socket.has_ipv6 and family == socket.AF_UNSPEC: - # Python can be compiled with --disable-ipv6, which causes - # operations on AF_INET6 sockets to fail, but does not - # automatically exclude those results from getaddrinfo - # results. - # http://bugs.python.org/issue16208 - family = socket.AF_INET - if flags is None: - flags = socket.AI_PASSIVE - bound_port = None - unique_addresses = set() # type: set - for res in sorted( - socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags), - key=lambda x: x[0], - ): - if res in unique_addresses: - continue - - unique_addresses.add(res) - - af, socktype, proto, canonname, sockaddr = res - if ( - sys.platform == "darwin" - and address == "localhost" - and af == socket.AF_INET6 - and sockaddr[3] != 0 - ): - # Mac OS X includes a link-local address fe80::1%lo0 in the - # getaddrinfo results for 'localhost'. However, the firewall - # doesn't understand that this is a local address and will - # prompt for access (often repeatedly, due to an apparent - # bug in its ability to remember granting access to an - # application). Skip these addresses. - continue - try: - sock = socket.socket(af, socktype, proto) - except socket.error as e: - if errno_from_exception(e) == errno.EAFNOSUPPORT: - continue - raise - if os.name != "nt": - try: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - except socket.error as e: - if errno_from_exception(e) != errno.ENOPROTOOPT: - # Hurd doesn't support SO_REUSEADDR. - raise - if reuse_port: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - if af == socket.AF_INET6: - # On linux, ipv6 sockets accept ipv4 too by default, - # but this makes it impossible to bind to both - # 0.0.0.0 in ipv4 and :: in ipv6. On other systems, - # separate sockets *must* be used to listen for both ipv4 - # and ipv6. For consistency, always disable ipv4 on our - # ipv6 sockets and use a separate ipv4 socket when needed. - # - # Python 2.x on windows doesn't have IPPROTO_IPV6. - if hasattr(socket, "IPPROTO_IPV6"): - sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) - - # automatic port allocation with port=None - # should bind on the same port on IPv4 and IPv6 - host, requested_port = sockaddr[:2] - if requested_port == 0 and bound_port is not None: - sockaddr = tuple([host, bound_port] + list(sockaddr[2:])) - - sock.setblocking(False) - try: - sock.bind(sockaddr) - except OSError as e: - if ( - errno_from_exception(e) == errno.EADDRNOTAVAIL - and address == "localhost" - and sockaddr[0] == "::1" - ): - # On some systems (most notably docker with default - # configurations), ipv6 is partially disabled: - # socket.has_ipv6 is true, we can create AF_INET6 - # sockets, and getaddrinfo("localhost", ..., - # AF_PASSIVE) resolves to ::1, but we get an error - # when binding. - # - # Swallow the error, but only for this specific case. - # If EADDRNOTAVAIL occurs in other situations, it - # might be a real problem like a typo in a - # configuration. - sock.close() - continue - else: - raise - bound_port = sock.getsockname()[1] - sock.listen(backlog) - sockets.append(sock) - return sockets - - -if hasattr(socket, "AF_UNIX"): - - def bind_unix_socket( - file: str, mode: int = 0o600, backlog: int = _DEFAULT_BACKLOG - ) -> socket.socket: - """Creates a listening unix socket. - - If a socket with the given name already exists, it will be deleted. - If any other file with that name exists, an exception will be - raised. - - Returns a socket object (not a list of socket objects like - `bind_sockets`) - """ - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - try: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - except socket.error as e: - if errno_from_exception(e) != errno.ENOPROTOOPT: - # Hurd doesn't support SO_REUSEADDR - raise - sock.setblocking(False) - try: - st = os.stat(file) - except FileNotFoundError: - pass - else: - if stat.S_ISSOCK(st.st_mode): - os.remove(file) - else: - raise ValueError("File %s exists and is not a socket", file) - sock.bind(file) - os.chmod(file, mode) - sock.listen(backlog) - return sock - - -def add_accept_handler( - sock: socket.socket, callback: Callable[[socket.socket, Any], None] -) -> Callable[[], None]: - """Adds an `.IOLoop` event handler to accept new connections on ``sock``. - - When a connection is accepted, ``callback(connection, address)`` will - be run (``connection`` is a socket object, and ``address`` is the - address of the other end of the connection). Note that this signature - is different from the ``callback(fd, events)`` signature used for - `.IOLoop` handlers. - - A callable is returned which, when called, will remove the `.IOLoop` - event handler and stop processing further incoming connections. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - - .. versionchanged:: 5.0 - A callable is returned (``None`` was returned before). - """ - io_loop = IOLoop.current() - removed = [False] - - def accept_handler(fd: socket.socket, events: int) -> None: - # More connections may come in while we're handling callbacks; - # to prevent starvation of other tasks we must limit the number - # of connections we accept at a time. Ideally we would accept - # up to the number of connections that were waiting when we - # entered this method, but this information is not available - # (and rearranging this method to call accept() as many times - # as possible before running any callbacks would have adverse - # effects on load balancing in multiprocess configurations). - # Instead, we use the (default) listen backlog as a rough - # heuristic for the number of connections we can reasonably - # accept at once. - for i in range(_DEFAULT_BACKLOG): - if removed[0]: - # The socket was probably closed - return - try: - connection, address = sock.accept() - except BlockingIOError: - # EWOULDBLOCK indicates we have accepted every - # connection that is available. - return - except ConnectionAbortedError: - # ECONNABORTED indicates that there was a connection - # but it was closed while still in the accept queue. - # (observed on FreeBSD). - continue - callback(connection, address) - - def remove_handler() -> None: - io_loop.remove_handler(sock) - removed[0] = True - - io_loop.add_handler(sock, accept_handler, IOLoop.READ) - return remove_handler - - -def is_valid_ip(ip: str) -> bool: - """Returns ``True`` if the given string is a well-formed IP address. - - Supports IPv4 and IPv6. - """ - if not ip or "\x00" in ip: - # getaddrinfo resolves empty strings to localhost, and truncates - # on zero bytes. - return False - try: - res = socket.getaddrinfo( - ip, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_NUMERICHOST - ) - return bool(res) - except socket.gaierror as e: - if e.args[0] == socket.EAI_NONAME: - return False - raise - return True - - -class Resolver(Configurable): - """Configurable asynchronous DNS resolver interface. - - By default, a blocking implementation is used (which simply calls - `socket.getaddrinfo`). An alternative implementation can be - chosen with the `Resolver.configure <.Configurable.configure>` - class method:: - - Resolver.configure('tornado.netutil.ThreadedResolver') - - The implementations of this interface included with Tornado are - - * `tornado.netutil.DefaultExecutorResolver` - * `tornado.netutil.BlockingResolver` (deprecated) - * `tornado.netutil.ThreadedResolver` (deprecated) - * `tornado.netutil.OverrideResolver` - * `tornado.platform.twisted.TwistedResolver` - * `tornado.platform.caresresolver.CaresResolver` - - .. versionchanged:: 5.0 - The default implementation has changed from `BlockingResolver` to - `DefaultExecutorResolver`. - """ - - @classmethod - def configurable_base(cls) -> Type["Resolver"]: - return Resolver - - @classmethod - def configurable_default(cls) -> Type["Resolver"]: - return DefaultExecutorResolver - - def resolve( - self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC - ) -> Awaitable[List[Tuple[int, Any]]]: - """Resolves an address. - - The ``host`` argument is a string which may be a hostname or a - literal IP address. - - Returns a `.Future` whose result is a list of (family, - address) pairs, where address is a tuple suitable to pass to - `socket.connect ` (i.e. a ``(host, - port)`` pair for IPv4; additional fields may be present for - IPv6). If a ``callback`` is passed, it will be run with the - result as an argument when it is complete. - - :raises IOError: if the address cannot be resolved. - - .. versionchanged:: 4.4 - Standardized all implementations to raise `IOError`. - - .. versionchanged:: 6.0 The ``callback`` argument was removed. - Use the returned awaitable object instead. - - """ - raise NotImplementedError() - - def close(self) -> None: - """Closes the `Resolver`, freeing any resources used. - - .. versionadded:: 3.1 - - """ - pass - - -def _resolve_addr( - host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC -) -> List[Tuple[int, Any]]: - # On Solaris, getaddrinfo fails if the given port is not found - # in /etc/services and no socket type is given, so we must pass - # one here. The socket type used here doesn't seem to actually - # matter (we discard the one we get back in the results), - # so the addresses we return should still be usable with SOCK_DGRAM. - addrinfo = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM) - results = [] - for fam, socktype, proto, canonname, address in addrinfo: - results.append((fam, address)) - return results # type: ignore - - -class DefaultExecutorResolver(Resolver): - """Resolver implementation using `.IOLoop.run_in_executor`. - - .. versionadded:: 5.0 - """ - - async def resolve( - self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC - ) -> List[Tuple[int, Any]]: - result = await IOLoop.current().run_in_executor( - None, _resolve_addr, host, port, family - ) - return result - - -class ExecutorResolver(Resolver): - """Resolver implementation using a `concurrent.futures.Executor`. - - Use this instead of `ThreadedResolver` when you require additional - control over the executor being used. - - The executor will be shut down when the resolver is closed unless - ``close_resolver=False``; use this if you want to reuse the same - executor elsewhere. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - - .. deprecated:: 5.0 - The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead - of this class. - """ - - def initialize( - self, - executor: Optional[concurrent.futures.Executor] = None, - close_executor: bool = True, - ) -> None: - self.io_loop = IOLoop.current() - if executor is not None: - self.executor = executor - self.close_executor = close_executor - else: - self.executor = dummy_executor - self.close_executor = False - - def close(self) -> None: - if self.close_executor: - self.executor.shutdown() - self.executor = None # type: ignore - - @run_on_executor - def resolve( - self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC - ) -> List[Tuple[int, Any]]: - return _resolve_addr(host, port, family) - - -class BlockingResolver(ExecutorResolver): - """Default `Resolver` implementation, using `socket.getaddrinfo`. - - The `.IOLoop` will be blocked during the resolution, although the - callback will not be run until the next `.IOLoop` iteration. - - .. deprecated:: 5.0 - The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead - of this class. - """ - - def initialize(self) -> None: # type: ignore - super().initialize() - - -class ThreadedResolver(ExecutorResolver): - """Multithreaded non-blocking `Resolver` implementation. - - Requires the `concurrent.futures` package to be installed - (available in the standard library since Python 3.2, - installable with ``pip install futures`` in older versions). - - The thread pool size can be configured with:: - - Resolver.configure('tornado.netutil.ThreadedResolver', - num_threads=10) - - .. versionchanged:: 3.1 - All ``ThreadedResolvers`` share a single thread pool, whose - size is set by the first one to be created. - - .. deprecated:: 5.0 - The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead - of this class. - """ - - _threadpool = None # type: ignore - _threadpool_pid = None # type: int - - def initialize(self, num_threads: int = 10) -> None: # type: ignore - threadpool = ThreadedResolver._create_threadpool(num_threads) - super().initialize(executor=threadpool, close_executor=False) - - @classmethod - def _create_threadpool( - cls, num_threads: int - ) -> concurrent.futures.ThreadPoolExecutor: - pid = os.getpid() - if cls._threadpool_pid != pid: - # Threads cannot survive after a fork, so if our pid isn't what it - # was when we created the pool then delete it. - cls._threadpool = None - if cls._threadpool is None: - cls._threadpool = concurrent.futures.ThreadPoolExecutor(num_threads) - cls._threadpool_pid = pid - return cls._threadpool - - -class OverrideResolver(Resolver): - """Wraps a resolver with a mapping of overrides. - - This can be used to make local DNS changes (e.g. for testing) - without modifying system-wide settings. - - The mapping can be in three formats:: - - { - # Hostname to host or ip - "example.com": "127.0.1.1", - - # Host+port to host+port - ("login.example.com", 443): ("localhost", 1443), - - # Host+port+address family to host+port - ("login.example.com", 443, socket.AF_INET6): ("::1", 1443), - } - - .. versionchanged:: 5.0 - Added support for host-port-family triplets. - """ - - def initialize(self, resolver: Resolver, mapping: dict) -> None: - self.resolver = resolver - self.mapping = mapping - - def close(self) -> None: - self.resolver.close() - - def resolve( - self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC - ) -> Awaitable[List[Tuple[int, Any]]]: - if (host, port, family) in self.mapping: - host, port = self.mapping[(host, port, family)] - elif (host, port) in self.mapping: - host, port = self.mapping[(host, port)] - elif host in self.mapping: - host = self.mapping[host] - return self.resolver.resolve(host, port, family) - - -# These are the keyword arguments to ssl.wrap_socket that must be translated -# to their SSLContext equivalents (the other arguments are still passed -# to SSLContext.wrap_socket). -_SSL_CONTEXT_KEYWORDS = frozenset( - ["ssl_version", "certfile", "keyfile", "cert_reqs", "ca_certs", "ciphers"] -) - - -def ssl_options_to_context( - ssl_options: Union[Dict[str, Any], ssl.SSLContext] -) -> ssl.SSLContext: - """Try to convert an ``ssl_options`` dictionary to an - `~ssl.SSLContext` object. - - The ``ssl_options`` dictionary contains keywords to be passed to - `ssl.wrap_socket`. In Python 2.7.9+, `ssl.SSLContext` objects can - be used instead. This function converts the dict form to its - `~ssl.SSLContext` equivalent, and may be used when a component which - accepts both forms needs to upgrade to the `~ssl.SSLContext` version - to use features like SNI or NPN. - """ - if isinstance(ssl_options, ssl.SSLContext): - return ssl_options - assert isinstance(ssl_options, dict) - assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options - # Can't use create_default_context since this interface doesn't - # tell us client vs server. - context = ssl.SSLContext(ssl_options.get("ssl_version", ssl.PROTOCOL_SSLv23)) - if "certfile" in ssl_options: - context.load_cert_chain( - ssl_options["certfile"], ssl_options.get("keyfile", None) - ) - if "cert_reqs" in ssl_options: - context.verify_mode = ssl_options["cert_reqs"] - if "ca_certs" in ssl_options: - context.load_verify_locations(ssl_options["ca_certs"]) - if "ciphers" in ssl_options: - context.set_ciphers(ssl_options["ciphers"]) - if hasattr(ssl, "OP_NO_COMPRESSION"): - # Disable TLS compression to avoid CRIME and related attacks. - # This constant depends on openssl version 1.0. - # TODO: Do we need to do this ourselves or can we trust - # the defaults? - context.options |= ssl.OP_NO_COMPRESSION - return context - - -def ssl_wrap_socket( - socket: socket.socket, - ssl_options: Union[Dict[str, Any], ssl.SSLContext], - server_hostname: Optional[str] = None, - **kwargs: Any -) -> ssl.SSLSocket: - """Returns an ``ssl.SSLSocket`` wrapping the given socket. - - ``ssl_options`` may be either an `ssl.SSLContext` object or a - dictionary (as accepted by `ssl_options_to_context`). Additional - keyword arguments are passed to ``wrap_socket`` (either the - `~ssl.SSLContext` method or the `ssl` module function as - appropriate). - """ - context = ssl_options_to_context(ssl_options) - if ssl.HAS_SNI: - # In python 3.4, wrap_socket only accepts the server_hostname - # argument if HAS_SNI is true. - # TODO: add a unittest (python added server-side SNI support in 3.4) - # In the meantime it can be manually tested with - # python3 -m tornado.httpclient https://sni.velox.ch - return context.wrap_socket(socket, server_hostname=server_hostname, **kwargs) - else: - return context.wrap_socket(socket, **kwargs) diff --git a/telegramer/include/tornado/options.py b/telegramer/include/tornado/options.py deleted file mode 100644 index f0b89a9..0000000 --- a/telegramer/include/tornado/options.py +++ /dev/null @@ -1,735 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A command line parsing module that lets modules define their own options. - -This module is inspired by Google's `gflags -`_. The primary difference -with libraries such as `argparse` is that a global registry is used so -that options may be defined in any module (it also enables -`tornado.log` by default). The rest of Tornado does not depend on this -module, so feel free to use `argparse` or other configuration -libraries if you prefer them. - -Options must be defined with `tornado.options.define` before use, -generally at the top level of a module. The options are then -accessible as attributes of `tornado.options.options`:: - - # myapp/db.py - from tornado.options import define, options - - define("mysql_host", default="127.0.0.1:3306", help="Main user DB") - define("memcache_hosts", default="127.0.0.1:11011", multiple=True, - help="Main user memcache servers") - - def connect(): - db = database.Connection(options.mysql_host) - ... - - # myapp/server.py - from tornado.options import define, options - - define("port", default=8080, help="port to listen on") - - def start_server(): - app = make_app() - app.listen(options.port) - -The ``main()`` method of your application does not need to be aware of all of -the options used throughout your program; they are all automatically loaded -when the modules are loaded. However, all modules that define options -must have been imported before the command line is parsed. - -Your ``main()`` method can parse the command line or parse a config file with -either `parse_command_line` or `parse_config_file`:: - - import myapp.db, myapp.server - import tornado.options - - if __name__ == '__main__': - tornado.options.parse_command_line() - # or - tornado.options.parse_config_file("/etc/server.conf") - -.. note:: - - When using multiple ``parse_*`` functions, pass ``final=False`` to all - but the last one, or side effects may occur twice (in particular, - this can result in log messages being doubled). - -`tornado.options.options` is a singleton instance of `OptionParser`, and -the top-level functions in this module (`define`, `parse_command_line`, etc) -simply call methods on it. You may create additional `OptionParser` -instances to define isolated sets of options, such as for subcommands. - -.. note:: - - By default, several options are defined that will configure the - standard `logging` module when `parse_command_line` or `parse_config_file` - are called. If you want Tornado to leave the logging configuration - alone so you can manage it yourself, either pass ``--logging=none`` - on the command line or do the following to disable it in code:: - - from tornado.options import options, parse_command_line - options.logging = None - parse_command_line() - -.. versionchanged:: 4.3 - Dashes and underscores are fully interchangeable in option names; - options can be defined, set, and read with any mix of the two. - Dashes are typical for command-line usage while config files require - underscores. -""" - -import datetime -import numbers -import re -import sys -import os -import textwrap - -from tornado.escape import _unicode, native_str -from tornado.log import define_logging_options -from tornado.util import basestring_type, exec_in - -from typing import ( - Any, - Iterator, - Iterable, - Tuple, - Set, - Dict, - Callable, - List, - TextIO, - Optional, -) - - -class Error(Exception): - """Exception raised by errors in the options module.""" - - pass - - -class OptionParser(object): - """A collection of options, a dictionary with object-like access. - - Normally accessed via static functions in the `tornado.options` module, - which reference a global instance. - """ - - def __init__(self) -> None: - # we have to use self.__dict__ because we override setattr. - self.__dict__["_options"] = {} - self.__dict__["_parse_callbacks"] = [] - self.define( - "help", - type=bool, - help="show this help information", - callback=self._help_callback, - ) - - def _normalize_name(self, name: str) -> str: - return name.replace("_", "-") - - def __getattr__(self, name: str) -> Any: - name = self._normalize_name(name) - if isinstance(self._options.get(name), _Option): - return self._options[name].value() - raise AttributeError("Unrecognized option %r" % name) - - def __setattr__(self, name: str, value: Any) -> None: - name = self._normalize_name(name) - if isinstance(self._options.get(name), _Option): - return self._options[name].set(value) - raise AttributeError("Unrecognized option %r" % name) - - def __iter__(self) -> Iterator: - return (opt.name for opt in self._options.values()) - - def __contains__(self, name: str) -> bool: - name = self._normalize_name(name) - return name in self._options - - def __getitem__(self, name: str) -> Any: - return self.__getattr__(name) - - def __setitem__(self, name: str, value: Any) -> None: - return self.__setattr__(name, value) - - def items(self) -> Iterable[Tuple[str, Any]]: - """An iterable of (name, value) pairs. - - .. versionadded:: 3.1 - """ - return [(opt.name, opt.value()) for name, opt in self._options.items()] - - def groups(self) -> Set[str]: - """The set of option-groups created by ``define``. - - .. versionadded:: 3.1 - """ - return set(opt.group_name for opt in self._options.values()) - - def group_dict(self, group: str) -> Dict[str, Any]: - """The names and values of options in a group. - - Useful for copying options into Application settings:: - - from tornado.options import define, parse_command_line, options - - define('template_path', group='application') - define('static_path', group='application') - - parse_command_line() - - application = Application( - handlers, **options.group_dict('application')) - - .. versionadded:: 3.1 - """ - return dict( - (opt.name, opt.value()) - for name, opt in self._options.items() - if not group or group == opt.group_name - ) - - def as_dict(self) -> Dict[str, Any]: - """The names and values of all options. - - .. versionadded:: 3.1 - """ - return dict((opt.name, opt.value()) for name, opt in self._options.items()) - - def define( - self, - name: str, - default: Any = None, - type: Optional[type] = None, - help: Optional[str] = None, - metavar: Optional[str] = None, - multiple: bool = False, - group: Optional[str] = None, - callback: Optional[Callable[[Any], None]] = None, - ) -> None: - """Defines a new command line option. - - ``type`` can be any of `str`, `int`, `float`, `bool`, - `~datetime.datetime`, or `~datetime.timedelta`. If no ``type`` - is given but a ``default`` is, ``type`` is the type of - ``default``. Otherwise, ``type`` defaults to `str`. - - If ``multiple`` is True, the option value is a list of ``type`` - instead of an instance of ``type``. - - ``help`` and ``metavar`` are used to construct the - automatically generated command line help string. The help - message is formatted like:: - - --name=METAVAR help string - - ``group`` is used to group the defined options in logical - groups. By default, command line options are grouped by the - file in which they are defined. - - Command line option names must be unique globally. - - If a ``callback`` is given, it will be run with the new value whenever - the option is changed. This can be used to combine command-line - and file-based options:: - - define("config", type=str, help="path to config file", - callback=lambda path: parse_config_file(path, final=False)) - - With this definition, options in the file specified by ``--config`` will - override options set earlier on the command line, but can be overridden - by later flags. - - """ - normalized = self._normalize_name(name) - if normalized in self._options: - raise Error( - "Option %r already defined in %s" - % (normalized, self._options[normalized].file_name) - ) - frame = sys._getframe(0) - options_file = frame.f_code.co_filename - - # Can be called directly, or through top level define() fn, in which - # case, step up above that frame to look for real caller. - if ( - frame.f_back.f_code.co_filename == options_file - and frame.f_back.f_code.co_name == "define" - ): - frame = frame.f_back - - file_name = frame.f_back.f_code.co_filename - if file_name == options_file: - file_name = "" - if type is None: - if not multiple and default is not None: - type = default.__class__ - else: - type = str - if group: - group_name = group # type: Optional[str] - else: - group_name = file_name - option = _Option( - name, - file_name=file_name, - default=default, - type=type, - help=help, - metavar=metavar, - multiple=multiple, - group_name=group_name, - callback=callback, - ) - self._options[normalized] = option - - def parse_command_line( - self, args: Optional[List[str]] = None, final: bool = True - ) -> List[str]: - """Parses all options given on the command line (defaults to - `sys.argv`). - - Options look like ``--option=value`` and are parsed according - to their ``type``. For boolean options, ``--option`` is - equivalent to ``--option=true`` - - If the option has ``multiple=True``, comma-separated values - are accepted. For multi-value integer options, the syntax - ``x:y`` is also accepted and equivalent to ``range(x, y)``. - - Note that ``args[0]`` is ignored since it is the program name - in `sys.argv`. - - We return a list of all arguments that are not parsed as options. - - If ``final`` is ``False``, parse callbacks will not be run. - This is useful for applications that wish to combine configurations - from multiple sources. - - """ - if args is None: - args = sys.argv - remaining = [] # type: List[str] - for i in range(1, len(args)): - # All things after the last option are command line arguments - if not args[i].startswith("-"): - remaining = args[i:] - break - if args[i] == "--": - remaining = args[i + 1 :] - break - arg = args[i].lstrip("-") - name, equals, value = arg.partition("=") - name = self._normalize_name(name) - if name not in self._options: - self.print_help() - raise Error("Unrecognized command line option: %r" % name) - option = self._options[name] - if not equals: - if option.type == bool: - value = "true" - else: - raise Error("Option %r requires a value" % name) - option.parse(value) - - if final: - self.run_parse_callbacks() - - return remaining - - def parse_config_file(self, path: str, final: bool = True) -> None: - """Parses and loads the config file at the given path. - - The config file contains Python code that will be executed (so - it is **not safe** to use untrusted config files). Anything in - the global namespace that matches a defined option will be - used to set that option's value. - - Options may either be the specified type for the option or - strings (in which case they will be parsed the same way as in - `.parse_command_line`) - - Example (using the options defined in the top-level docs of - this module):: - - port = 80 - mysql_host = 'mydb.example.com:3306' - # Both lists and comma-separated strings are allowed for - # multiple=True. - memcache_hosts = ['cache1.example.com:11011', - 'cache2.example.com:11011'] - memcache_hosts = 'cache1.example.com:11011,cache2.example.com:11011' - - If ``final`` is ``False``, parse callbacks will not be run. - This is useful for applications that wish to combine configurations - from multiple sources. - - .. note:: - - `tornado.options` is primarily a command-line library. - Config file support is provided for applications that wish - to use it, but applications that prefer config files may - wish to look at other libraries instead. - - .. versionchanged:: 4.1 - Config files are now always interpreted as utf-8 instead of - the system default encoding. - - .. versionchanged:: 4.4 - The special variable ``__file__`` is available inside config - files, specifying the absolute path to the config file itself. - - .. versionchanged:: 5.1 - Added the ability to set options via strings in config files. - - """ - config = {"__file__": os.path.abspath(path)} - with open(path, "rb") as f: - exec_in(native_str(f.read()), config, config) - for name in config: - normalized = self._normalize_name(name) - if normalized in self._options: - option = self._options[normalized] - if option.multiple: - if not isinstance(config[name], (list, str)): - raise Error( - "Option %r is required to be a list of %s " - "or a comma-separated string" - % (option.name, option.type.__name__) - ) - - if type(config[name]) == str and option.type != str: - option.parse(config[name]) - else: - option.set(config[name]) - - if final: - self.run_parse_callbacks() - - def print_help(self, file: Optional[TextIO] = None) -> None: - """Prints all the command line options to stderr (or another file).""" - if file is None: - file = sys.stderr - print("Usage: %s [OPTIONS]" % sys.argv[0], file=file) - print("\nOptions:\n", file=file) - by_group = {} # type: Dict[str, List[_Option]] - for option in self._options.values(): - by_group.setdefault(option.group_name, []).append(option) - - for filename, o in sorted(by_group.items()): - if filename: - print("\n%s options:\n" % os.path.normpath(filename), file=file) - o.sort(key=lambda option: option.name) - for option in o: - # Always print names with dashes in a CLI context. - prefix = self._normalize_name(option.name) - if option.metavar: - prefix += "=" + option.metavar - description = option.help or "" - if option.default is not None and option.default != "": - description += " (default %s)" % option.default - lines = textwrap.wrap(description, 79 - 35) - if len(prefix) > 30 or len(lines) == 0: - lines.insert(0, "") - print(" --%-30s %s" % (prefix, lines[0]), file=file) - for line in lines[1:]: - print("%-34s %s" % (" ", line), file=file) - print(file=file) - - def _help_callback(self, value: bool) -> None: - if value: - self.print_help() - sys.exit(0) - - def add_parse_callback(self, callback: Callable[[], None]) -> None: - """Adds a parse callback, to be invoked when option parsing is done.""" - self._parse_callbacks.append(callback) - - def run_parse_callbacks(self) -> None: - for callback in self._parse_callbacks: - callback() - - def mockable(self) -> "_Mockable": - """Returns a wrapper around self that is compatible with - `mock.patch `. - - The `mock.patch ` function (included in - the standard library `unittest.mock` package since Python 3.3, - or in the third-party ``mock`` package for older versions of - Python) is incompatible with objects like ``options`` that - override ``__getattr__`` and ``__setattr__``. This function - returns an object that can be used with `mock.patch.object - ` to modify option values:: - - with mock.patch.object(options.mockable(), 'name', value): - assert options.name == value - """ - return _Mockable(self) - - -class _Mockable(object): - """`mock.patch` compatible wrapper for `OptionParser`. - - As of ``mock`` version 1.0.1, when an object uses ``__getattr__`` - hooks instead of ``__dict__``, ``patch.__exit__`` tries to delete - the attribute it set instead of setting a new one (assuming that - the object does not capture ``__setattr__``, so the patch - created a new attribute in ``__dict__``). - - _Mockable's getattr and setattr pass through to the underlying - OptionParser, and delattr undoes the effect of a previous setattr. - """ - - def __init__(self, options: OptionParser) -> None: - # Modify __dict__ directly to bypass __setattr__ - self.__dict__["_options"] = options - self.__dict__["_originals"] = {} - - def __getattr__(self, name: str) -> Any: - return getattr(self._options, name) - - def __setattr__(self, name: str, value: Any) -> None: - assert name not in self._originals, "don't reuse mockable objects" - self._originals[name] = getattr(self._options, name) - setattr(self._options, name, value) - - def __delattr__(self, name: str) -> None: - setattr(self._options, name, self._originals.pop(name)) - - -class _Option(object): - # This class could almost be made generic, but the way the types - # interact with the multiple argument makes this tricky. (default - # and the callback use List[T], but type is still Type[T]). - UNSET = object() - - def __init__( - self, - name: str, - default: Any = None, - type: Optional[type] = None, - help: Optional[str] = None, - metavar: Optional[str] = None, - multiple: bool = False, - file_name: Optional[str] = None, - group_name: Optional[str] = None, - callback: Optional[Callable[[Any], None]] = None, - ) -> None: - if default is None and multiple: - default = [] - self.name = name - if type is None: - raise ValueError("type must not be None") - self.type = type - self.help = help - self.metavar = metavar - self.multiple = multiple - self.file_name = file_name - self.group_name = group_name - self.callback = callback - self.default = default - self._value = _Option.UNSET # type: Any - - def value(self) -> Any: - return self.default if self._value is _Option.UNSET else self._value - - def parse(self, value: str) -> Any: - _parse = { - datetime.datetime: self._parse_datetime, - datetime.timedelta: self._parse_timedelta, - bool: self._parse_bool, - basestring_type: self._parse_string, - }.get( - self.type, self.type - ) # type: Callable[[str], Any] - if self.multiple: - self._value = [] - for part in value.split(","): - if issubclass(self.type, numbers.Integral): - # allow ranges of the form X:Y (inclusive at both ends) - lo_str, _, hi_str = part.partition(":") - lo = _parse(lo_str) - hi = _parse(hi_str) if hi_str else lo - self._value.extend(range(lo, hi + 1)) - else: - self._value.append(_parse(part)) - else: - self._value = _parse(value) - if self.callback is not None: - self.callback(self._value) - return self.value() - - def set(self, value: Any) -> None: - if self.multiple: - if not isinstance(value, list): - raise Error( - "Option %r is required to be a list of %s" - % (self.name, self.type.__name__) - ) - for item in value: - if item is not None and not isinstance(item, self.type): - raise Error( - "Option %r is required to be a list of %s" - % (self.name, self.type.__name__) - ) - else: - if value is not None and not isinstance(value, self.type): - raise Error( - "Option %r is required to be a %s (%s given)" - % (self.name, self.type.__name__, type(value)) - ) - self._value = value - if self.callback is not None: - self.callback(self._value) - - # Supported date/time formats in our options - _DATETIME_FORMATS = [ - "%a %b %d %H:%M:%S %Y", - "%Y-%m-%d %H:%M:%S", - "%Y-%m-%d %H:%M", - "%Y-%m-%dT%H:%M", - "%Y%m%d %H:%M:%S", - "%Y%m%d %H:%M", - "%Y-%m-%d", - "%Y%m%d", - "%H:%M:%S", - "%H:%M", - ] - - def _parse_datetime(self, value: str) -> datetime.datetime: - for format in self._DATETIME_FORMATS: - try: - return datetime.datetime.strptime(value, format) - except ValueError: - pass - raise Error("Unrecognized date/time format: %r" % value) - - _TIMEDELTA_ABBREV_DICT = { - "h": "hours", - "m": "minutes", - "min": "minutes", - "s": "seconds", - "sec": "seconds", - "ms": "milliseconds", - "us": "microseconds", - "d": "days", - "w": "weeks", - } - - _FLOAT_PATTERN = r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?" - - _TIMEDELTA_PATTERN = re.compile( - r"\s*(%s)\s*(\w*)\s*" % _FLOAT_PATTERN, re.IGNORECASE - ) - - def _parse_timedelta(self, value: str) -> datetime.timedelta: - try: - sum = datetime.timedelta() - start = 0 - while start < len(value): - m = self._TIMEDELTA_PATTERN.match(value, start) - if not m: - raise Exception() - num = float(m.group(1)) - units = m.group(2) or "seconds" - units = self._TIMEDELTA_ABBREV_DICT.get(units, units) - sum += datetime.timedelta(**{units: num}) - start = m.end() - return sum - except Exception: - raise - - def _parse_bool(self, value: str) -> bool: - return value.lower() not in ("false", "0", "f") - - def _parse_string(self, value: str) -> str: - return _unicode(value) - - -options = OptionParser() -"""Global options object. - -All defined options are available as attributes on this object. -""" - - -def define( - name: str, - default: Any = None, - type: Optional[type] = None, - help: Optional[str] = None, - metavar: Optional[str] = None, - multiple: bool = False, - group: Optional[str] = None, - callback: Optional[Callable[[Any], None]] = None, -) -> None: - """Defines an option in the global namespace. - - See `OptionParser.define`. - """ - return options.define( - name, - default=default, - type=type, - help=help, - metavar=metavar, - multiple=multiple, - group=group, - callback=callback, - ) - - -def parse_command_line( - args: Optional[List[str]] = None, final: bool = True -) -> List[str]: - """Parses global options from the command line. - - See `OptionParser.parse_command_line`. - """ - return options.parse_command_line(args, final=final) - - -def parse_config_file(path: str, final: bool = True) -> None: - """Parses global options from a config file. - - See `OptionParser.parse_config_file`. - """ - return options.parse_config_file(path, final=final) - - -def print_help(file: Optional[TextIO] = None) -> None: - """Prints all the command line options to stderr (or another file). - - See `OptionParser.print_help`. - """ - return options.print_help(file) - - -def add_parse_callback(callback: Callable[[], None]) -> None: - """Adds a parse callback, to be invoked when option parsing is done. - - See `OptionParser.add_parse_callback` - """ - options.add_parse_callback(callback) - - -# Default options -define_logging_options(options) diff --git a/telegramer/include/tornado/platform/__init__.py b/telegramer/include/tornado/platform/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/tornado/platform/asyncio.py b/telegramer/include/tornado/platform/asyncio.py deleted file mode 100644 index 012948b..0000000 --- a/telegramer/include/tornado/platform/asyncio.py +++ /dev/null @@ -1,611 +0,0 @@ -"""Bridges between the `asyncio` module and Tornado IOLoop. - -.. versionadded:: 3.2 - -This module integrates Tornado with the ``asyncio`` module introduced -in Python 3.4. This makes it possible to combine the two libraries on -the same event loop. - -.. deprecated:: 5.0 - - While the code in this module is still used, it is now enabled - automatically when `asyncio` is available, so applications should - no longer need to refer to this module directly. - -.. note:: - - Tornado is designed to use a selector-based event loop. On Windows, - where a proactor-based event loop has been the default since Python 3.8, - a selector event loop is emulated by running ``select`` on a separate thread. - Configuring ``asyncio`` to use a selector event loop may improve performance - of Tornado (but may reduce performance of other ``asyncio``-based libraries - in the same process). -""" - -import asyncio -import atexit -import concurrent.futures -import errno -import functools -import select -import socket -import sys -import threading -import typing -from tornado.gen import convert_yielded -from tornado.ioloop import IOLoop, _Selectable - -from typing import Any, TypeVar, Awaitable, Callable, Union, Optional, List, Tuple, Dict - -if typing.TYPE_CHECKING: - from typing import Set # noqa: F401 - from typing_extensions import Protocol - - class _HasFileno(Protocol): - def fileno(self) -> int: - pass - - _FileDescriptorLike = Union[int, _HasFileno] - -_T = TypeVar("_T") - - -# Collection of selector thread event loops to shut down on exit. -_selector_loops = set() # type: Set[AddThreadSelectorEventLoop] - - -def _atexit_callback() -> None: - for loop in _selector_loops: - with loop._select_cond: - loop._closing_selector = True - loop._select_cond.notify() - try: - loop._waker_w.send(b"a") - except BlockingIOError: - pass - # If we don't join our (daemon) thread here, we may get a deadlock - # during interpreter shutdown. I don't really understand why. This - # deadlock happens every time in CI (both travis and appveyor) but - # I've never been able to reproduce locally. - loop._thread.join() - _selector_loops.clear() - - -atexit.register(_atexit_callback) - - -class BaseAsyncIOLoop(IOLoop): - def initialize( # type: ignore - self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any - ) -> None: - # asyncio_loop is always the real underlying IOLoop. This is used in - # ioloop.py to maintain the asyncio-to-ioloop mappings. - self.asyncio_loop = asyncio_loop - # selector_loop is an event loop that implements the add_reader family of - # methods. Usually the same as asyncio_loop but differs on platforms such - # as windows where the default event loop does not implement these methods. - self.selector_loop = asyncio_loop - if hasattr(asyncio, "ProactorEventLoop") and isinstance( - asyncio_loop, asyncio.ProactorEventLoop # type: ignore - ): - # Ignore this line for mypy because the abstract method checker - # doesn't understand dynamic proxies. - self.selector_loop = AddThreadSelectorEventLoop(asyncio_loop) # type: ignore - # Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler) - self.handlers = {} # type: Dict[int, Tuple[Union[int, _Selectable], Callable]] - # Set of fds listening for reads/writes - self.readers = set() # type: Set[int] - self.writers = set() # type: Set[int] - self.closing = False - # If an asyncio loop was closed through an asyncio interface - # instead of IOLoop.close(), we'd never hear about it and may - # have left a dangling reference in our map. In case an - # application (or, more likely, a test suite) creates and - # destroys a lot of event loops in this way, check here to - # ensure that we don't have a lot of dead loops building up in - # the map. - # - # TODO(bdarnell): consider making self.asyncio_loop a weakref - # for AsyncIOMainLoop and make _ioloop_for_asyncio a - # WeakKeyDictionary. - for loop in list(IOLoop._ioloop_for_asyncio): - if loop.is_closed(): - del IOLoop._ioloop_for_asyncio[loop] - IOLoop._ioloop_for_asyncio[asyncio_loop] = self - - self._thread_identity = 0 - - super().initialize(**kwargs) - - def assign_thread_identity() -> None: - self._thread_identity = threading.get_ident() - - self.add_callback(assign_thread_identity) - - def close(self, all_fds: bool = False) -> None: - self.closing = True - for fd in list(self.handlers): - fileobj, handler_func = self.handlers[fd] - self.remove_handler(fd) - if all_fds: - self.close_fd(fileobj) - # Remove the mapping before closing the asyncio loop. If this - # happened in the other order, we could race against another - # initialize() call which would see the closed asyncio loop, - # assume it was closed from the asyncio side, and do this - # cleanup for us, leading to a KeyError. - del IOLoop._ioloop_for_asyncio[self.asyncio_loop] - if self.selector_loop is not self.asyncio_loop: - self.selector_loop.close() - self.asyncio_loop.close() - - def add_handler( - self, fd: Union[int, _Selectable], handler: Callable[..., None], events: int - ) -> None: - fd, fileobj = self.split_fd(fd) - if fd in self.handlers: - raise ValueError("fd %s added twice" % fd) - self.handlers[fd] = (fileobj, handler) - if events & IOLoop.READ: - self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ) - self.readers.add(fd) - if events & IOLoop.WRITE: - self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE) - self.writers.add(fd) - - def update_handler(self, fd: Union[int, _Selectable], events: int) -> None: - fd, fileobj = self.split_fd(fd) - if events & IOLoop.READ: - if fd not in self.readers: - self.selector_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ) - self.readers.add(fd) - else: - if fd in self.readers: - self.selector_loop.remove_reader(fd) - self.readers.remove(fd) - if events & IOLoop.WRITE: - if fd not in self.writers: - self.selector_loop.add_writer(fd, self._handle_events, fd, IOLoop.WRITE) - self.writers.add(fd) - else: - if fd in self.writers: - self.selector_loop.remove_writer(fd) - self.writers.remove(fd) - - def remove_handler(self, fd: Union[int, _Selectable]) -> None: - fd, fileobj = self.split_fd(fd) - if fd not in self.handlers: - return - if fd in self.readers: - self.selector_loop.remove_reader(fd) - self.readers.remove(fd) - if fd in self.writers: - self.selector_loop.remove_writer(fd) - self.writers.remove(fd) - del self.handlers[fd] - - def _handle_events(self, fd: int, events: int) -> None: - fileobj, handler_func = self.handlers[fd] - handler_func(fileobj, events) - - def start(self) -> None: - try: - old_loop = asyncio.get_event_loop() - except (RuntimeError, AssertionError): - old_loop = None # type: ignore - try: - self._setup_logging() - asyncio.set_event_loop(self.asyncio_loop) - self.asyncio_loop.run_forever() - finally: - asyncio.set_event_loop(old_loop) - - def stop(self) -> None: - self.asyncio_loop.stop() - - def call_at( - self, when: float, callback: Callable[..., None], *args: Any, **kwargs: Any - ) -> object: - # asyncio.call_at supports *args but not **kwargs, so bind them here. - # We do not synchronize self.time and asyncio_loop.time, so - # convert from absolute to relative. - return self.asyncio_loop.call_later( - max(0, when - self.time()), - self._run_callback, - functools.partial(callback, *args, **kwargs), - ) - - def remove_timeout(self, timeout: object) -> None: - timeout.cancel() # type: ignore - - def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None: - if threading.get_ident() == self._thread_identity: - call_soon = self.asyncio_loop.call_soon - else: - call_soon = self.asyncio_loop.call_soon_threadsafe - try: - call_soon(self._run_callback, functools.partial(callback, *args, **kwargs)) - except RuntimeError: - # "Event loop is closed". Swallow the exception for - # consistency with PollIOLoop (and logical consistency - # with the fact that we can't guarantee that an - # add_callback that completes without error will - # eventually execute). - pass - except AttributeError: - # ProactorEventLoop may raise this instead of RuntimeError - # if call_soon_threadsafe races with a call to close(). - # Swallow it too for consistency. - pass - - def add_callback_from_signal( - self, callback: Callable, *args: Any, **kwargs: Any - ) -> None: - try: - self.asyncio_loop.call_soon_threadsafe( - self._run_callback, functools.partial(callback, *args, **kwargs) - ) - except RuntimeError: - pass - - def run_in_executor( - self, - executor: Optional[concurrent.futures.Executor], - func: Callable[..., _T], - *args: Any - ) -> Awaitable[_T]: - return self.asyncio_loop.run_in_executor(executor, func, *args) - - def set_default_executor(self, executor: concurrent.futures.Executor) -> None: - return self.asyncio_loop.set_default_executor(executor) - - -class AsyncIOMainLoop(BaseAsyncIOLoop): - """``AsyncIOMainLoop`` creates an `.IOLoop` that corresponds to the - current ``asyncio`` event loop (i.e. the one returned by - ``asyncio.get_event_loop()``). - - .. deprecated:: 5.0 - - Now used automatically when appropriate; it is no longer necessary - to refer to this class directly. - - .. versionchanged:: 5.0 - - Closing an `AsyncIOMainLoop` now closes the underlying asyncio loop. - """ - - def initialize(self, **kwargs: Any) -> None: # type: ignore - super().initialize(asyncio.get_event_loop(), **kwargs) - - def make_current(self) -> None: - # AsyncIOMainLoop already refers to the current asyncio loop so - # nothing to do here. - pass - - -class AsyncIOLoop(BaseAsyncIOLoop): - """``AsyncIOLoop`` is an `.IOLoop` that runs on an ``asyncio`` event loop. - This class follows the usual Tornado semantics for creating new - ``IOLoops``; these loops are not necessarily related to the - ``asyncio`` default event loop. - - Each ``AsyncIOLoop`` creates a new ``asyncio.EventLoop``; this object - can be accessed with the ``asyncio_loop`` attribute. - - .. versionchanged:: 5.0 - - When an ``AsyncIOLoop`` becomes the current `.IOLoop`, it also sets - the current `asyncio` event loop. - - .. deprecated:: 5.0 - - Now used automatically when appropriate; it is no longer necessary - to refer to this class directly. - """ - - def initialize(self, **kwargs: Any) -> None: # type: ignore - self.is_current = False - loop = asyncio.new_event_loop() - try: - super().initialize(loop, **kwargs) - except Exception: - # If initialize() does not succeed (taking ownership of the loop), - # we have to close it. - loop.close() - raise - - def close(self, all_fds: bool = False) -> None: - if self.is_current: - self.clear_current() - super().close(all_fds=all_fds) - - def make_current(self) -> None: - if not self.is_current: - try: - self.old_asyncio = asyncio.get_event_loop() - except (RuntimeError, AssertionError): - self.old_asyncio = None # type: ignore - self.is_current = True - asyncio.set_event_loop(self.asyncio_loop) - - def _clear_current_hook(self) -> None: - if self.is_current: - asyncio.set_event_loop(self.old_asyncio) - self.is_current = False - - -def to_tornado_future(asyncio_future: asyncio.Future) -> asyncio.Future: - """Convert an `asyncio.Future` to a `tornado.concurrent.Future`. - - .. versionadded:: 4.1 - - .. deprecated:: 5.0 - Tornado ``Futures`` have been merged with `asyncio.Future`, - so this method is now a no-op. - """ - return asyncio_future - - -def to_asyncio_future(tornado_future: asyncio.Future) -> asyncio.Future: - """Convert a Tornado yieldable object to an `asyncio.Future`. - - .. versionadded:: 4.1 - - .. versionchanged:: 4.3 - Now accepts any yieldable object, not just - `tornado.concurrent.Future`. - - .. deprecated:: 5.0 - Tornado ``Futures`` have been merged with `asyncio.Future`, - so this method is now equivalent to `tornado.gen.convert_yielded`. - """ - return convert_yielded(tornado_future) - - -if sys.platform == "win32" and hasattr(asyncio, "WindowsSelectorEventLoopPolicy"): - # "Any thread" and "selector" should be orthogonal, but there's not a clean - # interface for composing policies so pick the right base. - _BasePolicy = asyncio.WindowsSelectorEventLoopPolicy # type: ignore -else: - _BasePolicy = asyncio.DefaultEventLoopPolicy - - -class AnyThreadEventLoopPolicy(_BasePolicy): # type: ignore - """Event loop policy that allows loop creation on any thread. - - The default `asyncio` event loop policy only automatically creates - event loops in the main threads. Other threads must create event - loops explicitly or `asyncio.get_event_loop` (and therefore - `.IOLoop.current`) will fail. Installing this policy allows event - loops to be created automatically on any thread, matching the - behavior of Tornado versions prior to 5.0 (or 5.0 on Python 2). - - Usage:: - - asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) - - .. versionadded:: 5.0 - - """ - - def get_event_loop(self) -> asyncio.AbstractEventLoop: - try: - return super().get_event_loop() - except (RuntimeError, AssertionError): - # This was an AssertionError in Python 3.4.2 (which ships with Debian Jessie) - # and changed to a RuntimeError in 3.4.3. - # "There is no current event loop in thread %r" - loop = self.new_event_loop() - self.set_event_loop(loop) - return loop - - -class AddThreadSelectorEventLoop(asyncio.AbstractEventLoop): - """Wrap an event loop to add implementations of the ``add_reader`` method family. - - Instances of this class start a second thread to run a selector. - This thread is completely hidden from the user; all callbacks are - run on the wrapped event loop's thread. - - This class is used automatically by Tornado; applications should not need - to refer to it directly. - - It is safe to wrap any event loop with this class, although it only makes sense - for event loops that do not implement the ``add_reader`` family of methods - themselves (i.e. ``WindowsProactorEventLoop``) - - Closing the ``AddThreadSelectorEventLoop`` also closes the wrapped event loop. - - """ - - # This class is a __getattribute__-based proxy. All attributes other than those - # in this set are proxied through to the underlying loop. - MY_ATTRIBUTES = { - "_consume_waker", - "_select_cond", - "_select_args", - "_closing_selector", - "_thread", - "_handle_event", - "_readers", - "_real_loop", - "_start_select", - "_run_select", - "_handle_select", - "_wake_selector", - "_waker_r", - "_waker_w", - "_writers", - "add_reader", - "add_writer", - "close", - "remove_reader", - "remove_writer", - } - - def __getattribute__(self, name: str) -> Any: - if name in AddThreadSelectorEventLoop.MY_ATTRIBUTES: - return super().__getattribute__(name) - return getattr(self._real_loop, name) - - def __init__(self, real_loop: asyncio.AbstractEventLoop) -> None: - self._real_loop = real_loop - - # Create a thread to run the select system call. We manage this thread - # manually so we can trigger a clean shutdown from an atexit hook. Note - # that due to the order of operations at shutdown, only daemon threads - # can be shut down in this way (non-daemon threads would require the - # introduction of a new hook: https://bugs.python.org/issue41962) - self._select_cond = threading.Condition() - self._select_args = ( - None - ) # type: Optional[Tuple[List[_FileDescriptorLike], List[_FileDescriptorLike]]] - self._closing_selector = False - self._thread = threading.Thread( - name="Tornado selector", daemon=True, target=self._run_select, - ) - self._thread.start() - # Start the select loop once the loop is started. - self._real_loop.call_soon(self._start_select) - - self._readers = {} # type: Dict[_FileDescriptorLike, Callable] - self._writers = {} # type: Dict[_FileDescriptorLike, Callable] - - # Writing to _waker_w will wake up the selector thread, which - # watches for _waker_r to be readable. - self._waker_r, self._waker_w = socket.socketpair() - self._waker_r.setblocking(False) - self._waker_w.setblocking(False) - _selector_loops.add(self) - self.add_reader(self._waker_r, self._consume_waker) - - def __del__(self) -> None: - # If the top-level application code uses asyncio interfaces to - # start and stop the event loop, no objects created in Tornado - # can get a clean shutdown notification. If we're just left to - # be GC'd, we must explicitly close our sockets to avoid - # logging warnings. - _selector_loops.discard(self) - self._waker_r.close() - self._waker_w.close() - - def close(self) -> None: - with self._select_cond: - self._closing_selector = True - self._select_cond.notify() - self._wake_selector() - self._thread.join() - _selector_loops.discard(self) - self._waker_r.close() - self._waker_w.close() - self._real_loop.close() - - def _wake_selector(self) -> None: - try: - self._waker_w.send(b"a") - except BlockingIOError: - pass - - def _consume_waker(self) -> None: - try: - self._waker_r.recv(1024) - except BlockingIOError: - pass - - def _start_select(self) -> None: - # Capture reader and writer sets here in the event loop - # thread to avoid any problems with concurrent - # modification while the select loop uses them. - with self._select_cond: - assert self._select_args is None - self._select_args = (list(self._readers.keys()), list(self._writers.keys())) - self._select_cond.notify() - - def _run_select(self) -> None: - while True: - with self._select_cond: - while self._select_args is None and not self._closing_selector: - self._select_cond.wait() - if self._closing_selector: - return - assert self._select_args is not None - to_read, to_write = self._select_args - self._select_args = None - - # We use the simpler interface of the select module instead of - # the more stateful interface in the selectors module because - # this class is only intended for use on windows, where - # select.select is the only option. The selector interface - # does not have well-documented thread-safety semantics that - # we can rely on so ensuring proper synchronization would be - # tricky. - try: - # On windows, selecting on a socket for write will not - # return the socket when there is an error (but selecting - # for reads works). Also select for errors when selecting - # for writes, and merge the results. - # - # This pattern is also used in - # https://github.com/python/cpython/blob/v3.8.0/Lib/selectors.py#L312-L317 - rs, ws, xs = select.select(to_read, to_write, to_write) - ws = ws + xs - except OSError as e: - # After remove_reader or remove_writer is called, the file - # descriptor may subsequently be closed on the event loop - # thread. It's possible that this select thread hasn't - # gotten into the select system call by the time that - # happens in which case (at least on macOS), select may - # raise a "bad file descriptor" error. If we get that - # error, check and see if we're also being woken up by - # polling the waker alone. If we are, just return to the - # event loop and we'll get the updated set of file - # descriptors on the next iteration. Otherwise, raise the - # original error. - if e.errno == getattr(errno, "WSAENOTSOCK", errno.EBADF): - rs, _, _ = select.select([self._waker_r.fileno()], [], [], 0) - if rs: - ws = [] - else: - raise - else: - raise - self._real_loop.call_soon_threadsafe(self._handle_select, rs, ws) - - def _handle_select( - self, rs: List["_FileDescriptorLike"], ws: List["_FileDescriptorLike"] - ) -> None: - for r in rs: - self._handle_event(r, self._readers) - for w in ws: - self._handle_event(w, self._writers) - self._start_select() - - def _handle_event( - self, fd: "_FileDescriptorLike", cb_map: Dict["_FileDescriptorLike", Callable], - ) -> None: - try: - callback = cb_map[fd] - except KeyError: - return - callback() - - def add_reader( - self, fd: "_FileDescriptorLike", callback: Callable[..., None], *args: Any - ) -> None: - self._readers[fd] = functools.partial(callback, *args) - self._wake_selector() - - def add_writer( - self, fd: "_FileDescriptorLike", callback: Callable[..., None], *args: Any - ) -> None: - self._writers[fd] = functools.partial(callback, *args) - self._wake_selector() - - def remove_reader(self, fd: "_FileDescriptorLike") -> None: - del self._readers[fd] - self._wake_selector() - - def remove_writer(self, fd: "_FileDescriptorLike") -> None: - del self._writers[fd] - self._wake_selector() diff --git a/telegramer/include/tornado/platform/caresresolver.py b/telegramer/include/tornado/platform/caresresolver.py deleted file mode 100644 index e2c5009..0000000 --- a/telegramer/include/tornado/platform/caresresolver.py +++ /dev/null @@ -1,89 +0,0 @@ -import pycares # type: ignore -import socket - -from tornado.concurrent import Future -from tornado import gen -from tornado.ioloop import IOLoop -from tornado.netutil import Resolver, is_valid_ip - -import typing - -if typing.TYPE_CHECKING: - from typing import Generator, Any, List, Tuple, Dict # noqa: F401 - - -class CaresResolver(Resolver): - """Name resolver based on the c-ares library. - - This is a non-blocking and non-threaded resolver. It may not produce - the same results as the system resolver, but can be used for non-blocking - resolution when threads cannot be used. - - c-ares fails to resolve some names when ``family`` is ``AF_UNSPEC``, - so it is only recommended for use in ``AF_INET`` (i.e. IPv4). This is - the default for ``tornado.simple_httpclient``, but other libraries - may default to ``AF_UNSPEC``. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - """ - - def initialize(self) -> None: - self.io_loop = IOLoop.current() - self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb) - self.fds = {} # type: Dict[int, int] - - def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None: - state = (IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0) - if not state: - self.io_loop.remove_handler(fd) - del self.fds[fd] - elif fd in self.fds: - self.io_loop.update_handler(fd, state) - self.fds[fd] = state - else: - self.io_loop.add_handler(fd, self._handle_events, state) - self.fds[fd] = state - - def _handle_events(self, fd: int, events: int) -> None: - read_fd = pycares.ARES_SOCKET_BAD - write_fd = pycares.ARES_SOCKET_BAD - if events & IOLoop.READ: - read_fd = fd - if events & IOLoop.WRITE: - write_fd = fd - self.channel.process_fd(read_fd, write_fd) - - @gen.coroutine - def resolve( - self, host: str, port: int, family: int = 0 - ) -> "Generator[Any, Any, List[Tuple[int, Any]]]": - if is_valid_ip(host): - addresses = [host] - else: - # gethostbyname doesn't take callback as a kwarg - fut = Future() # type: Future[Tuple[Any, Any]] - self.channel.gethostbyname( - host, family, lambda result, error: fut.set_result((result, error)) - ) - result, error = yield fut - if error: - raise IOError( - "C-Ares returned error %s: %s while resolving %s" - % (error, pycares.errno.strerror(error), host) - ) - addresses = result.addresses - addrinfo = [] - for address in addresses: - if "." in address: - address_family = socket.AF_INET - elif ":" in address: - address_family = socket.AF_INET6 - else: - address_family = socket.AF_UNSPEC - if family != socket.AF_UNSPEC and family != address_family: - raise IOError( - "Requested socket family %d but got %d" % (family, address_family) - ) - addrinfo.append((typing.cast(int, address_family), (address, port))) - return addrinfo diff --git a/telegramer/include/tornado/platform/twisted.py b/telegramer/include/tornado/platform/twisted.py deleted file mode 100644 index 0987a84..0000000 --- a/telegramer/include/tornado/platform/twisted.py +++ /dev/null @@ -1,146 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -"""Bridges between the Twisted package and Tornado. -""" - -import socket -import sys - -import twisted.internet.abstract # type: ignore -import twisted.internet.asyncioreactor # type: ignore -from twisted.internet.defer import Deferred # type: ignore -from twisted.python import failure # type: ignore -import twisted.names.cache # type: ignore -import twisted.names.client # type: ignore -import twisted.names.hosts # type: ignore -import twisted.names.resolve # type: ignore - - -from tornado.concurrent import Future, future_set_exc_info -from tornado.escape import utf8 -from tornado import gen -from tornado.netutil import Resolver - -import typing - -if typing.TYPE_CHECKING: - from typing import Generator, Any, List, Tuple # noqa: F401 - - -class TwistedResolver(Resolver): - """Twisted-based asynchronous resolver. - - This is a non-blocking and non-threaded resolver. It is - recommended only when threads cannot be used, since it has - limitations compared to the standard ``getaddrinfo``-based - `~tornado.netutil.Resolver` and - `~tornado.netutil.DefaultExecutorResolver`. Specifically, it returns at - most one result, and arguments other than ``host`` and ``family`` - are ignored. It may fail to resolve when ``family`` is not - ``socket.AF_UNSPEC``. - - Requires Twisted 12.1 or newer. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - """ - - def initialize(self) -> None: - # partial copy of twisted.names.client.createResolver, which doesn't - # allow for a reactor to be passed in. - self.reactor = twisted.internet.asyncioreactor.AsyncioSelectorReactor() - - host_resolver = twisted.names.hosts.Resolver("/etc/hosts") - cache_resolver = twisted.names.cache.CacheResolver(reactor=self.reactor) - real_resolver = twisted.names.client.Resolver( - "/etc/resolv.conf", reactor=self.reactor - ) - self.resolver = twisted.names.resolve.ResolverChain( - [host_resolver, cache_resolver, real_resolver] - ) - - @gen.coroutine - def resolve( - self, host: str, port: int, family: int = 0 - ) -> "Generator[Any, Any, List[Tuple[int, Any]]]": - # getHostByName doesn't accept IP addresses, so if the input - # looks like an IP address just return it immediately. - if twisted.internet.abstract.isIPAddress(host): - resolved = host - resolved_family = socket.AF_INET - elif twisted.internet.abstract.isIPv6Address(host): - resolved = host - resolved_family = socket.AF_INET6 - else: - deferred = self.resolver.getHostByName(utf8(host)) - fut = Future() # type: Future[Any] - deferred.addBoth(fut.set_result) - resolved = yield fut - if isinstance(resolved, failure.Failure): - try: - resolved.raiseException() - except twisted.names.error.DomainError as e: - raise IOError(e) - elif twisted.internet.abstract.isIPAddress(resolved): - resolved_family = socket.AF_INET - elif twisted.internet.abstract.isIPv6Address(resolved): - resolved_family = socket.AF_INET6 - else: - resolved_family = socket.AF_UNSPEC - if family != socket.AF_UNSPEC and family != resolved_family: - raise Exception( - "Requested socket family %d but got %d" % (family, resolved_family) - ) - result = [(typing.cast(int, resolved_family), (resolved, port))] - return result - - -def install() -> None: - """Install ``AsyncioSelectorReactor`` as the default Twisted reactor. - - .. deprecated:: 5.1 - - This function is provided for backwards compatibility; code - that does not require compatibility with older versions of - Tornado should use - ``twisted.internet.asyncioreactor.install()`` directly. - - .. versionchanged:: 6.0.3 - - In Tornado 5.x and before, this function installed a reactor - based on the Tornado ``IOLoop``. When that reactor - implementation was removed in Tornado 6.0.0, this function was - removed as well. It was restored in Tornado 6.0.3 using the - ``asyncio`` reactor instead. - - """ - from twisted.internet.asyncioreactor import install - - install() - - -if hasattr(gen.convert_yielded, "register"): - - @gen.convert_yielded.register(Deferred) # type: ignore - def _(d: Deferred) -> Future: - f = Future() # type: Future[Any] - - def errback(failure: failure.Failure) -> None: - try: - failure.raiseException() - # Should never happen, but just in case - raise Exception("errback called without error") - except: - future_set_exc_info(f, sys.exc_info()) - - d.addCallbacks(f.set_result, errback) - return f diff --git a/telegramer/include/tornado/process.py b/telegramer/include/tornado/process.py deleted file mode 100644 index 26428fe..0000000 --- a/telegramer/include/tornado/process.py +++ /dev/null @@ -1,373 +0,0 @@ -# -# Copyright 2011 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Utilities for working with multiple processes, including both forking -the server into multiple processes and managing subprocesses. -""" - -import os -import multiprocessing -import signal -import subprocess -import sys -import time - -from binascii import hexlify - -from tornado.concurrent import ( - Future, - future_set_result_unless_cancelled, - future_set_exception_unless_cancelled, -) -from tornado import ioloop -from tornado.iostream import PipeIOStream -from tornado.log import gen_log - -import typing -from typing import Optional, Any, Callable - -if typing.TYPE_CHECKING: - from typing import List # noqa: F401 - -# Re-export this exception for convenience. -CalledProcessError = subprocess.CalledProcessError - - -def cpu_count() -> int: - """Returns the number of processors on this machine.""" - if multiprocessing is None: - return 1 - try: - return multiprocessing.cpu_count() - except NotImplementedError: - pass - try: - return os.sysconf("SC_NPROCESSORS_CONF") # type: ignore - except (AttributeError, ValueError): - pass - gen_log.error("Could not detect number of processors; assuming 1") - return 1 - - -def _reseed_random() -> None: - if "random" not in sys.modules: - return - import random - - # If os.urandom is available, this method does the same thing as - # random.seed (at least as of python 2.6). If os.urandom is not - # available, we mix in the pid in addition to a timestamp. - try: - seed = int(hexlify(os.urandom(16)), 16) - except NotImplementedError: - seed = int(time.time() * 1000) ^ os.getpid() - random.seed(seed) - - -_task_id = None - - -def fork_processes( - num_processes: Optional[int], max_restarts: Optional[int] = None -) -> int: - """Starts multiple worker processes. - - If ``num_processes`` is None or <= 0, we detect the number of cores - available on this machine and fork that number of child - processes. If ``num_processes`` is given and > 0, we fork that - specific number of sub-processes. - - Since we use processes and not threads, there is no shared memory - between any server code. - - Note that multiple processes are not compatible with the autoreload - module (or the ``autoreload=True`` option to `tornado.web.Application` - which defaults to True when ``debug=True``). - When using multiple processes, no IOLoops can be created or - referenced until after the call to ``fork_processes``. - - In each child process, ``fork_processes`` returns its *task id*, a - number between 0 and ``num_processes``. Processes that exit - abnormally (due to a signal or non-zero exit status) are restarted - with the same id (up to ``max_restarts`` times). In the parent - process, ``fork_processes`` calls ``sys.exit(0)`` after all child - processes have exited normally. - - max_restarts defaults to 100. - - Availability: Unix - """ - if sys.platform == "win32": - # The exact form of this condition matters to mypy; it understands - # if but not assert in this context. - raise Exception("fork not available on windows") - if max_restarts is None: - max_restarts = 100 - - global _task_id - assert _task_id is None - if num_processes is None or num_processes <= 0: - num_processes = cpu_count() - gen_log.info("Starting %d processes", num_processes) - children = {} - - def start_child(i: int) -> Optional[int]: - pid = os.fork() - if pid == 0: - # child process - _reseed_random() - global _task_id - _task_id = i - return i - else: - children[pid] = i - return None - - for i in range(num_processes): - id = start_child(i) - if id is not None: - return id - num_restarts = 0 - while children: - pid, status = os.wait() - if pid not in children: - continue - id = children.pop(pid) - if os.WIFSIGNALED(status): - gen_log.warning( - "child %d (pid %d) killed by signal %d, restarting", - id, - pid, - os.WTERMSIG(status), - ) - elif os.WEXITSTATUS(status) != 0: - gen_log.warning( - "child %d (pid %d) exited with status %d, restarting", - id, - pid, - os.WEXITSTATUS(status), - ) - else: - gen_log.info("child %d (pid %d) exited normally", id, pid) - continue - num_restarts += 1 - if num_restarts > max_restarts: - raise RuntimeError("Too many child restarts, giving up") - new_id = start_child(id) - if new_id is not None: - return new_id - # All child processes exited cleanly, so exit the master process - # instead of just returning to right after the call to - # fork_processes (which will probably just start up another IOLoop - # unless the caller checks the return value). - sys.exit(0) - - -def task_id() -> Optional[int]: - """Returns the current task id, if any. - - Returns None if this process was not created by `fork_processes`. - """ - global _task_id - return _task_id - - -class Subprocess(object): - """Wraps ``subprocess.Popen`` with IOStream support. - - The constructor is the same as ``subprocess.Popen`` with the following - additions: - - * ``stdin``, ``stdout``, and ``stderr`` may have the value - ``tornado.process.Subprocess.STREAM``, which will make the corresponding - attribute of the resulting Subprocess a `.PipeIOStream`. If this option - is used, the caller is responsible for closing the streams when done - with them. - - The ``Subprocess.STREAM`` option and the ``set_exit_callback`` and - ``wait_for_exit`` methods do not work on Windows. There is - therefore no reason to use this class instead of - ``subprocess.Popen`` on that platform. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - - """ - - STREAM = object() - - _initialized = False - _waiting = {} # type: ignore - _old_sigchld = None - - def __init__(self, *args: Any, **kwargs: Any) -> None: - self.io_loop = ioloop.IOLoop.current() - # All FDs we create should be closed on error; those in to_close - # should be closed in the parent process on success. - pipe_fds = [] # type: List[int] - to_close = [] # type: List[int] - if kwargs.get("stdin") is Subprocess.STREAM: - in_r, in_w = os.pipe() - kwargs["stdin"] = in_r - pipe_fds.extend((in_r, in_w)) - to_close.append(in_r) - self.stdin = PipeIOStream(in_w) - if kwargs.get("stdout") is Subprocess.STREAM: - out_r, out_w = os.pipe() - kwargs["stdout"] = out_w - pipe_fds.extend((out_r, out_w)) - to_close.append(out_w) - self.stdout = PipeIOStream(out_r) - if kwargs.get("stderr") is Subprocess.STREAM: - err_r, err_w = os.pipe() - kwargs["stderr"] = err_w - pipe_fds.extend((err_r, err_w)) - to_close.append(err_w) - self.stderr = PipeIOStream(err_r) - try: - self.proc = subprocess.Popen(*args, **kwargs) - except: - for fd in pipe_fds: - os.close(fd) - raise - for fd in to_close: - os.close(fd) - self.pid = self.proc.pid - for attr in ["stdin", "stdout", "stderr"]: - if not hasattr(self, attr): # don't clobber streams set above - setattr(self, attr, getattr(self.proc, attr)) - self._exit_callback = None # type: Optional[Callable[[int], None]] - self.returncode = None # type: Optional[int] - - def set_exit_callback(self, callback: Callable[[int], None]) -> None: - """Runs ``callback`` when this process exits. - - The callback takes one argument, the return code of the process. - - This method uses a ``SIGCHLD`` handler, which is a global setting - and may conflict if you have other libraries trying to handle the - same signal. If you are using more than one ``IOLoop`` it may - be necessary to call `Subprocess.initialize` first to designate - one ``IOLoop`` to run the signal handlers. - - In many cases a close callback on the stdout or stderr streams - can be used as an alternative to an exit callback if the - signal handler is causing a problem. - - Availability: Unix - """ - self._exit_callback = callback - Subprocess.initialize() - Subprocess._waiting[self.pid] = self - Subprocess._try_cleanup_process(self.pid) - - def wait_for_exit(self, raise_error: bool = True) -> "Future[int]": - """Returns a `.Future` which resolves when the process exits. - - Usage:: - - ret = yield proc.wait_for_exit() - - This is a coroutine-friendly alternative to `set_exit_callback` - (and a replacement for the blocking `subprocess.Popen.wait`). - - By default, raises `subprocess.CalledProcessError` if the process - has a non-zero exit status. Use ``wait_for_exit(raise_error=False)`` - to suppress this behavior and return the exit status without raising. - - .. versionadded:: 4.2 - - Availability: Unix - """ - future = Future() # type: Future[int] - - def callback(ret: int) -> None: - if ret != 0 and raise_error: - # Unfortunately we don't have the original args any more. - future_set_exception_unless_cancelled( - future, CalledProcessError(ret, "unknown") - ) - else: - future_set_result_unless_cancelled(future, ret) - - self.set_exit_callback(callback) - return future - - @classmethod - def initialize(cls) -> None: - """Initializes the ``SIGCHLD`` handler. - - The signal handler is run on an `.IOLoop` to avoid locking issues. - Note that the `.IOLoop` used for signal handling need not be the - same one used by individual Subprocess objects (as long as the - ``IOLoops`` are each running in separate threads). - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been - removed. - - Availability: Unix - """ - if cls._initialized: - return - io_loop = ioloop.IOLoop.current() - cls._old_sigchld = signal.signal( - signal.SIGCHLD, - lambda sig, frame: io_loop.add_callback_from_signal(cls._cleanup), - ) - cls._initialized = True - - @classmethod - def uninitialize(cls) -> None: - """Removes the ``SIGCHLD`` handler.""" - if not cls._initialized: - return - signal.signal(signal.SIGCHLD, cls._old_sigchld) - cls._initialized = False - - @classmethod - def _cleanup(cls) -> None: - for pid in list(cls._waiting.keys()): # make a copy - cls._try_cleanup_process(pid) - - @classmethod - def _try_cleanup_process(cls, pid: int) -> None: - try: - ret_pid, status = os.waitpid(pid, os.WNOHANG) # type: ignore - except ChildProcessError: - return - if ret_pid == 0: - return - assert ret_pid == pid - subproc = cls._waiting.pop(pid) - subproc.io_loop.add_callback_from_signal(subproc._set_returncode, status) - - def _set_returncode(self, status: int) -> None: - if sys.platform == "win32": - self.returncode = -1 - else: - if os.WIFSIGNALED(status): - self.returncode = -os.WTERMSIG(status) - else: - assert os.WIFEXITED(status) - self.returncode = os.WEXITSTATUS(status) - # We've taken over wait() duty from the subprocess.Popen - # object. If we don't inform it of the process's return code, - # it will log a warning at destruction in python 3.6+. - self.proc.returncode = self.returncode - if self._exit_callback: - callback = self._exit_callback - self._exit_callback = None - callback(self.returncode) diff --git a/telegramer/include/tornado/py.typed b/telegramer/include/tornado/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/telegramer/include/tornado/queues.py b/telegramer/include/tornado/queues.py deleted file mode 100644 index 1e87f62..0000000 --- a/telegramer/include/tornado/queues.py +++ /dev/null @@ -1,414 +0,0 @@ -# Copyright 2015 The Tornado Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Asynchronous queues for coroutines. These classes are very similar -to those provided in the standard library's `asyncio package -`_. - -.. warning:: - - Unlike the standard library's `queue` module, the classes defined here - are *not* thread-safe. To use these queues from another thread, - use `.IOLoop.add_callback` to transfer control to the `.IOLoop` thread - before calling any queue methods. - -""" - -import collections -import datetime -import heapq - -from tornado import gen, ioloop -from tornado.concurrent import Future, future_set_result_unless_cancelled -from tornado.locks import Event - -from typing import Union, TypeVar, Generic, Awaitable, Optional -import typing - -if typing.TYPE_CHECKING: - from typing import Deque, Tuple, Any # noqa: F401 - -_T = TypeVar("_T") - -__all__ = ["Queue", "PriorityQueue", "LifoQueue", "QueueFull", "QueueEmpty"] - - -class QueueEmpty(Exception): - """Raised by `.Queue.get_nowait` when the queue has no items.""" - - pass - - -class QueueFull(Exception): - """Raised by `.Queue.put_nowait` when a queue is at its maximum size.""" - - pass - - -def _set_timeout( - future: Future, timeout: Union[None, float, datetime.timedelta] -) -> None: - if timeout: - - def on_timeout() -> None: - if not future.done(): - future.set_exception(gen.TimeoutError()) - - io_loop = ioloop.IOLoop.current() - timeout_handle = io_loop.add_timeout(timeout, on_timeout) - future.add_done_callback(lambda _: io_loop.remove_timeout(timeout_handle)) - - -class _QueueIterator(Generic[_T]): - def __init__(self, q: "Queue[_T]") -> None: - self.q = q - - def __anext__(self) -> Awaitable[_T]: - return self.q.get() - - -class Queue(Generic[_T]): - """Coordinate producer and consumer coroutines. - - If maxsize is 0 (the default) the queue size is unbounded. - - .. testcode:: - - from tornado import gen - from tornado.ioloop import IOLoop - from tornado.queues import Queue - - q = Queue(maxsize=2) - - async def consumer(): - async for item in q: - try: - print('Doing work on %s' % item) - await gen.sleep(0.01) - finally: - q.task_done() - - async def producer(): - for item in range(5): - await q.put(item) - print('Put %s' % item) - - async def main(): - # Start consumer without waiting (since it never finishes). - IOLoop.current().spawn_callback(consumer) - await producer() # Wait for producer to put all tasks. - await q.join() # Wait for consumer to finish all tasks. - print('Done') - - IOLoop.current().run_sync(main) - - .. testoutput:: - - Put 0 - Put 1 - Doing work on 0 - Put 2 - Doing work on 1 - Put 3 - Doing work on 2 - Put 4 - Doing work on 3 - Doing work on 4 - Done - - - In versions of Python without native coroutines (before 3.5), - ``consumer()`` could be written as:: - - @gen.coroutine - def consumer(): - while True: - item = yield q.get() - try: - print('Doing work on %s' % item) - yield gen.sleep(0.01) - finally: - q.task_done() - - .. versionchanged:: 4.3 - Added ``async for`` support in Python 3.5. - - """ - - # Exact type depends on subclass. Could be another generic - # parameter and use protocols to be more precise here. - _queue = None # type: Any - - def __init__(self, maxsize: int = 0) -> None: - if maxsize is None: - raise TypeError("maxsize can't be None") - - if maxsize < 0: - raise ValueError("maxsize can't be negative") - - self._maxsize = maxsize - self._init() - self._getters = collections.deque([]) # type: Deque[Future[_T]] - self._putters = collections.deque([]) # type: Deque[Tuple[_T, Future[None]]] - self._unfinished_tasks = 0 - self._finished = Event() - self._finished.set() - - @property - def maxsize(self) -> int: - """Number of items allowed in the queue.""" - return self._maxsize - - def qsize(self) -> int: - """Number of items in the queue.""" - return len(self._queue) - - def empty(self) -> bool: - return not self._queue - - def full(self) -> bool: - if self.maxsize == 0: - return False - else: - return self.qsize() >= self.maxsize - - def put( - self, item: _T, timeout: Optional[Union[float, datetime.timedelta]] = None - ) -> "Future[None]": - """Put an item into the queue, perhaps waiting until there is room. - - Returns a Future, which raises `tornado.util.TimeoutError` after a - timeout. - - ``timeout`` may be a number denoting a time (on the same - scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a - `datetime.timedelta` object for a deadline relative to the - current time. - """ - future = Future() # type: Future[None] - try: - self.put_nowait(item) - except QueueFull: - self._putters.append((item, future)) - _set_timeout(future, timeout) - else: - future.set_result(None) - return future - - def put_nowait(self, item: _T) -> None: - """Put an item into the queue without blocking. - - If no free slot is immediately available, raise `QueueFull`. - """ - self._consume_expired() - if self._getters: - assert self.empty(), "queue non-empty, why are getters waiting?" - getter = self._getters.popleft() - self.__put_internal(item) - future_set_result_unless_cancelled(getter, self._get()) - elif self.full(): - raise QueueFull - else: - self.__put_internal(item) - - def get( - self, timeout: Optional[Union[float, datetime.timedelta]] = None - ) -> Awaitable[_T]: - """Remove and return an item from the queue. - - Returns an awaitable which resolves once an item is available, or raises - `tornado.util.TimeoutError` after a timeout. - - ``timeout`` may be a number denoting a time (on the same - scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a - `datetime.timedelta` object for a deadline relative to the - current time. - - .. note:: - - The ``timeout`` argument of this method differs from that - of the standard library's `queue.Queue.get`. That method - interprets numeric values as relative timeouts; this one - interprets them as absolute deadlines and requires - ``timedelta`` objects for relative timeouts (consistent - with other timeouts in Tornado). - - """ - future = Future() # type: Future[_T] - try: - future.set_result(self.get_nowait()) - except QueueEmpty: - self._getters.append(future) - _set_timeout(future, timeout) - return future - - def get_nowait(self) -> _T: - """Remove and return an item from the queue without blocking. - - Return an item if one is immediately available, else raise - `QueueEmpty`. - """ - self._consume_expired() - if self._putters: - assert self.full(), "queue not full, why are putters waiting?" - item, putter = self._putters.popleft() - self.__put_internal(item) - future_set_result_unless_cancelled(putter, None) - return self._get() - elif self.qsize(): - return self._get() - else: - raise QueueEmpty - - def task_done(self) -> None: - """Indicate that a formerly enqueued task is complete. - - Used by queue consumers. For each `.get` used to fetch a task, a - subsequent call to `.task_done` tells the queue that the processing - on the task is complete. - - If a `.join` is blocking, it resumes when all items have been - processed; that is, when every `.put` is matched by a `.task_done`. - - Raises `ValueError` if called more times than `.put`. - """ - if self._unfinished_tasks <= 0: - raise ValueError("task_done() called too many times") - self._unfinished_tasks -= 1 - if self._unfinished_tasks == 0: - self._finished.set() - - def join( - self, timeout: Optional[Union[float, datetime.timedelta]] = None - ) -> Awaitable[None]: - """Block until all items in the queue are processed. - - Returns an awaitable, which raises `tornado.util.TimeoutError` after a - timeout. - """ - return self._finished.wait(timeout) - - def __aiter__(self) -> _QueueIterator[_T]: - return _QueueIterator(self) - - # These three are overridable in subclasses. - def _init(self) -> None: - self._queue = collections.deque() - - def _get(self) -> _T: - return self._queue.popleft() - - def _put(self, item: _T) -> None: - self._queue.append(item) - - # End of the overridable methods. - - def __put_internal(self, item: _T) -> None: - self._unfinished_tasks += 1 - self._finished.clear() - self._put(item) - - def _consume_expired(self) -> None: - # Remove timed-out waiters. - while self._putters and self._putters[0][1].done(): - self._putters.popleft() - - while self._getters and self._getters[0].done(): - self._getters.popleft() - - def __repr__(self) -> str: - return "<%s at %s %s>" % (type(self).__name__, hex(id(self)), self._format()) - - def __str__(self) -> str: - return "<%s %s>" % (type(self).__name__, self._format()) - - def _format(self) -> str: - result = "maxsize=%r" % (self.maxsize,) - if getattr(self, "_queue", None): - result += " queue=%r" % self._queue - if self._getters: - result += " getters[%s]" % len(self._getters) - if self._putters: - result += " putters[%s]" % len(self._putters) - if self._unfinished_tasks: - result += " tasks=%s" % self._unfinished_tasks - return result - - -class PriorityQueue(Queue): - """A `.Queue` that retrieves entries in priority order, lowest first. - - Entries are typically tuples like ``(priority number, data)``. - - .. testcode:: - - from tornado.queues import PriorityQueue - - q = PriorityQueue() - q.put((1, 'medium-priority item')) - q.put((0, 'high-priority item')) - q.put((10, 'low-priority item')) - - print(q.get_nowait()) - print(q.get_nowait()) - print(q.get_nowait()) - - .. testoutput:: - - (0, 'high-priority item') - (1, 'medium-priority item') - (10, 'low-priority item') - """ - - def _init(self) -> None: - self._queue = [] - - def _put(self, item: _T) -> None: - heapq.heappush(self._queue, item) - - def _get(self) -> _T: - return heapq.heappop(self._queue) - - -class LifoQueue(Queue): - """A `.Queue` that retrieves the most recently put items first. - - .. testcode:: - - from tornado.queues import LifoQueue - - q = LifoQueue() - q.put(3) - q.put(2) - q.put(1) - - print(q.get_nowait()) - print(q.get_nowait()) - print(q.get_nowait()) - - .. testoutput:: - - 1 - 2 - 3 - """ - - def _init(self) -> None: - self._queue = [] - - def _put(self, item: _T) -> None: - self._queue.append(item) - - def _get(self) -> _T: - return self._queue.pop() diff --git a/telegramer/include/tornado/routing.py b/telegramer/include/tornado/routing.py deleted file mode 100644 index a145d71..0000000 --- a/telegramer/include/tornado/routing.py +++ /dev/null @@ -1,717 +0,0 @@ -# Copyright 2015 The Tornado Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Flexible routing implementation. - -Tornado routes HTTP requests to appropriate handlers using `Router` -class implementations. The `tornado.web.Application` class is a -`Router` implementation and may be used directly, or the classes in -this module may be used for additional flexibility. The `RuleRouter` -class can match on more criteria than `.Application`, or the `Router` -interface can be subclassed for maximum customization. - -`Router` interface extends `~.httputil.HTTPServerConnectionDelegate` -to provide additional routing capabilities. This also means that any -`Router` implementation can be used directly as a ``request_callback`` -for `~.httpserver.HTTPServer` constructor. - -`Router` subclass must implement a ``find_handler`` method to provide -a suitable `~.httputil.HTTPMessageDelegate` instance to handle the -request: - -.. code-block:: python - - class CustomRouter(Router): - def find_handler(self, request, **kwargs): - # some routing logic providing a suitable HTTPMessageDelegate instance - return MessageDelegate(request.connection) - - class MessageDelegate(HTTPMessageDelegate): - def __init__(self, connection): - self.connection = connection - - def finish(self): - self.connection.write_headers( - ResponseStartLine("HTTP/1.1", 200, "OK"), - HTTPHeaders({"Content-Length": "2"}), - b"OK") - self.connection.finish() - - router = CustomRouter() - server = HTTPServer(router) - -The main responsibility of `Router` implementation is to provide a -mapping from a request to `~.httputil.HTTPMessageDelegate` instance -that will handle this request. In the example above we can see that -routing is possible even without instantiating an `~.web.Application`. - -For routing to `~.web.RequestHandler` implementations we need an -`~.web.Application` instance. `~.web.Application.get_handler_delegate` -provides a convenient way to create `~.httputil.HTTPMessageDelegate` -for a given request and `~.web.RequestHandler`. - -Here is a simple example of how we can we route to -`~.web.RequestHandler` subclasses by HTTP method: - -.. code-block:: python - - resources = {} - - class GetResource(RequestHandler): - def get(self, path): - if path not in resources: - raise HTTPError(404) - - self.finish(resources[path]) - - class PostResource(RequestHandler): - def post(self, path): - resources[path] = self.request.body - - class HTTPMethodRouter(Router): - def __init__(self, app): - self.app = app - - def find_handler(self, request, **kwargs): - handler = GetResource if request.method == "GET" else PostResource - return self.app.get_handler_delegate(request, handler, path_args=[request.path]) - - router = HTTPMethodRouter(Application()) - server = HTTPServer(router) - -`ReversibleRouter` interface adds the ability to distinguish between -the routes and reverse them to the original urls using route's name -and additional arguments. `~.web.Application` is itself an -implementation of `ReversibleRouter` class. - -`RuleRouter` and `ReversibleRuleRouter` are implementations of -`Router` and `ReversibleRouter` interfaces and can be used for -creating rule-based routing configurations. - -Rules are instances of `Rule` class. They contain a `Matcher`, which -provides the logic for determining whether the rule is a match for a -particular request and a target, which can be one of the following. - -1) An instance of `~.httputil.HTTPServerConnectionDelegate`: - -.. code-block:: python - - router = RuleRouter([ - Rule(PathMatches("/handler"), ConnectionDelegate()), - # ... more rules - ]) - - class ConnectionDelegate(HTTPServerConnectionDelegate): - def start_request(self, server_conn, request_conn): - return MessageDelegate(request_conn) - -2) A callable accepting a single argument of `~.httputil.HTTPServerRequest` type: - -.. code-block:: python - - router = RuleRouter([ - Rule(PathMatches("/callable"), request_callable) - ]) - - def request_callable(request): - request.write(b"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nOK") - request.finish() - -3) Another `Router` instance: - -.. code-block:: python - - router = RuleRouter([ - Rule(PathMatches("/router.*"), CustomRouter()) - ]) - -Of course a nested `RuleRouter` or a `~.web.Application` is allowed: - -.. code-block:: python - - router = RuleRouter([ - Rule(HostMatches("example.com"), RuleRouter([ - Rule(PathMatches("/app1/.*"), Application([(r"/app1/handler", Handler)])), - ])) - ]) - - server = HTTPServer(router) - -In the example below `RuleRouter` is used to route between applications: - -.. code-block:: python - - app1 = Application([ - (r"/app1/handler", Handler1), - # other handlers ... - ]) - - app2 = Application([ - (r"/app2/handler", Handler2), - # other handlers ... - ]) - - router = RuleRouter([ - Rule(PathMatches("/app1.*"), app1), - Rule(PathMatches("/app2.*"), app2) - ]) - - server = HTTPServer(router) - -For more information on application-level routing see docs for `~.web.Application`. - -.. versionadded:: 4.5 - -""" - -import re -from functools import partial - -from tornado import httputil -from tornado.httpserver import _CallableAdapter -from tornado.escape import url_escape, url_unescape, utf8 -from tornado.log import app_log -from tornado.util import basestring_type, import_object, re_unescape, unicode_type - -from typing import Any, Union, Optional, Awaitable, List, Dict, Pattern, Tuple, overload - - -class Router(httputil.HTTPServerConnectionDelegate): - """Abstract router interface.""" - - def find_handler( - self, request: httputil.HTTPServerRequest, **kwargs: Any - ) -> Optional[httputil.HTTPMessageDelegate]: - """Must be implemented to return an appropriate instance of `~.httputil.HTTPMessageDelegate` - that can serve the request. - Routing implementations may pass additional kwargs to extend the routing logic. - - :arg httputil.HTTPServerRequest request: current HTTP request. - :arg kwargs: additional keyword arguments passed by routing implementation. - :returns: an instance of `~.httputil.HTTPMessageDelegate` that will be used to - process the request. - """ - raise NotImplementedError() - - def start_request( - self, server_conn: object, request_conn: httputil.HTTPConnection - ) -> httputil.HTTPMessageDelegate: - return _RoutingDelegate(self, server_conn, request_conn) - - -class ReversibleRouter(Router): - """Abstract router interface for routers that can handle named routes - and support reversing them to original urls. - """ - - def reverse_url(self, name: str, *args: Any) -> Optional[str]: - """Returns url string for a given route name and arguments - or ``None`` if no match is found. - - :arg str name: route name. - :arg args: url parameters. - :returns: parametrized url string for a given route name (or ``None``). - """ - raise NotImplementedError() - - -class _RoutingDelegate(httputil.HTTPMessageDelegate): - def __init__( - self, router: Router, server_conn: object, request_conn: httputil.HTTPConnection - ) -> None: - self.server_conn = server_conn - self.request_conn = request_conn - self.delegate = None # type: Optional[httputil.HTTPMessageDelegate] - self.router = router # type: Router - - def headers_received( - self, - start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], - headers: httputil.HTTPHeaders, - ) -> Optional[Awaitable[None]]: - assert isinstance(start_line, httputil.RequestStartLine) - request = httputil.HTTPServerRequest( - connection=self.request_conn, - server_connection=self.server_conn, - start_line=start_line, - headers=headers, - ) - - self.delegate = self.router.find_handler(request) - if self.delegate is None: - app_log.debug( - "Delegate for %s %s request not found", - start_line.method, - start_line.path, - ) - self.delegate = _DefaultMessageDelegate(self.request_conn) - - return self.delegate.headers_received(start_line, headers) - - def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: - assert self.delegate is not None - return self.delegate.data_received(chunk) - - def finish(self) -> None: - assert self.delegate is not None - self.delegate.finish() - - def on_connection_close(self) -> None: - assert self.delegate is not None - self.delegate.on_connection_close() - - -class _DefaultMessageDelegate(httputil.HTTPMessageDelegate): - def __init__(self, connection: httputil.HTTPConnection) -> None: - self.connection = connection - - def finish(self) -> None: - self.connection.write_headers( - httputil.ResponseStartLine("HTTP/1.1", 404, "Not Found"), - httputil.HTTPHeaders(), - ) - self.connection.finish() - - -# _RuleList can either contain pre-constructed Rules or a sequence of -# arguments to be passed to the Rule constructor. -_RuleList = List[ - Union[ - "Rule", - List[Any], # Can't do detailed typechecking of lists. - Tuple[Union[str, "Matcher"], Any], - Tuple[Union[str, "Matcher"], Any, Dict[str, Any]], - Tuple[Union[str, "Matcher"], Any, Dict[str, Any], str], - ] -] - - -class RuleRouter(Router): - """Rule-based router implementation.""" - - def __init__(self, rules: Optional[_RuleList] = None) -> None: - """Constructs a router from an ordered list of rules:: - - RuleRouter([ - Rule(PathMatches("/handler"), Target), - # ... more rules - ]) - - You can also omit explicit `Rule` constructor and use tuples of arguments:: - - RuleRouter([ - (PathMatches("/handler"), Target), - ]) - - `PathMatches` is a default matcher, so the example above can be simplified:: - - RuleRouter([ - ("/handler", Target), - ]) - - In the examples above, ``Target`` can be a nested `Router` instance, an instance of - `~.httputil.HTTPServerConnectionDelegate` or an old-style callable, - accepting a request argument. - - :arg rules: a list of `Rule` instances or tuples of `Rule` - constructor arguments. - """ - self.rules = [] # type: List[Rule] - if rules: - self.add_rules(rules) - - def add_rules(self, rules: _RuleList) -> None: - """Appends new rules to the router. - - :arg rules: a list of Rule instances (or tuples of arguments, which are - passed to Rule constructor). - """ - for rule in rules: - if isinstance(rule, (tuple, list)): - assert len(rule) in (2, 3, 4) - if isinstance(rule[0], basestring_type): - rule = Rule(PathMatches(rule[0]), *rule[1:]) - else: - rule = Rule(*rule) - - self.rules.append(self.process_rule(rule)) - - def process_rule(self, rule: "Rule") -> "Rule": - """Override this method for additional preprocessing of each rule. - - :arg Rule rule: a rule to be processed. - :returns: the same or modified Rule instance. - """ - return rule - - def find_handler( - self, request: httputil.HTTPServerRequest, **kwargs: Any - ) -> Optional[httputil.HTTPMessageDelegate]: - for rule in self.rules: - target_params = rule.matcher.match(request) - if target_params is not None: - if rule.target_kwargs: - target_params["target_kwargs"] = rule.target_kwargs - - delegate = self.get_target_delegate( - rule.target, request, **target_params - ) - - if delegate is not None: - return delegate - - return None - - def get_target_delegate( - self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any - ) -> Optional[httputil.HTTPMessageDelegate]: - """Returns an instance of `~.httputil.HTTPMessageDelegate` for a - Rule's target. This method is called by `~.find_handler` and can be - extended to provide additional target types. - - :arg target: a Rule's target. - :arg httputil.HTTPServerRequest request: current request. - :arg target_params: additional parameters that can be useful - for `~.httputil.HTTPMessageDelegate` creation. - """ - if isinstance(target, Router): - return target.find_handler(request, **target_params) - - elif isinstance(target, httputil.HTTPServerConnectionDelegate): - assert request.connection is not None - return target.start_request(request.server_connection, request.connection) - - elif callable(target): - assert request.connection is not None - return _CallableAdapter( - partial(target, **target_params), request.connection - ) - - return None - - -class ReversibleRuleRouter(ReversibleRouter, RuleRouter): - """A rule-based router that implements ``reverse_url`` method. - - Each rule added to this router may have a ``name`` attribute that can be - used to reconstruct an original uri. The actual reconstruction takes place - in a rule's matcher (see `Matcher.reverse`). - """ - - def __init__(self, rules: Optional[_RuleList] = None) -> None: - self.named_rules = {} # type: Dict[str, Any] - super().__init__(rules) - - def process_rule(self, rule: "Rule") -> "Rule": - rule = super().process_rule(rule) - - if rule.name: - if rule.name in self.named_rules: - app_log.warning( - "Multiple handlers named %s; replacing previous value", rule.name - ) - self.named_rules[rule.name] = rule - - return rule - - def reverse_url(self, name: str, *args: Any) -> Optional[str]: - if name in self.named_rules: - return self.named_rules[name].matcher.reverse(*args) - - for rule in self.rules: - if isinstance(rule.target, ReversibleRouter): - reversed_url = rule.target.reverse_url(name, *args) - if reversed_url is not None: - return reversed_url - - return None - - -class Rule(object): - """A routing rule.""" - - def __init__( - self, - matcher: "Matcher", - target: Any, - target_kwargs: Optional[Dict[str, Any]] = None, - name: Optional[str] = None, - ) -> None: - """Constructs a Rule instance. - - :arg Matcher matcher: a `Matcher` instance used for determining - whether the rule should be considered a match for a specific - request. - :arg target: a Rule's target (typically a ``RequestHandler`` or - `~.httputil.HTTPServerConnectionDelegate` subclass or even a nested `Router`, - depending on routing implementation). - :arg dict target_kwargs: a dict of parameters that can be useful - at the moment of target instantiation (for example, ``status_code`` - for a ``RequestHandler`` subclass). They end up in - ``target_params['target_kwargs']`` of `RuleRouter.get_target_delegate` - method. - :arg str name: the name of the rule that can be used to find it - in `ReversibleRouter.reverse_url` implementation. - """ - if isinstance(target, str): - # import the Module and instantiate the class - # Must be a fully qualified name (module.ClassName) - target = import_object(target) - - self.matcher = matcher # type: Matcher - self.target = target - self.target_kwargs = target_kwargs if target_kwargs else {} - self.name = name - - def reverse(self, *args: Any) -> Optional[str]: - return self.matcher.reverse(*args) - - def __repr__(self) -> str: - return "%s(%r, %s, kwargs=%r, name=%r)" % ( - self.__class__.__name__, - self.matcher, - self.target, - self.target_kwargs, - self.name, - ) - - -class Matcher(object): - """Represents a matcher for request features.""" - - def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: - """Matches current instance against the request. - - :arg httputil.HTTPServerRequest request: current HTTP request - :returns: a dict of parameters to be passed to the target handler - (for example, ``handler_kwargs``, ``path_args``, ``path_kwargs`` - can be passed for proper `~.web.RequestHandler` instantiation). - An empty dict is a valid (and common) return value to indicate a match - when the argument-passing features are not used. - ``None`` must be returned to indicate that there is no match.""" - raise NotImplementedError() - - def reverse(self, *args: Any) -> Optional[str]: - """Reconstructs full url from matcher instance and additional arguments.""" - return None - - -class AnyMatches(Matcher): - """Matches any request.""" - - def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: - return {} - - -class HostMatches(Matcher): - """Matches requests from hosts specified by ``host_pattern`` regex.""" - - def __init__(self, host_pattern: Union[str, Pattern]) -> None: - if isinstance(host_pattern, basestring_type): - if not host_pattern.endswith("$"): - host_pattern += "$" - self.host_pattern = re.compile(host_pattern) - else: - self.host_pattern = host_pattern - - def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: - if self.host_pattern.match(request.host_name): - return {} - - return None - - -class DefaultHostMatches(Matcher): - """Matches requests from host that is equal to application's default_host. - Always returns no match if ``X-Real-Ip`` header is present. - """ - - def __init__(self, application: Any, host_pattern: Pattern) -> None: - self.application = application - self.host_pattern = host_pattern - - def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: - # Look for default host if not behind load balancer (for debugging) - if "X-Real-Ip" not in request.headers: - if self.host_pattern.match(self.application.default_host): - return {} - return None - - -class PathMatches(Matcher): - """Matches requests with paths specified by ``path_pattern`` regex.""" - - def __init__(self, path_pattern: Union[str, Pattern]) -> None: - if isinstance(path_pattern, basestring_type): - if not path_pattern.endswith("$"): - path_pattern += "$" - self.regex = re.compile(path_pattern) - else: - self.regex = path_pattern - - assert len(self.regex.groupindex) in (0, self.regex.groups), ( - "groups in url regexes must either be all named or all " - "positional: %r" % self.regex.pattern - ) - - self._path, self._group_count = self._find_groups() - - def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: - match = self.regex.match(request.path) - if match is None: - return None - if not self.regex.groups: - return {} - - path_args = [] # type: List[bytes] - path_kwargs = {} # type: Dict[str, bytes] - - # Pass matched groups to the handler. Since - # match.groups() includes both named and - # unnamed groups, we want to use either groups - # or groupdict but not both. - if self.regex.groupindex: - path_kwargs = dict( - (str(k), _unquote_or_none(v)) for (k, v) in match.groupdict().items() - ) - else: - path_args = [_unquote_or_none(s) for s in match.groups()] - - return dict(path_args=path_args, path_kwargs=path_kwargs) - - def reverse(self, *args: Any) -> Optional[str]: - if self._path is None: - raise ValueError("Cannot reverse url regex " + self.regex.pattern) - assert len(args) == self._group_count, ( - "required number of arguments " "not found" - ) - if not len(args): - return self._path - converted_args = [] - for a in args: - if not isinstance(a, (unicode_type, bytes)): - a = str(a) - converted_args.append(url_escape(utf8(a), plus=False)) - return self._path % tuple(converted_args) - - def _find_groups(self) -> Tuple[Optional[str], Optional[int]]: - """Returns a tuple (reverse string, group count) for a url. - - For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method - would return ('/%s/%s/', 2). - """ - pattern = self.regex.pattern - if pattern.startswith("^"): - pattern = pattern[1:] - if pattern.endswith("$"): - pattern = pattern[:-1] - - if self.regex.groups != pattern.count("("): - # The pattern is too complicated for our simplistic matching, - # so we can't support reversing it. - return None, None - - pieces = [] - for fragment in pattern.split("("): - if ")" in fragment: - paren_loc = fragment.index(")") - if paren_loc >= 0: - try: - unescaped_fragment = re_unescape(fragment[paren_loc + 1 :]) - except ValueError: - # If we can't unescape part of it, we can't - # reverse this url. - return (None, None) - pieces.append("%s" + unescaped_fragment) - else: - try: - unescaped_fragment = re_unescape(fragment) - except ValueError: - # If we can't unescape part of it, we can't - # reverse this url. - return (None, None) - pieces.append(unescaped_fragment) - - return "".join(pieces), self.regex.groups - - -class URLSpec(Rule): - """Specifies mappings between URLs and handlers. - - .. versionchanged: 4.5 - `URLSpec` is now a subclass of a `Rule` with `PathMatches` matcher and is preserved for - backwards compatibility. - """ - - def __init__( - self, - pattern: Union[str, Pattern], - handler: Any, - kwargs: Optional[Dict[str, Any]] = None, - name: Optional[str] = None, - ) -> None: - """Parameters: - - * ``pattern``: Regular expression to be matched. Any capturing - groups in the regex will be passed in to the handler's - get/post/etc methods as arguments (by keyword if named, by - position if unnamed. Named and unnamed capturing groups - may not be mixed in the same rule). - - * ``handler``: `~.web.RequestHandler` subclass to be invoked. - - * ``kwargs`` (optional): A dictionary of additional arguments - to be passed to the handler's constructor. - - * ``name`` (optional): A name for this handler. Used by - `~.web.Application.reverse_url`. - - """ - matcher = PathMatches(pattern) - super().__init__(matcher, handler, kwargs, name) - - self.regex = matcher.regex - self.handler_class = self.target - self.kwargs = kwargs - - def __repr__(self) -> str: - return "%s(%r, %s, kwargs=%r, name=%r)" % ( - self.__class__.__name__, - self.regex.pattern, - self.handler_class, - self.kwargs, - self.name, - ) - - -@overload -def _unquote_or_none(s: str) -> bytes: - pass - - -@overload # noqa: F811 -def _unquote_or_none(s: None) -> None: - pass - - -def _unquote_or_none(s: Optional[str]) -> Optional[bytes]: # noqa: F811 - """None-safe wrapper around url_unescape to handle unmatched optional - groups correctly. - - Note that args are passed as bytes so the handler can decide what - encoding to use. - """ - if s is None: - return s - return url_unescape(s, encoding=None, plus=False) diff --git a/telegramer/include/tornado/simple_httpclient.py b/telegramer/include/tornado/simple_httpclient.py deleted file mode 100644 index f99f391..0000000 --- a/telegramer/include/tornado/simple_httpclient.py +++ /dev/null @@ -1,699 +0,0 @@ -from tornado.escape import _unicode -from tornado import gen, version -from tornado.httpclient import ( - HTTPResponse, - HTTPError, - AsyncHTTPClient, - main, - _RequestProxy, - HTTPRequest, -) -from tornado import httputil -from tornado.http1connection import HTTP1Connection, HTTP1ConnectionParameters -from tornado.ioloop import IOLoop -from tornado.iostream import StreamClosedError, IOStream -from tornado.netutil import ( - Resolver, - OverrideResolver, - _client_ssl_defaults, - is_valid_ip, -) -from tornado.log import gen_log -from tornado.tcpclient import TCPClient - -import base64 -import collections -import copy -import functools -import re -import socket -import ssl -import sys -import time -from io import BytesIO -import urllib.parse - -from typing import Dict, Any, Callable, Optional, Type, Union -from types import TracebackType -import typing - -if typing.TYPE_CHECKING: - from typing import Deque, Tuple, List # noqa: F401 - - -class HTTPTimeoutError(HTTPError): - """Error raised by SimpleAsyncHTTPClient on timeout. - - For historical reasons, this is a subclass of `.HTTPClientError` - which simulates a response code of 599. - - .. versionadded:: 5.1 - """ - - def __init__(self, message: str) -> None: - super().__init__(599, message=message) - - def __str__(self) -> str: - return self.message or "Timeout" - - -class HTTPStreamClosedError(HTTPError): - """Error raised by SimpleAsyncHTTPClient when the underlying stream is closed. - - When a more specific exception is available (such as `ConnectionResetError`), - it may be raised instead of this one. - - For historical reasons, this is a subclass of `.HTTPClientError` - which simulates a response code of 599. - - .. versionadded:: 5.1 - """ - - def __init__(self, message: str) -> None: - super().__init__(599, message=message) - - def __str__(self) -> str: - return self.message or "Stream closed" - - -class SimpleAsyncHTTPClient(AsyncHTTPClient): - """Non-blocking HTTP client with no external dependencies. - - This class implements an HTTP 1.1 client on top of Tornado's IOStreams. - Some features found in the curl-based AsyncHTTPClient are not yet - supported. In particular, proxies are not supported, connections - are not reused, and callers cannot select the network interface to be - used. - """ - - def initialize( # type: ignore - self, - max_clients: int = 10, - hostname_mapping: Optional[Dict[str, str]] = None, - max_buffer_size: int = 104857600, - resolver: Optional[Resolver] = None, - defaults: Optional[Dict[str, Any]] = None, - max_header_size: Optional[int] = None, - max_body_size: Optional[int] = None, - ) -> None: - """Creates a AsyncHTTPClient. - - Only a single AsyncHTTPClient instance exists per IOLoop - in order to provide limitations on the number of pending connections. - ``force_instance=True`` may be used to suppress this behavior. - - Note that because of this implicit reuse, unless ``force_instance`` - is used, only the first call to the constructor actually uses - its arguments. It is recommended to use the ``configure`` method - instead of the constructor to ensure that arguments take effect. - - ``max_clients`` is the number of concurrent requests that can be - in progress; when this limit is reached additional requests will be - queued. Note that time spent waiting in this queue still counts - against the ``request_timeout``. - - ``hostname_mapping`` is a dictionary mapping hostnames to IP addresses. - It can be used to make local DNS changes when modifying system-wide - settings like ``/etc/hosts`` is not possible or desirable (e.g. in - unittests). - - ``max_buffer_size`` (default 100MB) is the number of bytes - that can be read into memory at once. ``max_body_size`` - (defaults to ``max_buffer_size``) is the largest response body - that the client will accept. Without a - ``streaming_callback``, the smaller of these two limits - applies; with a ``streaming_callback`` only ``max_body_size`` - does. - - .. versionchanged:: 4.2 - Added the ``max_body_size`` argument. - """ - super().initialize(defaults=defaults) - self.max_clients = max_clients - self.queue = ( - collections.deque() - ) # type: Deque[Tuple[object, HTTPRequest, Callable[[HTTPResponse], None]]] - self.active = ( - {} - ) # type: Dict[object, Tuple[HTTPRequest, Callable[[HTTPResponse], None]]] - self.waiting = ( - {} - ) # type: Dict[object, Tuple[HTTPRequest, Callable[[HTTPResponse], None], object]] - self.max_buffer_size = max_buffer_size - self.max_header_size = max_header_size - self.max_body_size = max_body_size - # TCPClient could create a Resolver for us, but we have to do it - # ourselves to support hostname_mapping. - if resolver: - self.resolver = resolver - self.own_resolver = False - else: - self.resolver = Resolver() - self.own_resolver = True - if hostname_mapping is not None: - self.resolver = OverrideResolver( - resolver=self.resolver, mapping=hostname_mapping - ) - self.tcp_client = TCPClient(resolver=self.resolver) - - def close(self) -> None: - super().close() - if self.own_resolver: - self.resolver.close() - self.tcp_client.close() - - def fetch_impl( - self, request: HTTPRequest, callback: Callable[[HTTPResponse], None] - ) -> None: - key = object() - self.queue.append((key, request, callback)) - assert request.connect_timeout is not None - assert request.request_timeout is not None - timeout_handle = None - if len(self.active) >= self.max_clients: - timeout = ( - min(request.connect_timeout, request.request_timeout) - or request.connect_timeout - or request.request_timeout - ) # min but skip zero - if timeout: - timeout_handle = self.io_loop.add_timeout( - self.io_loop.time() + timeout, - functools.partial(self._on_timeout, key, "in request queue"), - ) - self.waiting[key] = (request, callback, timeout_handle) - self._process_queue() - if self.queue: - gen_log.debug( - "max_clients limit reached, request queued. " - "%d active, %d queued requests." % (len(self.active), len(self.queue)) - ) - - def _process_queue(self) -> None: - while self.queue and len(self.active) < self.max_clients: - key, request, callback = self.queue.popleft() - if key not in self.waiting: - continue - self._remove_timeout(key) - self.active[key] = (request, callback) - release_callback = functools.partial(self._release_fetch, key) - self._handle_request(request, release_callback, callback) - - def _connection_class(self) -> type: - return _HTTPConnection - - def _handle_request( - self, - request: HTTPRequest, - release_callback: Callable[[], None], - final_callback: Callable[[HTTPResponse], None], - ) -> None: - self._connection_class()( - self, - request, - release_callback, - final_callback, - self.max_buffer_size, - self.tcp_client, - self.max_header_size, - self.max_body_size, - ) - - def _release_fetch(self, key: object) -> None: - del self.active[key] - self._process_queue() - - def _remove_timeout(self, key: object) -> None: - if key in self.waiting: - request, callback, timeout_handle = self.waiting[key] - if timeout_handle is not None: - self.io_loop.remove_timeout(timeout_handle) - del self.waiting[key] - - def _on_timeout(self, key: object, info: Optional[str] = None) -> None: - """Timeout callback of request. - - Construct a timeout HTTPResponse when a timeout occurs. - - :arg object key: A simple object to mark the request. - :info string key: More detailed timeout information. - """ - request, callback, timeout_handle = self.waiting[key] - self.queue.remove((key, request, callback)) - - error_message = "Timeout {0}".format(info) if info else "Timeout" - timeout_response = HTTPResponse( - request, - 599, - error=HTTPTimeoutError(error_message), - request_time=self.io_loop.time() - request.start_time, - ) - self.io_loop.add_callback(callback, timeout_response) - del self.waiting[key] - - -class _HTTPConnection(httputil.HTTPMessageDelegate): - _SUPPORTED_METHODS = set( - ["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] - ) - - def __init__( - self, - client: Optional[SimpleAsyncHTTPClient], - request: HTTPRequest, - release_callback: Callable[[], None], - final_callback: Callable[[HTTPResponse], None], - max_buffer_size: int, - tcp_client: TCPClient, - max_header_size: int, - max_body_size: int, - ) -> None: - self.io_loop = IOLoop.current() - self.start_time = self.io_loop.time() - self.start_wall_time = time.time() - self.client = client - self.request = request - self.release_callback = release_callback - self.final_callback = final_callback - self.max_buffer_size = max_buffer_size - self.tcp_client = tcp_client - self.max_header_size = max_header_size - self.max_body_size = max_body_size - self.code = None # type: Optional[int] - self.headers = None # type: Optional[httputil.HTTPHeaders] - self.chunks = [] # type: List[bytes] - self._decompressor = None - # Timeout handle returned by IOLoop.add_timeout - self._timeout = None # type: object - self._sockaddr = None - IOLoop.current().add_future( - gen.convert_yielded(self.run()), lambda f: f.result() - ) - - async def run(self) -> None: - try: - self.parsed = urllib.parse.urlsplit(_unicode(self.request.url)) - if self.parsed.scheme not in ("http", "https"): - raise ValueError("Unsupported url scheme: %s" % self.request.url) - # urlsplit results have hostname and port results, but they - # didn't support ipv6 literals until python 2.7. - netloc = self.parsed.netloc - if "@" in netloc: - userpass, _, netloc = netloc.rpartition("@") - host, port = httputil.split_host_and_port(netloc) - if port is None: - port = 443 if self.parsed.scheme == "https" else 80 - if re.match(r"^\[.*\]$", host): - # raw ipv6 addresses in urls are enclosed in brackets - host = host[1:-1] - self.parsed_hostname = host # save final host for _on_connect - - if self.request.allow_ipv6 is False: - af = socket.AF_INET - else: - af = socket.AF_UNSPEC - - ssl_options = self._get_ssl_options(self.parsed.scheme) - - source_ip = None - if self.request.network_interface: - if is_valid_ip(self.request.network_interface): - source_ip = self.request.network_interface - else: - raise ValueError( - "Unrecognized IPv4 or IPv6 address for network_interface, got %r" - % (self.request.network_interface,) - ) - - timeout = ( - min(self.request.connect_timeout, self.request.request_timeout) - or self.request.connect_timeout - or self.request.request_timeout - ) # min but skip zero - if timeout: - self._timeout = self.io_loop.add_timeout( - self.start_time + timeout, - functools.partial(self._on_timeout, "while connecting"), - ) - stream = await self.tcp_client.connect( - host, - port, - af=af, - ssl_options=ssl_options, - max_buffer_size=self.max_buffer_size, - source_ip=source_ip, - ) - - if self.final_callback is None: - # final_callback is cleared if we've hit our timeout. - stream.close() - return - self.stream = stream - self.stream.set_close_callback(self.on_connection_close) - self._remove_timeout() - if self.final_callback is None: - return - if self.request.request_timeout: - self._timeout = self.io_loop.add_timeout( - self.start_time + self.request.request_timeout, - functools.partial(self._on_timeout, "during request"), - ) - if ( - self.request.method not in self._SUPPORTED_METHODS - and not self.request.allow_nonstandard_methods - ): - raise KeyError("unknown method %s" % self.request.method) - for key in ( - "proxy_host", - "proxy_port", - "proxy_username", - "proxy_password", - "proxy_auth_mode", - ): - if getattr(self.request, key, None): - raise NotImplementedError("%s not supported" % key) - if "Connection" not in self.request.headers: - self.request.headers["Connection"] = "close" - if "Host" not in self.request.headers: - if "@" in self.parsed.netloc: - self.request.headers["Host"] = self.parsed.netloc.rpartition("@")[ - -1 - ] - else: - self.request.headers["Host"] = self.parsed.netloc - username, password = None, None - if self.parsed.username is not None: - username, password = self.parsed.username, self.parsed.password - elif self.request.auth_username is not None: - username = self.request.auth_username - password = self.request.auth_password or "" - if username is not None: - assert password is not None - if self.request.auth_mode not in (None, "basic"): - raise ValueError("unsupported auth_mode %s", self.request.auth_mode) - self.request.headers["Authorization"] = "Basic " + _unicode( - base64.b64encode( - httputil.encode_username_password(username, password) - ) - ) - if self.request.user_agent: - self.request.headers["User-Agent"] = self.request.user_agent - elif self.request.headers.get("User-Agent") is None: - self.request.headers["User-Agent"] = "Tornado/{}".format(version) - if not self.request.allow_nonstandard_methods: - # Some HTTP methods nearly always have bodies while others - # almost never do. Fail in this case unless the user has - # opted out of sanity checks with allow_nonstandard_methods. - body_expected = self.request.method in ("POST", "PATCH", "PUT") - body_present = ( - self.request.body is not None - or self.request.body_producer is not None - ) - if (body_expected and not body_present) or ( - body_present and not body_expected - ): - raise ValueError( - "Body must %sbe None for method %s (unless " - "allow_nonstandard_methods is true)" - % ("not " if body_expected else "", self.request.method) - ) - if self.request.expect_100_continue: - self.request.headers["Expect"] = "100-continue" - if self.request.body is not None: - # When body_producer is used the caller is responsible for - # setting Content-Length (or else chunked encoding will be used). - self.request.headers["Content-Length"] = str(len(self.request.body)) - if ( - self.request.method == "POST" - and "Content-Type" not in self.request.headers - ): - self.request.headers[ - "Content-Type" - ] = "application/x-www-form-urlencoded" - if self.request.decompress_response: - self.request.headers["Accept-Encoding"] = "gzip" - req_path = (self.parsed.path or "/") + ( - ("?" + self.parsed.query) if self.parsed.query else "" - ) - self.connection = self._create_connection(stream) - start_line = httputil.RequestStartLine(self.request.method, req_path, "") - self.connection.write_headers(start_line, self.request.headers) - if self.request.expect_100_continue: - await self.connection.read_response(self) - else: - await self._write_body(True) - except Exception: - if not self._handle_exception(*sys.exc_info()): - raise - - def _get_ssl_options( - self, scheme: str - ) -> Union[None, Dict[str, Any], ssl.SSLContext]: - if scheme == "https": - if self.request.ssl_options is not None: - return self.request.ssl_options - # If we are using the defaults, don't construct a - # new SSLContext. - if ( - self.request.validate_cert - and self.request.ca_certs is None - and self.request.client_cert is None - and self.request.client_key is None - ): - return _client_ssl_defaults - ssl_ctx = ssl.create_default_context( - ssl.Purpose.SERVER_AUTH, cafile=self.request.ca_certs - ) - if not self.request.validate_cert: - ssl_ctx.check_hostname = False - ssl_ctx.verify_mode = ssl.CERT_NONE - if self.request.client_cert is not None: - ssl_ctx.load_cert_chain( - self.request.client_cert, self.request.client_key - ) - if hasattr(ssl, "OP_NO_COMPRESSION"): - # See netutil.ssl_options_to_context - ssl_ctx.options |= ssl.OP_NO_COMPRESSION - return ssl_ctx - return None - - def _on_timeout(self, info: Optional[str] = None) -> None: - """Timeout callback of _HTTPConnection instance. - - Raise a `HTTPTimeoutError` when a timeout occurs. - - :info string key: More detailed timeout information. - """ - self._timeout = None - error_message = "Timeout {0}".format(info) if info else "Timeout" - if self.final_callback is not None: - self._handle_exception( - HTTPTimeoutError, HTTPTimeoutError(error_message), None - ) - - def _remove_timeout(self) -> None: - if self._timeout is not None: - self.io_loop.remove_timeout(self._timeout) - self._timeout = None - - def _create_connection(self, stream: IOStream) -> HTTP1Connection: - stream.set_nodelay(True) - connection = HTTP1Connection( - stream, - True, - HTTP1ConnectionParameters( - no_keep_alive=True, - max_header_size=self.max_header_size, - max_body_size=self.max_body_size, - decompress=bool(self.request.decompress_response), - ), - self._sockaddr, - ) - return connection - - async def _write_body(self, start_read: bool) -> None: - if self.request.body is not None: - self.connection.write(self.request.body) - elif self.request.body_producer is not None: - fut = self.request.body_producer(self.connection.write) - if fut is not None: - await fut - self.connection.finish() - if start_read: - try: - await self.connection.read_response(self) - except StreamClosedError: - if not self._handle_exception(*sys.exc_info()): - raise - - def _release(self) -> None: - if self.release_callback is not None: - release_callback = self.release_callback - self.release_callback = None # type: ignore - release_callback() - - def _run_callback(self, response: HTTPResponse) -> None: - self._release() - if self.final_callback is not None: - final_callback = self.final_callback - self.final_callback = None # type: ignore - self.io_loop.add_callback(final_callback, response) - - def _handle_exception( - self, - typ: "Optional[Type[BaseException]]", - value: Optional[BaseException], - tb: Optional[TracebackType], - ) -> bool: - if self.final_callback: - self._remove_timeout() - if isinstance(value, StreamClosedError): - if value.real_error is None: - value = HTTPStreamClosedError("Stream closed") - else: - value = value.real_error - self._run_callback( - HTTPResponse( - self.request, - 599, - error=value, - request_time=self.io_loop.time() - self.start_time, - start_time=self.start_wall_time, - ) - ) - - if hasattr(self, "stream"): - # TODO: this may cause a StreamClosedError to be raised - # by the connection's Future. Should we cancel the - # connection more gracefully? - self.stream.close() - return True - else: - # If our callback has already been called, we are probably - # catching an exception that is not caused by us but rather - # some child of our callback. Rather than drop it on the floor, - # pass it along, unless it's just the stream being closed. - return isinstance(value, StreamClosedError) - - def on_connection_close(self) -> None: - if self.final_callback is not None: - message = "Connection closed" - if self.stream.error: - raise self.stream.error - try: - raise HTTPStreamClosedError(message) - except HTTPStreamClosedError: - self._handle_exception(*sys.exc_info()) - - async def headers_received( - self, - first_line: Union[httputil.ResponseStartLine, httputil.RequestStartLine], - headers: httputil.HTTPHeaders, - ) -> None: - assert isinstance(first_line, httputil.ResponseStartLine) - if self.request.expect_100_continue and first_line.code == 100: - await self._write_body(False) - return - self.code = first_line.code - self.reason = first_line.reason - self.headers = headers - - if self._should_follow_redirect(): - return - - if self.request.header_callback is not None: - # Reassemble the start line. - self.request.header_callback("%s %s %s\r\n" % first_line) - for k, v in self.headers.get_all(): - self.request.header_callback("%s: %s\r\n" % (k, v)) - self.request.header_callback("\r\n") - - def _should_follow_redirect(self) -> bool: - if self.request.follow_redirects: - assert self.request.max_redirects is not None - return ( - self.code in (301, 302, 303, 307, 308) - and self.request.max_redirects > 0 - and self.headers is not None - and self.headers.get("Location") is not None - ) - return False - - def finish(self) -> None: - assert self.code is not None - data = b"".join(self.chunks) - self._remove_timeout() - original_request = getattr(self.request, "original_request", self.request) - if self._should_follow_redirect(): - assert isinstance(self.request, _RequestProxy) - new_request = copy.copy(self.request.request) - new_request.url = urllib.parse.urljoin( - self.request.url, self.headers["Location"] - ) - new_request.max_redirects = self.request.max_redirects - 1 - del new_request.headers["Host"] - # https://tools.ietf.org/html/rfc7231#section-6.4 - # - # The original HTTP spec said that after a 301 or 302 - # redirect, the request method should be preserved. - # However, browsers implemented this by changing the - # method to GET, and the behavior stuck. 303 redirects - # always specified this POST-to-GET behavior, arguably - # for *all* methods, but libcurl < 7.70 only does this - # for POST, while libcurl >= 7.70 does it for other methods. - if (self.code == 303 and self.request.method != "HEAD") or ( - self.code in (301, 302) and self.request.method == "POST" - ): - new_request.method = "GET" - new_request.body = None - for h in [ - "Content-Length", - "Content-Type", - "Content-Encoding", - "Transfer-Encoding", - ]: - try: - del self.request.headers[h] - except KeyError: - pass - new_request.original_request = original_request - final_callback = self.final_callback - self.final_callback = None - self._release() - fut = self.client.fetch(new_request, raise_error=False) - fut.add_done_callback(lambda f: final_callback(f.result())) - self._on_end_request() - return - if self.request.streaming_callback: - buffer = BytesIO() - else: - buffer = BytesIO(data) # TODO: don't require one big string? - response = HTTPResponse( - original_request, - self.code, - reason=getattr(self, "reason", None), - headers=self.headers, - request_time=self.io_loop.time() - self.start_time, - start_time=self.start_wall_time, - buffer=buffer, - effective_url=self.request.url, - ) - self._run_callback(response) - self._on_end_request() - - def _on_end_request(self) -> None: - self.stream.close() - - def data_received(self, chunk: bytes) -> None: - if self._should_follow_redirect(): - # We're going to follow a redirect so just discard the body. - return - if self.request.streaming_callback is not None: - self.request.streaming_callback(chunk) - else: - self.chunks.append(chunk) - - -if __name__ == "__main__": - AsyncHTTPClient.configure(SimpleAsyncHTTPClient) - main() diff --git a/telegramer/include/tornado/speedups.c b/telegramer/include/tornado/speedups.c deleted file mode 100644 index 525d660..0000000 --- a/telegramer/include/tornado/speedups.c +++ /dev/null @@ -1,70 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -#include - -static PyObject* websocket_mask(PyObject* self, PyObject* args) { - const char* mask; - Py_ssize_t mask_len; - uint32_t uint32_mask; - uint64_t uint64_mask; - const char* data; - Py_ssize_t data_len; - Py_ssize_t i; - PyObject* result; - char* buf; - - if (!PyArg_ParseTuple(args, "s#s#", &mask, &mask_len, &data, &data_len)) { - return NULL; - } - - uint32_mask = ((uint32_t*)mask)[0]; - - result = PyBytes_FromStringAndSize(NULL, data_len); - if (!result) { - return NULL; - } - buf = PyBytes_AsString(result); - - if (sizeof(size_t) >= 8) { - uint64_mask = uint32_mask; - uint64_mask = (uint64_mask << 32) | uint32_mask; - - while (data_len >= 8) { - ((uint64_t*)buf)[0] = ((uint64_t*)data)[0] ^ uint64_mask; - data += 8; - buf += 8; - data_len -= 8; - } - } - - while (data_len >= 4) { - ((uint32_t*)buf)[0] = ((uint32_t*)data)[0] ^ uint32_mask; - data += 4; - buf += 4; - data_len -= 4; - } - - for (i = 0; i < data_len; i++) { - buf[i] = data[i] ^ mask[i]; - } - - return result; -} - -static PyMethodDef methods[] = { - {"websocket_mask", websocket_mask, METH_VARARGS, ""}, - {NULL, NULL, 0, NULL} -}; - -static struct PyModuleDef speedupsmodule = { - PyModuleDef_HEAD_INIT, - "speedups", - NULL, - -1, - methods -}; - -PyMODINIT_FUNC -PyInit_speedups(void) { - return PyModule_Create(&speedupsmodule); -} diff --git a/telegramer/include/tornado/tcpclient.py b/telegramer/include/tornado/tcpclient.py deleted file mode 100644 index e2d682e..0000000 --- a/telegramer/include/tornado/tcpclient.py +++ /dev/null @@ -1,328 +0,0 @@ -# -# Copyright 2014 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A non-blocking TCP connection factory. -""" - -import functools -import socket -import numbers -import datetime -import ssl - -from tornado.concurrent import Future, future_add_done_callback -from tornado.ioloop import IOLoop -from tornado.iostream import IOStream -from tornado import gen -from tornado.netutil import Resolver -from tornado.gen import TimeoutError - -from typing import Any, Union, Dict, Tuple, List, Callable, Iterator, Optional, Set - -_INITIAL_CONNECT_TIMEOUT = 0.3 - - -class _Connector(object): - """A stateless implementation of the "Happy Eyeballs" algorithm. - - "Happy Eyeballs" is documented in RFC6555 as the recommended practice - for when both IPv4 and IPv6 addresses are available. - - In this implementation, we partition the addresses by family, and - make the first connection attempt to whichever address was - returned first by ``getaddrinfo``. If that connection fails or - times out, we begin a connection in parallel to the first address - of the other family. If there are additional failures we retry - with other addresses, keeping one connection attempt per family - in flight at a time. - - http://tools.ietf.org/html/rfc6555 - - """ - - def __init__( - self, - addrinfo: List[Tuple], - connect: Callable[ - [socket.AddressFamily, Tuple], Tuple[IOStream, "Future[IOStream]"] - ], - ) -> None: - self.io_loop = IOLoop.current() - self.connect = connect - - self.future = ( - Future() - ) # type: Future[Tuple[socket.AddressFamily, Any, IOStream]] - self.timeout = None # type: Optional[object] - self.connect_timeout = None # type: Optional[object] - self.last_error = None # type: Optional[Exception] - self.remaining = len(addrinfo) - self.primary_addrs, self.secondary_addrs = self.split(addrinfo) - self.streams = set() # type: Set[IOStream] - - @staticmethod - def split( - addrinfo: List[Tuple], - ) -> Tuple[ - List[Tuple[socket.AddressFamily, Tuple]], - List[Tuple[socket.AddressFamily, Tuple]], - ]: - """Partition the ``addrinfo`` list by address family. - - Returns two lists. The first list contains the first entry from - ``addrinfo`` and all others with the same family, and the - second list contains all other addresses (normally one list will - be AF_INET and the other AF_INET6, although non-standard resolvers - may return additional families). - """ - primary = [] - secondary = [] - primary_af = addrinfo[0][0] - for af, addr in addrinfo: - if af == primary_af: - primary.append((af, addr)) - else: - secondary.append((af, addr)) - return primary, secondary - - def start( - self, - timeout: float = _INITIAL_CONNECT_TIMEOUT, - connect_timeout: Optional[Union[float, datetime.timedelta]] = None, - ) -> "Future[Tuple[socket.AddressFamily, Any, IOStream]]": - self.try_connect(iter(self.primary_addrs)) - self.set_timeout(timeout) - if connect_timeout is not None: - self.set_connect_timeout(connect_timeout) - return self.future - - def try_connect(self, addrs: Iterator[Tuple[socket.AddressFamily, Tuple]]) -> None: - try: - af, addr = next(addrs) - except StopIteration: - # We've reached the end of our queue, but the other queue - # might still be working. Send a final error on the future - # only when both queues are finished. - if self.remaining == 0 and not self.future.done(): - self.future.set_exception( - self.last_error or IOError("connection failed") - ) - return - stream, future = self.connect(af, addr) - self.streams.add(stream) - future_add_done_callback( - future, functools.partial(self.on_connect_done, addrs, af, addr) - ) - - def on_connect_done( - self, - addrs: Iterator[Tuple[socket.AddressFamily, Tuple]], - af: socket.AddressFamily, - addr: Tuple, - future: "Future[IOStream]", - ) -> None: - self.remaining -= 1 - try: - stream = future.result() - except Exception as e: - if self.future.done(): - return - # Error: try again (but remember what happened so we have an - # error to raise in the end) - self.last_error = e - self.try_connect(addrs) - if self.timeout is not None: - # If the first attempt failed, don't wait for the - # timeout to try an address from the secondary queue. - self.io_loop.remove_timeout(self.timeout) - self.on_timeout() - return - self.clear_timeouts() - if self.future.done(): - # This is a late arrival; just drop it. - stream.close() - else: - self.streams.discard(stream) - self.future.set_result((af, addr, stream)) - self.close_streams() - - def set_timeout(self, timeout: float) -> None: - self.timeout = self.io_loop.add_timeout( - self.io_loop.time() + timeout, self.on_timeout - ) - - def on_timeout(self) -> None: - self.timeout = None - if not self.future.done(): - self.try_connect(iter(self.secondary_addrs)) - - def clear_timeout(self) -> None: - if self.timeout is not None: - self.io_loop.remove_timeout(self.timeout) - - def set_connect_timeout( - self, connect_timeout: Union[float, datetime.timedelta] - ) -> None: - self.connect_timeout = self.io_loop.add_timeout( - connect_timeout, self.on_connect_timeout - ) - - def on_connect_timeout(self) -> None: - if not self.future.done(): - self.future.set_exception(TimeoutError()) - self.close_streams() - - def clear_timeouts(self) -> None: - if self.timeout is not None: - self.io_loop.remove_timeout(self.timeout) - if self.connect_timeout is not None: - self.io_loop.remove_timeout(self.connect_timeout) - - def close_streams(self) -> None: - for stream in self.streams: - stream.close() - - -class TCPClient(object): - """A non-blocking TCP connection factory. - - .. versionchanged:: 5.0 - The ``io_loop`` argument (deprecated since version 4.1) has been removed. - """ - - def __init__(self, resolver: Optional[Resolver] = None) -> None: - if resolver is not None: - self.resolver = resolver - self._own_resolver = False - else: - self.resolver = Resolver() - self._own_resolver = True - - def close(self) -> None: - if self._own_resolver: - self.resolver.close() - - async def connect( - self, - host: str, - port: int, - af: socket.AddressFamily = socket.AF_UNSPEC, - ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None, - max_buffer_size: Optional[int] = None, - source_ip: Optional[str] = None, - source_port: Optional[int] = None, - timeout: Optional[Union[float, datetime.timedelta]] = None, - ) -> IOStream: - """Connect to the given host and port. - - Asynchronously returns an `.IOStream` (or `.SSLIOStream` if - ``ssl_options`` is not None). - - Using the ``source_ip`` kwarg, one can specify the source - IP address to use when establishing the connection. - In case the user needs to resolve and - use a specific interface, it has to be handled outside - of Tornado as this depends very much on the platform. - - Raises `TimeoutError` if the input future does not complete before - ``timeout``, which may be specified in any form allowed by - `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or an absolute time - relative to `.IOLoop.time`) - - Similarly, when the user requires a certain source port, it can - be specified using the ``source_port`` arg. - - .. versionchanged:: 4.5 - Added the ``source_ip`` and ``source_port`` arguments. - - .. versionchanged:: 5.0 - Added the ``timeout`` argument. - """ - if timeout is not None: - if isinstance(timeout, numbers.Real): - timeout = IOLoop.current().time() + timeout - elif isinstance(timeout, datetime.timedelta): - timeout = IOLoop.current().time() + timeout.total_seconds() - else: - raise TypeError("Unsupported timeout %r" % timeout) - if timeout is not None: - addrinfo = await gen.with_timeout( - timeout, self.resolver.resolve(host, port, af) - ) - else: - addrinfo = await self.resolver.resolve(host, port, af) - connector = _Connector( - addrinfo, - functools.partial( - self._create_stream, - max_buffer_size, - source_ip=source_ip, - source_port=source_port, - ), - ) - af, addr, stream = await connector.start(connect_timeout=timeout) - # TODO: For better performance we could cache the (af, addr) - # information here and re-use it on subsequent connections to - # the same host. (http://tools.ietf.org/html/rfc6555#section-4.2) - if ssl_options is not None: - if timeout is not None: - stream = await gen.with_timeout( - timeout, - stream.start_tls( - False, ssl_options=ssl_options, server_hostname=host - ), - ) - else: - stream = await stream.start_tls( - False, ssl_options=ssl_options, server_hostname=host - ) - return stream - - def _create_stream( - self, - max_buffer_size: int, - af: socket.AddressFamily, - addr: Tuple, - source_ip: Optional[str] = None, - source_port: Optional[int] = None, - ) -> Tuple[IOStream, "Future[IOStream]"]: - # Always connect in plaintext; we'll convert to ssl if necessary - # after one connection has completed. - source_port_bind = source_port if isinstance(source_port, int) else 0 - source_ip_bind = source_ip - if source_port_bind and not source_ip: - # User required a specific port, but did not specify - # a certain source IP, will bind to the default loopback. - source_ip_bind = "::1" if af == socket.AF_INET6 else "127.0.0.1" - # Trying to use the same address family as the requested af socket: - # - 127.0.0.1 for IPv4 - # - ::1 for IPv6 - socket_obj = socket.socket(af) - if source_port_bind or source_ip_bind: - # If the user requires binding also to a specific IP/port. - try: - socket_obj.bind((source_ip_bind, source_port_bind)) - except socket.error: - socket_obj.close() - # Fail loudly if unable to use the IP/port. - raise - try: - stream = IOStream(socket_obj, max_buffer_size=max_buffer_size) - except socket.error as e: - fu = Future() # type: Future[IOStream] - fu.set_exception(e) - return stream, fu - else: - return stream, stream.connect(addr) diff --git a/telegramer/include/tornado/tcpserver.py b/telegramer/include/tornado/tcpserver.py deleted file mode 100644 index 476ffc9..0000000 --- a/telegramer/include/tornado/tcpserver.py +++ /dev/null @@ -1,334 +0,0 @@ -# -# Copyright 2011 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A non-blocking, single-threaded TCP server.""" - -import errno -import os -import socket -import ssl - -from tornado import gen -from tornado.log import app_log -from tornado.ioloop import IOLoop -from tornado.iostream import IOStream, SSLIOStream -from tornado.netutil import bind_sockets, add_accept_handler, ssl_wrap_socket -from tornado import process -from tornado.util import errno_from_exception - -import typing -from typing import Union, Dict, Any, Iterable, Optional, Awaitable - -if typing.TYPE_CHECKING: - from typing import Callable, List # noqa: F401 - - -class TCPServer(object): - r"""A non-blocking, single-threaded TCP server. - - To use `TCPServer`, define a subclass which overrides the `handle_stream` - method. For example, a simple echo server could be defined like this:: - - from tornado.tcpserver import TCPServer - from tornado.iostream import StreamClosedError - from tornado import gen - - class EchoServer(TCPServer): - async def handle_stream(self, stream, address): - while True: - try: - data = await stream.read_until(b"\n") - await stream.write(data) - except StreamClosedError: - break - - To make this server serve SSL traffic, send the ``ssl_options`` keyword - argument with an `ssl.SSLContext` object. For compatibility with older - versions of Python ``ssl_options`` may also be a dictionary of keyword - arguments for the `ssl.wrap_socket` method.:: - - ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"), - os.path.join(data_dir, "mydomain.key")) - TCPServer(ssl_options=ssl_ctx) - - `TCPServer` initialization follows one of three patterns: - - 1. `listen`: simple single-process:: - - server = TCPServer() - server.listen(8888) - IOLoop.current().start() - - 2. `bind`/`start`: simple multi-process:: - - server = TCPServer() - server.bind(8888) - server.start(0) # Forks multiple sub-processes - IOLoop.current().start() - - When using this interface, an `.IOLoop` must *not* be passed - to the `TCPServer` constructor. `start` will always start - the server on the default singleton `.IOLoop`. - - 3. `add_sockets`: advanced multi-process:: - - sockets = bind_sockets(8888) - tornado.process.fork_processes(0) - server = TCPServer() - server.add_sockets(sockets) - IOLoop.current().start() - - The `add_sockets` interface is more complicated, but it can be - used with `tornado.process.fork_processes` to give you more - flexibility in when the fork happens. `add_sockets` can - also be used in single-process servers if you want to create - your listening sockets in some way other than - `~tornado.netutil.bind_sockets`. - - .. versionadded:: 3.1 - The ``max_buffer_size`` argument. - - .. versionchanged:: 5.0 - The ``io_loop`` argument has been removed. - """ - - def __init__( - self, - ssl_options: Optional[Union[Dict[str, Any], ssl.SSLContext]] = None, - max_buffer_size: Optional[int] = None, - read_chunk_size: Optional[int] = None, - ) -> None: - self.ssl_options = ssl_options - self._sockets = {} # type: Dict[int, socket.socket] - self._handlers = {} # type: Dict[int, Callable[[], None]] - self._pending_sockets = [] # type: List[socket.socket] - self._started = False - self._stopped = False - self.max_buffer_size = max_buffer_size - self.read_chunk_size = read_chunk_size - - # Verify the SSL options. Otherwise we don't get errors until clients - # connect. This doesn't verify that the keys are legitimate, but - # the SSL module doesn't do that until there is a connected socket - # which seems like too much work - if self.ssl_options is not None and isinstance(self.ssl_options, dict): - # Only certfile is required: it can contain both keys - if "certfile" not in self.ssl_options: - raise KeyError('missing key "certfile" in ssl_options') - - if not os.path.exists(self.ssl_options["certfile"]): - raise ValueError( - 'certfile "%s" does not exist' % self.ssl_options["certfile"] - ) - if "keyfile" in self.ssl_options and not os.path.exists( - self.ssl_options["keyfile"] - ): - raise ValueError( - 'keyfile "%s" does not exist' % self.ssl_options["keyfile"] - ) - - def listen(self, port: int, address: str = "") -> None: - """Starts accepting connections on the given port. - - This method may be called more than once to listen on multiple ports. - `listen` takes effect immediately; it is not necessary to call - `TCPServer.start` afterwards. It is, however, necessary to start - the `.IOLoop`. - """ - sockets = bind_sockets(port, address=address) - self.add_sockets(sockets) - - def add_sockets(self, sockets: Iterable[socket.socket]) -> None: - """Makes this server start accepting connections on the given sockets. - - The ``sockets`` parameter is a list of socket objects such as - those returned by `~tornado.netutil.bind_sockets`. - `add_sockets` is typically used in combination with that - method and `tornado.process.fork_processes` to provide greater - control over the initialization of a multi-process server. - """ - for sock in sockets: - self._sockets[sock.fileno()] = sock - self._handlers[sock.fileno()] = add_accept_handler( - sock, self._handle_connection - ) - - def add_socket(self, socket: socket.socket) -> None: - """Singular version of `add_sockets`. Takes a single socket object.""" - self.add_sockets([socket]) - - def bind( - self, - port: int, - address: Optional[str] = None, - family: socket.AddressFamily = socket.AF_UNSPEC, - backlog: int = 128, - reuse_port: bool = False, - ) -> None: - """Binds this server to the given port on the given address. - - To start the server, call `start`. If you want to run this server - in a single process, you can call `listen` as a shortcut to the - sequence of `bind` and `start` calls. - - Address may be either an IP address or hostname. If it's a hostname, - the server will listen on all IP addresses associated with the - name. Address may be an empty string or None to listen on all - available interfaces. Family may be set to either `socket.AF_INET` - or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise - both will be used if available. - - The ``backlog`` argument has the same meaning as for - `socket.listen `. The ``reuse_port`` argument - has the same meaning as for `.bind_sockets`. - - This method may be called multiple times prior to `start` to listen - on multiple ports or interfaces. - - .. versionchanged:: 4.4 - Added the ``reuse_port`` argument. - """ - sockets = bind_sockets( - port, address=address, family=family, backlog=backlog, reuse_port=reuse_port - ) - if self._started: - self.add_sockets(sockets) - else: - self._pending_sockets.extend(sockets) - - def start( - self, num_processes: Optional[int] = 1, max_restarts: Optional[int] = None - ) -> None: - """Starts this server in the `.IOLoop`. - - By default, we run the server in this process and do not fork any - additional child process. - - If num_processes is ``None`` or <= 0, we detect the number of cores - available on this machine and fork that number of child - processes. If num_processes is given and > 1, we fork that - specific number of sub-processes. - - Since we use processes and not threads, there is no shared memory - between any server code. - - Note that multiple processes are not compatible with the autoreload - module (or the ``autoreload=True`` option to `tornado.web.Application` - which defaults to True when ``debug=True``). - When using multiple processes, no IOLoops can be created or - referenced until after the call to ``TCPServer.start(n)``. - - Values of ``num_processes`` other than 1 are not supported on Windows. - - The ``max_restarts`` argument is passed to `.fork_processes`. - - .. versionchanged:: 6.0 - - Added ``max_restarts`` argument. - """ - assert not self._started - self._started = True - if num_processes != 1: - process.fork_processes(num_processes, max_restarts) - sockets = self._pending_sockets - self._pending_sockets = [] - self.add_sockets(sockets) - - def stop(self) -> None: - """Stops listening for new connections. - - Requests currently in progress may still continue after the - server is stopped. - """ - if self._stopped: - return - self._stopped = True - for fd, sock in self._sockets.items(): - assert sock.fileno() == fd - # Unregister socket from IOLoop - self._handlers.pop(fd)() - sock.close() - - def handle_stream( - self, stream: IOStream, address: tuple - ) -> Optional[Awaitable[None]]: - """Override to handle a new `.IOStream` from an incoming connection. - - This method may be a coroutine; if so any exceptions it raises - asynchronously will be logged. Accepting of incoming connections - will not be blocked by this coroutine. - - If this `TCPServer` is configured for SSL, ``handle_stream`` - may be called before the SSL handshake has completed. Use - `.SSLIOStream.wait_for_handshake` if you need to verify the client's - certificate or use NPN/ALPN. - - .. versionchanged:: 4.2 - Added the option for this method to be a coroutine. - """ - raise NotImplementedError() - - def _handle_connection(self, connection: socket.socket, address: Any) -> None: - if self.ssl_options is not None: - assert ssl, "Python 2.6+ and OpenSSL required for SSL" - try: - connection = ssl_wrap_socket( - connection, - self.ssl_options, - server_side=True, - do_handshake_on_connect=False, - ) - except ssl.SSLError as err: - if err.args[0] == ssl.SSL_ERROR_EOF: - return connection.close() - else: - raise - except socket.error as err: - # If the connection is closed immediately after it is created - # (as in a port scan), we can get one of several errors. - # wrap_socket makes an internal call to getpeername, - # which may return either EINVAL (Mac OS X) or ENOTCONN - # (Linux). If it returns ENOTCONN, this error is - # silently swallowed by the ssl module, so we need to - # catch another error later on (AttributeError in - # SSLIOStream._do_ssl_handshake). - # To test this behavior, try nmap with the -sT flag. - # https://github.com/tornadoweb/tornado/pull/750 - if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL): - return connection.close() - else: - raise - try: - if self.ssl_options is not None: - stream = SSLIOStream( - connection, - max_buffer_size=self.max_buffer_size, - read_chunk_size=self.read_chunk_size, - ) # type: IOStream - else: - stream = IOStream( - connection, - max_buffer_size=self.max_buffer_size, - read_chunk_size=self.read_chunk_size, - ) - - future = self.handle_stream(stream, address) - if future is not None: - IOLoop.current().add_future( - gen.convert_yielded(future), lambda f: f.result() - ) - except Exception: - app_log.error("Error in connection callback", exc_info=True) diff --git a/telegramer/include/tornado/template.py b/telegramer/include/tornado/template.py deleted file mode 100644 index 2e6e0a2..0000000 --- a/telegramer/include/tornado/template.py +++ /dev/null @@ -1,1048 +0,0 @@ -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A simple template system that compiles templates to Python code. - -Basic usage looks like:: - - t = template.Template("{{ myvalue }}") - print(t.generate(myvalue="XXX")) - -`Loader` is a class that loads templates from a root directory and caches -the compiled templates:: - - loader = template.Loader("/home/btaylor") - print(loader.load("test.html").generate(myvalue="XXX")) - -We compile all templates to raw Python. Error-reporting is currently... uh, -interesting. Syntax for the templates:: - - ### base.html - - - {% block title %}Default title{% end %} - - -
    - {% for student in students %} - {% block student %} -
  • {{ escape(student.name) }}
  • - {% end %} - {% end %} -
- - - - ### bold.html - {% extends "base.html" %} - - {% block title %}A bolder title{% end %} - - {% block student %} -
  • {{ escape(student.name) }}
  • - {% end %} - -Unlike most other template systems, we do not put any restrictions on the -expressions you can include in your statements. ``if`` and ``for`` blocks get -translated exactly into Python, so you can do complex expressions like:: - - {% for student in [p for p in people if p.student and p.age > 23] %} -
  • {{ escape(student.name) }}
  • - {% end %} - -Translating directly to Python means you can apply functions to expressions -easily, like the ``escape()`` function in the examples above. You can pass -functions in to your template just like any other variable -(In a `.RequestHandler`, override `.RequestHandler.get_template_namespace`):: - - ### Python code - def add(x, y): - return x + y - template.execute(add=add) - - ### The template - {{ add(1, 2) }} - -We provide the functions `escape() <.xhtml_escape>`, `.url_escape()`, -`.json_encode()`, and `.squeeze()` to all templates by default. - -Typical applications do not create `Template` or `Loader` instances by -hand, but instead use the `~.RequestHandler.render` and -`~.RequestHandler.render_string` methods of -`tornado.web.RequestHandler`, which load templates automatically based -on the ``template_path`` `.Application` setting. - -Variable names beginning with ``_tt_`` are reserved by the template -system and should not be used by application code. - -Syntax Reference ----------------- - -Template expressions are surrounded by double curly braces: ``{{ ... }}``. -The contents may be any python expression, which will be escaped according -to the current autoescape setting and inserted into the output. Other -template directives use ``{% %}``. - -To comment out a section so that it is omitted from the output, surround it -with ``{# ... #}``. - - -To include a literal ``{{``, ``{%``, or ``{#`` in the output, escape them as -``{{!``, ``{%!``, and ``{#!``, respectively. - - -``{% apply *function* %}...{% end %}`` - Applies a function to the output of all template code between ``apply`` - and ``end``:: - - {% apply linkify %}{{name}} said: {{message}}{% end %} - - Note that as an implementation detail apply blocks are implemented - as nested functions and thus may interact strangely with variables - set via ``{% set %}``, or the use of ``{% break %}`` or ``{% continue %}`` - within loops. - -``{% autoescape *function* %}`` - Sets the autoescape mode for the current file. This does not affect - other files, even those referenced by ``{% include %}``. Note that - autoescaping can also be configured globally, at the `.Application` - or `Loader`.:: - - {% autoescape xhtml_escape %} - {% autoescape None %} - -``{% block *name* %}...{% end %}`` - Indicates a named, replaceable block for use with ``{% extends %}``. - Blocks in the parent template will be replaced with the contents of - the same-named block in a child template.:: - - - {% block title %}Default title{% end %} - - - {% extends "base.html" %} - {% block title %}My page title{% end %} - -``{% comment ... %}`` - A comment which will be removed from the template output. Note that - there is no ``{% end %}`` tag; the comment goes from the word ``comment`` - to the closing ``%}`` tag. - -``{% extends *filename* %}`` - Inherit from another template. Templates that use ``extends`` should - contain one or more ``block`` tags to replace content from the parent - template. Anything in the child template not contained in a ``block`` - tag will be ignored. For an example, see the ``{% block %}`` tag. - -``{% for *var* in *expr* %}...{% end %}`` - Same as the python ``for`` statement. ``{% break %}`` and - ``{% continue %}`` may be used inside the loop. - -``{% from *x* import *y* %}`` - Same as the python ``import`` statement. - -``{% if *condition* %}...{% elif *condition* %}...{% else %}...{% end %}`` - Conditional statement - outputs the first section whose condition is - true. (The ``elif`` and ``else`` sections are optional) - -``{% import *module* %}`` - Same as the python ``import`` statement. - -``{% include *filename* %}`` - Includes another template file. The included file can see all the local - variables as if it were copied directly to the point of the ``include`` - directive (the ``{% autoescape %}`` directive is an exception). - Alternately, ``{% module Template(filename, **kwargs) %}`` may be used - to include another template with an isolated namespace. - -``{% module *expr* %}`` - Renders a `~tornado.web.UIModule`. The output of the ``UIModule`` is - not escaped:: - - {% module Template("foo.html", arg=42) %} - - ``UIModules`` are a feature of the `tornado.web.RequestHandler` - class (and specifically its ``render`` method) and will not work - when the template system is used on its own in other contexts. - -``{% raw *expr* %}`` - Outputs the result of the given expression without autoescaping. - -``{% set *x* = *y* %}`` - Sets a local variable. - -``{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}`` - Same as the python ``try`` statement. - -``{% while *condition* %}... {% end %}`` - Same as the python ``while`` statement. ``{% break %}`` and - ``{% continue %}`` may be used inside the loop. - -``{% whitespace *mode* %}`` - Sets the whitespace mode for the remainder of the current file - (or until the next ``{% whitespace %}`` directive). See - `filter_whitespace` for available options. New in Tornado 4.3. -""" - -import datetime -from io import StringIO -import linecache -import os.path -import posixpath -import re -import threading - -from tornado import escape -from tornado.log import app_log -from tornado.util import ObjectDict, exec_in, unicode_type - -from typing import Any, Union, Callable, List, Dict, Iterable, Optional, TextIO -import typing - -if typing.TYPE_CHECKING: - from typing import Tuple, ContextManager # noqa: F401 - -_DEFAULT_AUTOESCAPE = "xhtml_escape" - - -class _UnsetMarker: - pass - - -_UNSET = _UnsetMarker() - - -def filter_whitespace(mode: str, text: str) -> str: - """Transform whitespace in ``text`` according to ``mode``. - - Available modes are: - - * ``all``: Return all whitespace unmodified. - * ``single``: Collapse consecutive whitespace with a single whitespace - character, preserving newlines. - * ``oneline``: Collapse all runs of whitespace into a single space - character, removing all newlines in the process. - - .. versionadded:: 4.3 - """ - if mode == "all": - return text - elif mode == "single": - text = re.sub(r"([\t ]+)", " ", text) - text = re.sub(r"(\s*\n\s*)", "\n", text) - return text - elif mode == "oneline": - return re.sub(r"(\s+)", " ", text) - else: - raise Exception("invalid whitespace mode %s" % mode) - - -class Template(object): - """A compiled template. - - We compile into Python from the given template_string. You can generate - the template from variables with generate(). - """ - - # note that the constructor's signature is not extracted with - # autodoc because _UNSET looks like garbage. When changing - # this signature update website/sphinx/template.rst too. - def __init__( - self, - template_string: Union[str, bytes], - name: str = "", - loader: Optional["BaseLoader"] = None, - compress_whitespace: Union[bool, _UnsetMarker] = _UNSET, - autoescape: Optional[Union[str, _UnsetMarker]] = _UNSET, - whitespace: Optional[str] = None, - ) -> None: - """Construct a Template. - - :arg str template_string: the contents of the template file. - :arg str name: the filename from which the template was loaded - (used for error message). - :arg tornado.template.BaseLoader loader: the `~tornado.template.BaseLoader` responsible - for this template, used to resolve ``{% include %}`` and ``{% extend %}`` directives. - :arg bool compress_whitespace: Deprecated since Tornado 4.3. - Equivalent to ``whitespace="single"`` if true and - ``whitespace="all"`` if false. - :arg str autoescape: The name of a function in the template - namespace, or ``None`` to disable escaping by default. - :arg str whitespace: A string specifying treatment of whitespace; - see `filter_whitespace` for options. - - .. versionchanged:: 4.3 - Added ``whitespace`` parameter; deprecated ``compress_whitespace``. - """ - self.name = escape.native_str(name) - - if compress_whitespace is not _UNSET: - # Convert deprecated compress_whitespace (bool) to whitespace (str). - if whitespace is not None: - raise Exception("cannot set both whitespace and compress_whitespace") - whitespace = "single" if compress_whitespace else "all" - if whitespace is None: - if loader and loader.whitespace: - whitespace = loader.whitespace - else: - # Whitespace defaults by filename. - if name.endswith(".html") or name.endswith(".js"): - whitespace = "single" - else: - whitespace = "all" - # Validate the whitespace setting. - assert whitespace is not None - filter_whitespace(whitespace, "") - - if not isinstance(autoescape, _UnsetMarker): - self.autoescape = autoescape # type: Optional[str] - elif loader: - self.autoescape = loader.autoescape - else: - self.autoescape = _DEFAULT_AUTOESCAPE - - self.namespace = loader.namespace if loader else {} - reader = _TemplateReader(name, escape.native_str(template_string), whitespace) - self.file = _File(self, _parse(reader, self)) - self.code = self._generate_python(loader) - self.loader = loader - try: - # Under python2.5, the fake filename used here must match - # the module name used in __name__ below. - # The dont_inherit flag prevents template.py's future imports - # from being applied to the generated code. - self.compiled = compile( - escape.to_unicode(self.code), - "%s.generated.py" % self.name.replace(".", "_"), - "exec", - dont_inherit=True, - ) - except Exception: - formatted_code = _format_code(self.code).rstrip() - app_log.error("%s code:\n%s", self.name, formatted_code) - raise - - def generate(self, **kwargs: Any) -> bytes: - """Generate this template with the given arguments.""" - namespace = { - "escape": escape.xhtml_escape, - "xhtml_escape": escape.xhtml_escape, - "url_escape": escape.url_escape, - "json_encode": escape.json_encode, - "squeeze": escape.squeeze, - "linkify": escape.linkify, - "datetime": datetime, - "_tt_utf8": escape.utf8, # for internal use - "_tt_string_types": (unicode_type, bytes), - # __name__ and __loader__ allow the traceback mechanism to find - # the generated source code. - "__name__": self.name.replace(".", "_"), - "__loader__": ObjectDict(get_source=lambda name: self.code), - } - namespace.update(self.namespace) - namespace.update(kwargs) - exec_in(self.compiled, namespace) - execute = typing.cast(Callable[[], bytes], namespace["_tt_execute"]) - # Clear the traceback module's cache of source data now that - # we've generated a new template (mainly for this module's - # unittests, where different tests reuse the same name). - linecache.clearcache() - return execute() - - def _generate_python(self, loader: Optional["BaseLoader"]) -> str: - buffer = StringIO() - try: - # named_blocks maps from names to _NamedBlock objects - named_blocks = {} # type: Dict[str, _NamedBlock] - ancestors = self._get_ancestors(loader) - ancestors.reverse() - for ancestor in ancestors: - ancestor.find_named_blocks(loader, named_blocks) - writer = _CodeWriter(buffer, named_blocks, loader, ancestors[0].template) - ancestors[0].generate(writer) - return buffer.getvalue() - finally: - buffer.close() - - def _get_ancestors(self, loader: Optional["BaseLoader"]) -> List["_File"]: - ancestors = [self.file] - for chunk in self.file.body.chunks: - if isinstance(chunk, _ExtendsBlock): - if not loader: - raise ParseError( - "{% extends %} block found, but no " "template loader" - ) - template = loader.load(chunk.name, self.name) - ancestors.extend(template._get_ancestors(loader)) - return ancestors - - -class BaseLoader(object): - """Base class for template loaders. - - You must use a template loader to use template constructs like - ``{% extends %}`` and ``{% include %}``. The loader caches all - templates after they are loaded the first time. - """ - - def __init__( - self, - autoescape: str = _DEFAULT_AUTOESCAPE, - namespace: Optional[Dict[str, Any]] = None, - whitespace: Optional[str] = None, - ) -> None: - """Construct a template loader. - - :arg str autoescape: The name of a function in the template - namespace, such as "xhtml_escape", or ``None`` to disable - autoescaping by default. - :arg dict namespace: A dictionary to be added to the default template - namespace, or ``None``. - :arg str whitespace: A string specifying default behavior for - whitespace in templates; see `filter_whitespace` for options. - Default is "single" for files ending in ".html" and ".js" and - "all" for other files. - - .. versionchanged:: 4.3 - Added ``whitespace`` parameter. - """ - self.autoescape = autoescape - self.namespace = namespace or {} - self.whitespace = whitespace - self.templates = {} # type: Dict[str, Template] - # self.lock protects self.templates. It's a reentrant lock - # because templates may load other templates via `include` or - # `extends`. Note that thanks to the GIL this code would be safe - # even without the lock, but could lead to wasted work as multiple - # threads tried to compile the same template simultaneously. - self.lock = threading.RLock() - - def reset(self) -> None: - """Resets the cache of compiled templates.""" - with self.lock: - self.templates = {} - - def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str: - """Converts a possibly-relative path to absolute (used internally).""" - raise NotImplementedError() - - def load(self, name: str, parent_path: Optional[str] = None) -> Template: - """Loads a template.""" - name = self.resolve_path(name, parent_path=parent_path) - with self.lock: - if name not in self.templates: - self.templates[name] = self._create_template(name) - return self.templates[name] - - def _create_template(self, name: str) -> Template: - raise NotImplementedError() - - -class Loader(BaseLoader): - """A template loader that loads from a single root directory. - """ - - def __init__(self, root_directory: str, **kwargs: Any) -> None: - super().__init__(**kwargs) - self.root = os.path.abspath(root_directory) - - def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str: - if ( - parent_path - and not parent_path.startswith("<") - and not parent_path.startswith("/") - and not name.startswith("/") - ): - current_path = os.path.join(self.root, parent_path) - file_dir = os.path.dirname(os.path.abspath(current_path)) - relative_path = os.path.abspath(os.path.join(file_dir, name)) - if relative_path.startswith(self.root): - name = relative_path[len(self.root) + 1 :] - return name - - def _create_template(self, name: str) -> Template: - path = os.path.join(self.root, name) - with open(path, "rb") as f: - template = Template(f.read(), name=name, loader=self) - return template - - -class DictLoader(BaseLoader): - """A template loader that loads from a dictionary.""" - - def __init__(self, dict: Dict[str, str], **kwargs: Any) -> None: - super().__init__(**kwargs) - self.dict = dict - - def resolve_path(self, name: str, parent_path: Optional[str] = None) -> str: - if ( - parent_path - and not parent_path.startswith("<") - and not parent_path.startswith("/") - and not name.startswith("/") - ): - file_dir = posixpath.dirname(parent_path) - name = posixpath.normpath(posixpath.join(file_dir, name)) - return name - - def _create_template(self, name: str) -> Template: - return Template(self.dict[name], name=name, loader=self) - - -class _Node(object): - def each_child(self) -> Iterable["_Node"]: - return () - - def generate(self, writer: "_CodeWriter") -> None: - raise NotImplementedError() - - def find_named_blocks( - self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"] - ) -> None: - for child in self.each_child(): - child.find_named_blocks(loader, named_blocks) - - -class _File(_Node): - def __init__(self, template: Template, body: "_ChunkList") -> None: - self.template = template - self.body = body - self.line = 0 - - def generate(self, writer: "_CodeWriter") -> None: - writer.write_line("def _tt_execute():", self.line) - with writer.indent(): - writer.write_line("_tt_buffer = []", self.line) - writer.write_line("_tt_append = _tt_buffer.append", self.line) - self.body.generate(writer) - writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line) - - def each_child(self) -> Iterable["_Node"]: - return (self.body,) - - -class _ChunkList(_Node): - def __init__(self, chunks: List[_Node]) -> None: - self.chunks = chunks - - def generate(self, writer: "_CodeWriter") -> None: - for chunk in self.chunks: - chunk.generate(writer) - - def each_child(self) -> Iterable["_Node"]: - return self.chunks - - -class _NamedBlock(_Node): - def __init__(self, name: str, body: _Node, template: Template, line: int) -> None: - self.name = name - self.body = body - self.template = template - self.line = line - - def each_child(self) -> Iterable["_Node"]: - return (self.body,) - - def generate(self, writer: "_CodeWriter") -> None: - block = writer.named_blocks[self.name] - with writer.include(block.template, self.line): - block.body.generate(writer) - - def find_named_blocks( - self, loader: Optional[BaseLoader], named_blocks: Dict[str, "_NamedBlock"] - ) -> None: - named_blocks[self.name] = self - _Node.find_named_blocks(self, loader, named_blocks) - - -class _ExtendsBlock(_Node): - def __init__(self, name: str) -> None: - self.name = name - - -class _IncludeBlock(_Node): - def __init__(self, name: str, reader: "_TemplateReader", line: int) -> None: - self.name = name - self.template_name = reader.name - self.line = line - - def find_named_blocks( - self, loader: Optional[BaseLoader], named_blocks: Dict[str, _NamedBlock] - ) -> None: - assert loader is not None - included = loader.load(self.name, self.template_name) - included.file.find_named_blocks(loader, named_blocks) - - def generate(self, writer: "_CodeWriter") -> None: - assert writer.loader is not None - included = writer.loader.load(self.name, self.template_name) - with writer.include(included, self.line): - included.file.body.generate(writer) - - -class _ApplyBlock(_Node): - def __init__(self, method: str, line: int, body: _Node) -> None: - self.method = method - self.line = line - self.body = body - - def each_child(self) -> Iterable["_Node"]: - return (self.body,) - - def generate(self, writer: "_CodeWriter") -> None: - method_name = "_tt_apply%d" % writer.apply_counter - writer.apply_counter += 1 - writer.write_line("def %s():" % method_name, self.line) - with writer.indent(): - writer.write_line("_tt_buffer = []", self.line) - writer.write_line("_tt_append = _tt_buffer.append", self.line) - self.body.generate(writer) - writer.write_line("return _tt_utf8('').join(_tt_buffer)", self.line) - writer.write_line( - "_tt_append(_tt_utf8(%s(%s())))" % (self.method, method_name), self.line - ) - - -class _ControlBlock(_Node): - def __init__(self, statement: str, line: int, body: _Node) -> None: - self.statement = statement - self.line = line - self.body = body - - def each_child(self) -> Iterable[_Node]: - return (self.body,) - - def generate(self, writer: "_CodeWriter") -> None: - writer.write_line("%s:" % self.statement, self.line) - with writer.indent(): - self.body.generate(writer) - # Just in case the body was empty - writer.write_line("pass", self.line) - - -class _IntermediateControlBlock(_Node): - def __init__(self, statement: str, line: int) -> None: - self.statement = statement - self.line = line - - def generate(self, writer: "_CodeWriter") -> None: - # In case the previous block was empty - writer.write_line("pass", self.line) - writer.write_line("%s:" % self.statement, self.line, writer.indent_size() - 1) - - -class _Statement(_Node): - def __init__(self, statement: str, line: int) -> None: - self.statement = statement - self.line = line - - def generate(self, writer: "_CodeWriter") -> None: - writer.write_line(self.statement, self.line) - - -class _Expression(_Node): - def __init__(self, expression: str, line: int, raw: bool = False) -> None: - self.expression = expression - self.line = line - self.raw = raw - - def generate(self, writer: "_CodeWriter") -> None: - writer.write_line("_tt_tmp = %s" % self.expression, self.line) - writer.write_line( - "if isinstance(_tt_tmp, _tt_string_types):" " _tt_tmp = _tt_utf8(_tt_tmp)", - self.line, - ) - writer.write_line("else: _tt_tmp = _tt_utf8(str(_tt_tmp))", self.line) - if not self.raw and writer.current_template.autoescape is not None: - # In python3 functions like xhtml_escape return unicode, - # so we have to convert to utf8 again. - writer.write_line( - "_tt_tmp = _tt_utf8(%s(_tt_tmp))" % writer.current_template.autoescape, - self.line, - ) - writer.write_line("_tt_append(_tt_tmp)", self.line) - - -class _Module(_Expression): - def __init__(self, expression: str, line: int) -> None: - super().__init__("_tt_modules." + expression, line, raw=True) - - -class _Text(_Node): - def __init__(self, value: str, line: int, whitespace: str) -> None: - self.value = value - self.line = line - self.whitespace = whitespace - - def generate(self, writer: "_CodeWriter") -> None: - value = self.value - - # Compress whitespace if requested, with a crude heuristic to avoid - # altering preformatted whitespace. - if "
    " not in value:
    -            value = filter_whitespace(self.whitespace, value)
    -
    -        if value:
    -            writer.write_line("_tt_append(%r)" % escape.utf8(value), self.line)
    -
    -
    -class ParseError(Exception):
    -    """Raised for template syntax errors.
    -
    -    ``ParseError`` instances have ``filename`` and ``lineno`` attributes
    -    indicating the position of the error.
    -
    -    .. versionchanged:: 4.3
    -       Added ``filename`` and ``lineno`` attributes.
    -    """
    -
    -    def __init__(
    -        self, message: str, filename: Optional[str] = None, lineno: int = 0
    -    ) -> None:
    -        self.message = message
    -        # The names "filename" and "lineno" are chosen for consistency
    -        # with python SyntaxError.
    -        self.filename = filename
    -        self.lineno = lineno
    -
    -    def __str__(self) -> str:
    -        return "%s at %s:%d" % (self.message, self.filename, self.lineno)
    -
    -
    -class _CodeWriter(object):
    -    def __init__(
    -        self,
    -        file: TextIO,
    -        named_blocks: Dict[str, _NamedBlock],
    -        loader: Optional[BaseLoader],
    -        current_template: Template,
    -    ) -> None:
    -        self.file = file
    -        self.named_blocks = named_blocks
    -        self.loader = loader
    -        self.current_template = current_template
    -        self.apply_counter = 0
    -        self.include_stack = []  # type: List[Tuple[Template, int]]
    -        self._indent = 0
    -
    -    def indent_size(self) -> int:
    -        return self._indent
    -
    -    def indent(self) -> "ContextManager":
    -        class Indenter(object):
    -            def __enter__(_) -> "_CodeWriter":
    -                self._indent += 1
    -                return self
    -
    -            def __exit__(_, *args: Any) -> None:
    -                assert self._indent > 0
    -                self._indent -= 1
    -
    -        return Indenter()
    -
    -    def include(self, template: Template, line: int) -> "ContextManager":
    -        self.include_stack.append((self.current_template, line))
    -        self.current_template = template
    -
    -        class IncludeTemplate(object):
    -            def __enter__(_) -> "_CodeWriter":
    -                return self
    -
    -            def __exit__(_, *args: Any) -> None:
    -                self.current_template = self.include_stack.pop()[0]
    -
    -        return IncludeTemplate()
    -
    -    def write_line(
    -        self, line: str, line_number: int, indent: Optional[int] = None
    -    ) -> None:
    -        if indent is None:
    -            indent = self._indent
    -        line_comment = "  # %s:%d" % (self.current_template.name, line_number)
    -        if self.include_stack:
    -            ancestors = [
    -                "%s:%d" % (tmpl.name, lineno) for (tmpl, lineno) in self.include_stack
    -            ]
    -            line_comment += " (via %s)" % ", ".join(reversed(ancestors))
    -        print("    " * indent + line + line_comment, file=self.file)
    -
    -
    -class _TemplateReader(object):
    -    def __init__(self, name: str, text: str, whitespace: str) -> None:
    -        self.name = name
    -        self.text = text
    -        self.whitespace = whitespace
    -        self.line = 1
    -        self.pos = 0
    -
    -    def find(self, needle: str, start: int = 0, end: Optional[int] = None) -> int:
    -        assert start >= 0, start
    -        pos = self.pos
    -        start += pos
    -        if end is None:
    -            index = self.text.find(needle, start)
    -        else:
    -            end += pos
    -            assert end >= start
    -            index = self.text.find(needle, start, end)
    -        if index != -1:
    -            index -= pos
    -        return index
    -
    -    def consume(self, count: Optional[int] = None) -> str:
    -        if count is None:
    -            count = len(self.text) - self.pos
    -        newpos = self.pos + count
    -        self.line += self.text.count("\n", self.pos, newpos)
    -        s = self.text[self.pos : newpos]
    -        self.pos = newpos
    -        return s
    -
    -    def remaining(self) -> int:
    -        return len(self.text) - self.pos
    -
    -    def __len__(self) -> int:
    -        return self.remaining()
    -
    -    def __getitem__(self, key: Union[int, slice]) -> str:
    -        if isinstance(key, slice):
    -            size = len(self)
    -            start, stop, step = key.indices(size)
    -            if start is None:
    -                start = self.pos
    -            else:
    -                start += self.pos
    -            if stop is not None:
    -                stop += self.pos
    -            return self.text[slice(start, stop, step)]
    -        elif key < 0:
    -            return self.text[key]
    -        else:
    -            return self.text[self.pos + key]
    -
    -    def __str__(self) -> str:
    -        return self.text[self.pos :]
    -
    -    def raise_parse_error(self, msg: str) -> None:
    -        raise ParseError(msg, self.name, self.line)
    -
    -
    -def _format_code(code: str) -> str:
    -    lines = code.splitlines()
    -    format = "%%%dd  %%s\n" % len(repr(len(lines) + 1))
    -    return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])
    -
    -
    -def _parse(
    -    reader: _TemplateReader,
    -    template: Template,
    -    in_block: Optional[str] = None,
    -    in_loop: Optional[str] = None,
    -) -> _ChunkList:
    -    body = _ChunkList([])
    -    while True:
    -        # Find next template directive
    -        curly = 0
    -        while True:
    -            curly = reader.find("{", curly)
    -            if curly == -1 or curly + 1 == reader.remaining():
    -                # EOF
    -                if in_block:
    -                    reader.raise_parse_error(
    -                        "Missing {%% end %%} block for %s" % in_block
    -                    )
    -                body.chunks.append(
    -                    _Text(reader.consume(), reader.line, reader.whitespace)
    -                )
    -                return body
    -            # If the first curly brace is not the start of a special token,
    -            # start searching from the character after it
    -            if reader[curly + 1] not in ("{", "%", "#"):
    -                curly += 1
    -                continue
    -            # When there are more than 2 curlies in a row, use the
    -            # innermost ones.  This is useful when generating languages
    -            # like latex where curlies are also meaningful
    -            if (
    -                curly + 2 < reader.remaining()
    -                and reader[curly + 1] == "{"
    -                and reader[curly + 2] == "{"
    -            ):
    -                curly += 1
    -                continue
    -            break
    -
    -        # Append any text before the special token
    -        if curly > 0:
    -            cons = reader.consume(curly)
    -            body.chunks.append(_Text(cons, reader.line, reader.whitespace))
    -
    -        start_brace = reader.consume(2)
    -        line = reader.line
    -
    -        # Template directives may be escaped as "{{!" or "{%!".
    -        # In this case output the braces and consume the "!".
    -        # This is especially useful in conjunction with jquery templates,
    -        # which also use double braces.
    -        if reader.remaining() and reader[0] == "!":
    -            reader.consume(1)
    -            body.chunks.append(_Text(start_brace, line, reader.whitespace))
    -            continue
    -
    -        # Comment
    -        if start_brace == "{#":
    -            end = reader.find("#}")
    -            if end == -1:
    -                reader.raise_parse_error("Missing end comment #}")
    -            contents = reader.consume(end).strip()
    -            reader.consume(2)
    -            continue
    -
    -        # Expression
    -        if start_brace == "{{":
    -            end = reader.find("}}")
    -            if end == -1:
    -                reader.raise_parse_error("Missing end expression }}")
    -            contents = reader.consume(end).strip()
    -            reader.consume(2)
    -            if not contents:
    -                reader.raise_parse_error("Empty expression")
    -            body.chunks.append(_Expression(contents, line))
    -            continue
    -
    -        # Block
    -        assert start_brace == "{%", start_brace
    -        end = reader.find("%}")
    -        if end == -1:
    -            reader.raise_parse_error("Missing end block %}")
    -        contents = reader.consume(end).strip()
    -        reader.consume(2)
    -        if not contents:
    -            reader.raise_parse_error("Empty block tag ({% %})")
    -
    -        operator, space, suffix = contents.partition(" ")
    -        suffix = suffix.strip()
    -
    -        # Intermediate ("else", "elif", etc) blocks
    -        intermediate_blocks = {
    -            "else": set(["if", "for", "while", "try"]),
    -            "elif": set(["if"]),
    -            "except": set(["try"]),
    -            "finally": set(["try"]),
    -        }
    -        allowed_parents = intermediate_blocks.get(operator)
    -        if allowed_parents is not None:
    -            if not in_block:
    -                reader.raise_parse_error(
    -                    "%s outside %s block" % (operator, allowed_parents)
    -                )
    -            if in_block not in allowed_parents:
    -                reader.raise_parse_error(
    -                    "%s block cannot be attached to %s block" % (operator, in_block)
    -                )
    -            body.chunks.append(_IntermediateControlBlock(contents, line))
    -            continue
    -
    -        # End tag
    -        elif operator == "end":
    -            if not in_block:
    -                reader.raise_parse_error("Extra {% end %} block")
    -            return body
    -
    -        elif operator in (
    -            "extends",
    -            "include",
    -            "set",
    -            "import",
    -            "from",
    -            "comment",
    -            "autoescape",
    -            "whitespace",
    -            "raw",
    -            "module",
    -        ):
    -            if operator == "comment":
    -                continue
    -            if operator == "extends":
    -                suffix = suffix.strip('"').strip("'")
    -                if not suffix:
    -                    reader.raise_parse_error("extends missing file path")
    -                block = _ExtendsBlock(suffix)  # type: _Node
    -            elif operator in ("import", "from"):
    -                if not suffix:
    -                    reader.raise_parse_error("import missing statement")
    -                block = _Statement(contents, line)
    -            elif operator == "include":
    -                suffix = suffix.strip('"').strip("'")
    -                if not suffix:
    -                    reader.raise_parse_error("include missing file path")
    -                block = _IncludeBlock(suffix, reader, line)
    -            elif operator == "set":
    -                if not suffix:
    -                    reader.raise_parse_error("set missing statement")
    -                block = _Statement(suffix, line)
    -            elif operator == "autoescape":
    -                fn = suffix.strip()  # type: Optional[str]
    -                if fn == "None":
    -                    fn = None
    -                template.autoescape = fn
    -                continue
    -            elif operator == "whitespace":
    -                mode = suffix.strip()
    -                # Validate the selected mode
    -                filter_whitespace(mode, "")
    -                reader.whitespace = mode
    -                continue
    -            elif operator == "raw":
    -                block = _Expression(suffix, line, raw=True)
    -            elif operator == "module":
    -                block = _Module(suffix, line)
    -            body.chunks.append(block)
    -            continue
    -
    -        elif operator in ("apply", "block", "try", "if", "for", "while"):
    -            # parse inner body recursively
    -            if operator in ("for", "while"):
    -                block_body = _parse(reader, template, operator, operator)
    -            elif operator == "apply":
    -                # apply creates a nested function so syntactically it's not
    -                # in the loop.
    -                block_body = _parse(reader, template, operator, None)
    -            else:
    -                block_body = _parse(reader, template, operator, in_loop)
    -
    -            if operator == "apply":
    -                if not suffix:
    -                    reader.raise_parse_error("apply missing method name")
    -                block = _ApplyBlock(suffix, line, block_body)
    -            elif operator == "block":
    -                if not suffix:
    -                    reader.raise_parse_error("block missing name")
    -                block = _NamedBlock(suffix, block_body, template, line)
    -            else:
    -                block = _ControlBlock(contents, line, block_body)
    -            body.chunks.append(block)
    -            continue
    -
    -        elif operator in ("break", "continue"):
    -            if not in_loop:
    -                reader.raise_parse_error(
    -                    "%s outside %s block" % (operator, set(["for", "while"]))
    -                )
    -            body.chunks.append(_Statement(contents, line))
    -            continue
    -
    -        else:
    -            reader.raise_parse_error("unknown operator: %r" % operator)
    diff --git a/telegramer/include/tornado/testing.py b/telegramer/include/tornado/testing.py
    deleted file mode 100644
    index 3351b92..0000000
    --- a/telegramer/include/tornado/testing.py
    +++ /dev/null
    @@ -1,818 +0,0 @@
    -"""Support classes for automated testing.
    -
    -* `AsyncTestCase` and `AsyncHTTPTestCase`:  Subclasses of unittest.TestCase
    -  with additional support for testing asynchronous (`.IOLoop`-based) code.
    -
    -* `ExpectLog`: Make test logs less spammy.
    -
    -* `main()`: A simple test runner (wrapper around unittest.main()) with support
    -  for the tornado.autoreload module to rerun the tests when code changes.
    -"""
    -
    -import asyncio
    -from collections.abc import Generator
    -import functools
    -import inspect
    -import logging
    -import os
    -import re
    -import signal
    -import socket
    -import sys
    -import unittest
    -
    -from tornado import gen
    -from tornado.httpclient import AsyncHTTPClient, HTTPResponse
    -from tornado.httpserver import HTTPServer
    -from tornado.ioloop import IOLoop, TimeoutError
    -from tornado import netutil
    -from tornado.platform.asyncio import AsyncIOMainLoop
    -from tornado.process import Subprocess
    -from tornado.log import app_log
    -from tornado.util import raise_exc_info, basestring_type
    -from tornado.web import Application
    -
    -import typing
    -from typing import Tuple, Any, Callable, Type, Dict, Union, Optional
    -from types import TracebackType
    -
    -if typing.TYPE_CHECKING:
    -    # Coroutine wasn't added to typing until 3.5.3, so only import it
    -    # when mypy is running and use forward references.
    -    from typing import Coroutine  # noqa: F401
    -
    -    _ExcInfoTuple = Tuple[
    -        Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]
    -    ]
    -
    -
    -_NON_OWNED_IOLOOPS = AsyncIOMainLoop
    -
    -
    -def bind_unused_port(reuse_port: bool = False) -> Tuple[socket.socket, int]:
    -    """Binds a server socket to an available port on localhost.
    -
    -    Returns a tuple (socket, port).
    -
    -    .. versionchanged:: 4.4
    -       Always binds to ``127.0.0.1`` without resolving the name
    -       ``localhost``.
    -    """
    -    sock = netutil.bind_sockets(
    -        0, "127.0.0.1", family=socket.AF_INET, reuse_port=reuse_port
    -    )[0]
    -    port = sock.getsockname()[1]
    -    return sock, port
    -
    -
    -def get_async_test_timeout() -> float:
    -    """Get the global timeout setting for async tests.
    -
    -    Returns a float, the timeout in seconds.
    -
    -    .. versionadded:: 3.1
    -    """
    -    env = os.environ.get("ASYNC_TEST_TIMEOUT")
    -    if env is not None:
    -        try:
    -            return float(env)
    -        except ValueError:
    -            pass
    -    return 5
    -
    -
    -class _TestMethodWrapper(object):
    -    """Wraps a test method to raise an error if it returns a value.
    -
    -    This is mainly used to detect undecorated generators (if a test
    -    method yields it must use a decorator to consume the generator),
    -    but will also detect other kinds of return values (these are not
    -    necessarily errors, but we alert anyway since there is no good
    -    reason to return a value from a test).
    -    """
    -
    -    def __init__(self, orig_method: Callable) -> None:
    -        self.orig_method = orig_method
    -
    -    def __call__(self, *args: Any, **kwargs: Any) -> None:
    -        result = self.orig_method(*args, **kwargs)
    -        if isinstance(result, Generator) or inspect.iscoroutine(result):
    -            raise TypeError(
    -                "Generator and coroutine test methods should be"
    -                " decorated with tornado.testing.gen_test"
    -            )
    -        elif result is not None:
    -            raise ValueError("Return value from test method ignored: %r" % result)
    -
    -    def __getattr__(self, name: str) -> Any:
    -        """Proxy all unknown attributes to the original method.
    -
    -        This is important for some of the decorators in the `unittest`
    -        module, such as `unittest.skipIf`.
    -        """
    -        return getattr(self.orig_method, name)
    -
    -
    -class AsyncTestCase(unittest.TestCase):
    -    """`~unittest.TestCase` subclass for testing `.IOLoop`-based
    -    asynchronous code.
    -
    -    The unittest framework is synchronous, so the test must be
    -    complete by the time the test method returns. This means that
    -    asynchronous code cannot be used in quite the same way as usual
    -    and must be adapted to fit. To write your tests with coroutines,
    -    decorate your test methods with `tornado.testing.gen_test` instead
    -    of `tornado.gen.coroutine`.
    -
    -    This class also provides the (deprecated) `stop()` and `wait()`
    -    methods for a more manual style of testing. The test method itself
    -    must call ``self.wait()``, and asynchronous callbacks should call
    -    ``self.stop()`` to signal completion.
    -
    -    By default, a new `.IOLoop` is constructed for each test and is available
    -    as ``self.io_loop``.  If the code being tested requires a
    -    global `.IOLoop`, subclasses should override `get_new_ioloop` to return it.
    -
    -    The `.IOLoop`'s ``start`` and ``stop`` methods should not be
    -    called directly.  Instead, use `self.stop ` and `self.wait
    -    `.  Arguments passed to ``self.stop`` are returned from
    -    ``self.wait``.  It is possible to have multiple ``wait``/``stop``
    -    cycles in the same test.
    -
    -    Example::
    -
    -        # This test uses coroutine style.
    -        class MyTestCase(AsyncTestCase):
    -            @tornado.testing.gen_test
    -            def test_http_fetch(self):
    -                client = AsyncHTTPClient()
    -                response = yield client.fetch("http://www.tornadoweb.org")
    -                # Test contents of response
    -                self.assertIn("FriendFeed", response.body)
    -
    -        # This test uses argument passing between self.stop and self.wait.
    -        class MyTestCase2(AsyncTestCase):
    -            def test_http_fetch(self):
    -                client = AsyncHTTPClient()
    -                client.fetch("http://www.tornadoweb.org/", self.stop)
    -                response = self.wait()
    -                # Test contents of response
    -                self.assertIn("FriendFeed", response.body)
    -    """
    -
    -    def __init__(self, methodName: str = "runTest") -> None:
    -        super().__init__(methodName)
    -        self.__stopped = False
    -        self.__running = False
    -        self.__failure = None  # type: Optional[_ExcInfoTuple]
    -        self.__stop_args = None  # type: Any
    -        self.__timeout = None  # type: Optional[object]
    -
    -        # It's easy to forget the @gen_test decorator, but if you do
    -        # the test will silently be ignored because nothing will consume
    -        # the generator.  Replace the test method with a wrapper that will
    -        # make sure it's not an undecorated generator.
    -        setattr(self, methodName, _TestMethodWrapper(getattr(self, methodName)))
    -
    -        # Not used in this class itself, but used by @gen_test
    -        self._test_generator = None  # type: Optional[Union[Generator, Coroutine]]
    -
    -    def setUp(self) -> None:
    -        super().setUp()
    -        self.io_loop = self.get_new_ioloop()
    -        self.io_loop.make_current()
    -
    -    def tearDown(self) -> None:
    -        # Native coroutines tend to produce warnings if they're not
    -        # allowed to run to completion. It's difficult to ensure that
    -        # this always happens in tests, so cancel any tasks that are
    -        # still pending by the time we get here.
    -        asyncio_loop = self.io_loop.asyncio_loop  # type: ignore
    -        if hasattr(asyncio, "all_tasks"):  # py37
    -            tasks = asyncio.all_tasks(asyncio_loop)  # type: ignore
    -        else:
    -            tasks = asyncio.Task.all_tasks(asyncio_loop)
    -        # Tasks that are done may still appear here and may contain
    -        # non-cancellation exceptions, so filter them out.
    -        tasks = [t for t in tasks if not t.done()]
    -        for t in tasks:
    -            t.cancel()
    -        # Allow the tasks to run and finalize themselves (which means
    -        # raising a CancelledError inside the coroutine). This may
    -        # just transform the "task was destroyed but it is pending"
    -        # warning into a "uncaught CancelledError" warning, but
    -        # catching CancelledErrors in coroutines that may leak is
    -        # simpler than ensuring that no coroutines leak.
    -        if tasks:
    -            done, pending = self.io_loop.run_sync(lambda: asyncio.wait(tasks))
    -            assert not pending
    -            # If any task failed with anything but a CancelledError, raise it.
    -            for f in done:
    -                try:
    -                    f.result()
    -                except asyncio.CancelledError:
    -                    pass
    -
    -        # Clean up Subprocess, so it can be used again with a new ioloop.
    -        Subprocess.uninitialize()
    -        self.io_loop.clear_current()
    -        if not isinstance(self.io_loop, _NON_OWNED_IOLOOPS):
    -            # Try to clean up any file descriptors left open in the ioloop.
    -            # This avoids leaks, especially when tests are run repeatedly
    -            # in the same process with autoreload (because curl does not
    -            # set FD_CLOEXEC on its file descriptors)
    -            self.io_loop.close(all_fds=True)
    -        super().tearDown()
    -        # In case an exception escaped or the StackContext caught an exception
    -        # when there wasn't a wait() to re-raise it, do so here.
    -        # This is our last chance to raise an exception in a way that the
    -        # unittest machinery understands.
    -        self.__rethrow()
    -
    -    def get_new_ioloop(self) -> IOLoop:
    -        """Returns the `.IOLoop` to use for this test.
    -
    -        By default, a new `.IOLoop` is created for each test.
    -        Subclasses may override this method to return
    -        `.IOLoop.current()` if it is not appropriate to use a new
    -        `.IOLoop` in each tests (for example, if there are global
    -        singletons using the default `.IOLoop`) or if a per-test event
    -        loop is being provided by another system (such as
    -        ``pytest-asyncio``).
    -        """
    -        return IOLoop()
    -
    -    def _handle_exception(
    -        self, typ: Type[Exception], value: Exception, tb: TracebackType
    -    ) -> bool:
    -        if self.__failure is None:
    -            self.__failure = (typ, value, tb)
    -        else:
    -            app_log.error(
    -                "multiple unhandled exceptions in test", exc_info=(typ, value, tb)
    -            )
    -        self.stop()
    -        return True
    -
    -    def __rethrow(self) -> None:
    -        if self.__failure is not None:
    -            failure = self.__failure
    -            self.__failure = None
    -            raise_exc_info(failure)
    -
    -    def run(
    -        self, result: Optional[unittest.TestResult] = None
    -    ) -> Optional[unittest.TestResult]:
    -        ret = super().run(result)
    -        # As a last resort, if an exception escaped super.run() and wasn't
    -        # re-raised in tearDown, raise it here.  This will cause the
    -        # unittest run to fail messily, but that's better than silently
    -        # ignoring an error.
    -        self.__rethrow()
    -        return ret
    -
    -    def stop(self, _arg: Any = None, **kwargs: Any) -> None:
    -        """Stops the `.IOLoop`, causing one pending (or future) call to `wait()`
    -        to return.
    -
    -        Keyword arguments or a single positional argument passed to `stop()` are
    -        saved and will be returned by `wait()`.
    -
    -        .. deprecated:: 5.1
    -
    -           `stop` and `wait` are deprecated; use ``@gen_test`` instead.
    -        """
    -        assert _arg is None or not kwargs
    -        self.__stop_args = kwargs or _arg
    -        if self.__running:
    -            self.io_loop.stop()
    -            self.__running = False
    -        self.__stopped = True
    -
    -    def wait(
    -        self,
    -        condition: Optional[Callable[..., bool]] = None,
    -        timeout: Optional[float] = None,
    -    ) -> Any:
    -        """Runs the `.IOLoop` until stop is called or timeout has passed.
    -
    -        In the event of a timeout, an exception will be thrown. The
    -        default timeout is 5 seconds; it may be overridden with a
    -        ``timeout`` keyword argument or globally with the
    -        ``ASYNC_TEST_TIMEOUT`` environment variable.
    -
    -        If ``condition`` is not ``None``, the `.IOLoop` will be restarted
    -        after `stop()` until ``condition()`` returns ``True``.
    -
    -        .. versionchanged:: 3.1
    -           Added the ``ASYNC_TEST_TIMEOUT`` environment variable.
    -
    -        .. deprecated:: 5.1
    -
    -           `stop` and `wait` are deprecated; use ``@gen_test`` instead.
    -        """
    -        if timeout is None:
    -            timeout = get_async_test_timeout()
    -
    -        if not self.__stopped:
    -            if timeout:
    -
    -                def timeout_func() -> None:
    -                    try:
    -                        raise self.failureException(
    -                            "Async operation timed out after %s seconds" % timeout
    -                        )
    -                    except Exception:
    -                        self.__failure = sys.exc_info()
    -                    self.stop()
    -
    -                self.__timeout = self.io_loop.add_timeout(
    -                    self.io_loop.time() + timeout, timeout_func
    -                )
    -            while True:
    -                self.__running = True
    -                self.io_loop.start()
    -                if self.__failure is not None or condition is None or condition():
    -                    break
    -            if self.__timeout is not None:
    -                self.io_loop.remove_timeout(self.__timeout)
    -                self.__timeout = None
    -        assert self.__stopped
    -        self.__stopped = False
    -        self.__rethrow()
    -        result = self.__stop_args
    -        self.__stop_args = None
    -        return result
    -
    -
    -class AsyncHTTPTestCase(AsyncTestCase):
    -    """A test case that starts up an HTTP server.
    -
    -    Subclasses must override `get_app()`, which returns the
    -    `tornado.web.Application` (or other `.HTTPServer` callback) to be tested.
    -    Tests will typically use the provided ``self.http_client`` to fetch
    -    URLs from this server.
    -
    -    Example, assuming the "Hello, world" example from the user guide is in
    -    ``hello.py``::
    -
    -        import hello
    -
    -        class TestHelloApp(AsyncHTTPTestCase):
    -            def get_app(self):
    -                return hello.make_app()
    -
    -            def test_homepage(self):
    -                response = self.fetch('/')
    -                self.assertEqual(response.code, 200)
    -                self.assertEqual(response.body, 'Hello, world')
    -
    -    That call to ``self.fetch()`` is equivalent to ::
    -
    -        self.http_client.fetch(self.get_url('/'), self.stop)
    -        response = self.wait()
    -
    -    which illustrates how AsyncTestCase can turn an asynchronous operation,
    -    like ``http_client.fetch()``, into a synchronous operation. If you need
    -    to do other asynchronous operations in tests, you'll probably need to use
    -    ``stop()`` and ``wait()`` yourself.
    -    """
    -
    -    def setUp(self) -> None:
    -        super().setUp()
    -        sock, port = bind_unused_port()
    -        self.__port = port
    -
    -        self.http_client = self.get_http_client()
    -        self._app = self.get_app()
    -        self.http_server = self.get_http_server()
    -        self.http_server.add_sockets([sock])
    -
    -    def get_http_client(self) -> AsyncHTTPClient:
    -        return AsyncHTTPClient()
    -
    -    def get_http_server(self) -> HTTPServer:
    -        return HTTPServer(self._app, **self.get_httpserver_options())
    -
    -    def get_app(self) -> Application:
    -        """Should be overridden by subclasses to return a
    -        `tornado.web.Application` or other `.HTTPServer` callback.
    -        """
    -        raise NotImplementedError()
    -
    -    def fetch(
    -        self, path: str, raise_error: bool = False, **kwargs: Any
    -    ) -> HTTPResponse:
    -        """Convenience method to synchronously fetch a URL.
    -
    -        The given path will be appended to the local server's host and
    -        port.  Any additional keyword arguments will be passed directly to
    -        `.AsyncHTTPClient.fetch` (and so could be used to pass
    -        ``method="POST"``, ``body="..."``, etc).
    -
    -        If the path begins with http:// or https://, it will be treated as a
    -        full URL and will be fetched as-is.
    -
    -        If ``raise_error`` is ``True``, a `tornado.httpclient.HTTPError` will
    -        be raised if the response code is not 200. This is the same behavior
    -        as the ``raise_error`` argument to `.AsyncHTTPClient.fetch`, but
    -        the default is ``False`` here (it's ``True`` in `.AsyncHTTPClient`)
    -        because tests often need to deal with non-200 response codes.
    -
    -        .. versionchanged:: 5.0
    -           Added support for absolute URLs.
    -
    -        .. versionchanged:: 5.1
    -
    -           Added the ``raise_error`` argument.
    -
    -        .. deprecated:: 5.1
    -
    -           This method currently turns any exception into an
    -           `.HTTPResponse` with status code 599. In Tornado 6.0,
    -           errors other than `tornado.httpclient.HTTPError` will be
    -           passed through, and ``raise_error=False`` will only
    -           suppress errors that would be raised due to non-200
    -           response codes.
    -
    -        """
    -        if path.lower().startswith(("http://", "https://")):
    -            url = path
    -        else:
    -            url = self.get_url(path)
    -        return self.io_loop.run_sync(
    -            lambda: self.http_client.fetch(url, raise_error=raise_error, **kwargs),
    -            timeout=get_async_test_timeout(),
    -        )
    -
    -    def get_httpserver_options(self) -> Dict[str, Any]:
    -        """May be overridden by subclasses to return additional
    -        keyword arguments for the server.
    -        """
    -        return {}
    -
    -    def get_http_port(self) -> int:
    -        """Returns the port used by the server.
    -
    -        A new port is chosen for each test.
    -        """
    -        return self.__port
    -
    -    def get_protocol(self) -> str:
    -        return "http"
    -
    -    def get_url(self, path: str) -> str:
    -        """Returns an absolute url for the given path on the test server."""
    -        return "%s://127.0.0.1:%s%s" % (self.get_protocol(), self.get_http_port(), path)
    -
    -    def tearDown(self) -> None:
    -        self.http_server.stop()
    -        self.io_loop.run_sync(
    -            self.http_server.close_all_connections, timeout=get_async_test_timeout()
    -        )
    -        self.http_client.close()
    -        del self.http_server
    -        del self._app
    -        super().tearDown()
    -
    -
    -class AsyncHTTPSTestCase(AsyncHTTPTestCase):
    -    """A test case that starts an HTTPS server.
    -
    -    Interface is generally the same as `AsyncHTTPTestCase`.
    -    """
    -
    -    def get_http_client(self) -> AsyncHTTPClient:
    -        return AsyncHTTPClient(force_instance=True, defaults=dict(validate_cert=False))
    -
    -    def get_httpserver_options(self) -> Dict[str, Any]:
    -        return dict(ssl_options=self.get_ssl_options())
    -
    -    def get_ssl_options(self) -> Dict[str, Any]:
    -        """May be overridden by subclasses to select SSL options.
    -
    -        By default includes a self-signed testing certificate.
    -        """
    -        return AsyncHTTPSTestCase.default_ssl_options()
    -
    -    @staticmethod
    -    def default_ssl_options() -> Dict[str, Any]:
    -        # Testing keys were generated with:
    -        # openssl req -new -keyout tornado/test/test.key \
    -        #                     -out tornado/test/test.crt -nodes -days 3650 -x509
    -        module_dir = os.path.dirname(__file__)
    -        return dict(
    -            certfile=os.path.join(module_dir, "test", "test.crt"),
    -            keyfile=os.path.join(module_dir, "test", "test.key"),
    -        )
    -
    -    def get_protocol(self) -> str:
    -        return "https"
    -
    -
    -@typing.overload
    -def gen_test(
    -    *, timeout: Optional[float] = None
    -) -> Callable[[Callable[..., Union[Generator, "Coroutine"]]], Callable[..., None]]:
    -    pass
    -
    -
    -@typing.overload  # noqa: F811
    -def gen_test(func: Callable[..., Union[Generator, "Coroutine"]]) -> Callable[..., None]:
    -    pass
    -
    -
    -def gen_test(  # noqa: F811
    -    func: Optional[Callable[..., Union[Generator, "Coroutine"]]] = None,
    -    timeout: Optional[float] = None,
    -) -> Union[
    -    Callable[..., None],
    -    Callable[[Callable[..., Union[Generator, "Coroutine"]]], Callable[..., None]],
    -]:
    -    """Testing equivalent of ``@gen.coroutine``, to be applied to test methods.
    -
    -    ``@gen.coroutine`` cannot be used on tests because the `.IOLoop` is not
    -    already running.  ``@gen_test`` should be applied to test methods
    -    on subclasses of `AsyncTestCase`.
    -
    -    Example::
    -
    -        class MyTest(AsyncHTTPTestCase):
    -            @gen_test
    -            def test_something(self):
    -                response = yield self.http_client.fetch(self.get_url('/'))
    -
    -    By default, ``@gen_test`` times out after 5 seconds. The timeout may be
    -    overridden globally with the ``ASYNC_TEST_TIMEOUT`` environment variable,
    -    or for each test with the ``timeout`` keyword argument::
    -
    -        class MyTest(AsyncHTTPTestCase):
    -            @gen_test(timeout=10)
    -            def test_something_slow(self):
    -                response = yield self.http_client.fetch(self.get_url('/'))
    -
    -    Note that ``@gen_test`` is incompatible with `AsyncTestCase.stop`,
    -    `AsyncTestCase.wait`, and `AsyncHTTPTestCase.fetch`. Use ``yield
    -    self.http_client.fetch(self.get_url())`` as shown above instead.
    -
    -    .. versionadded:: 3.1
    -       The ``timeout`` argument and ``ASYNC_TEST_TIMEOUT`` environment
    -       variable.
    -
    -    .. versionchanged:: 4.0
    -       The wrapper now passes along ``*args, **kwargs`` so it can be used
    -       on functions with arguments.
    -
    -    """
    -    if timeout is None:
    -        timeout = get_async_test_timeout()
    -
    -    def wrap(f: Callable[..., Union[Generator, "Coroutine"]]) -> Callable[..., None]:
    -        # Stack up several decorators to allow us to access the generator
    -        # object itself.  In the innermost wrapper, we capture the generator
    -        # and save it in an attribute of self.  Next, we run the wrapped
    -        # function through @gen.coroutine.  Finally, the coroutine is
    -        # wrapped again to make it synchronous with run_sync.
    -        #
    -        # This is a good case study arguing for either some sort of
    -        # extensibility in the gen decorators or cancellation support.
    -        @functools.wraps(f)
    -        def pre_coroutine(self, *args, **kwargs):
    -            # type: (AsyncTestCase, *Any, **Any) -> Union[Generator, Coroutine]
    -            # Type comments used to avoid pypy3 bug.
    -            result = f(self, *args, **kwargs)
    -            if isinstance(result, Generator) or inspect.iscoroutine(result):
    -                self._test_generator = result
    -            else:
    -                self._test_generator = None
    -            return result
    -
    -        if inspect.iscoroutinefunction(f):
    -            coro = pre_coroutine
    -        else:
    -            coro = gen.coroutine(pre_coroutine)
    -
    -        @functools.wraps(coro)
    -        def post_coroutine(self, *args, **kwargs):
    -            # type: (AsyncTestCase, *Any, **Any) -> None
    -            try:
    -                return self.io_loop.run_sync(
    -                    functools.partial(coro, self, *args, **kwargs), timeout=timeout
    -                )
    -            except TimeoutError as e:
    -                # run_sync raises an error with an unhelpful traceback.
    -                # If the underlying generator is still running, we can throw the
    -                # exception back into it so the stack trace is replaced by the
    -                # point where the test is stopped. The only reason the generator
    -                # would not be running would be if it were cancelled, which means
    -                # a native coroutine, so we can rely on the cr_running attribute.
    -                if self._test_generator is not None and getattr(
    -                    self._test_generator, "cr_running", True
    -                ):
    -                    self._test_generator.throw(type(e), e)
    -                    # In case the test contains an overly broad except
    -                    # clause, we may get back here.
    -                # Coroutine was stopped or didn't raise a useful stack trace,
    -                # so re-raise the original exception which is better than nothing.
    -                raise
    -
    -        return post_coroutine
    -
    -    if func is not None:
    -        # Used like:
    -        #     @gen_test
    -        #     def f(self):
    -        #         pass
    -        return wrap(func)
    -    else:
    -        # Used like @gen_test(timeout=10)
    -        return wrap
    -
    -
    -# Without this attribute, nosetests will try to run gen_test as a test
    -# anywhere it is imported.
    -gen_test.__test__ = False  # type: ignore
    -
    -
    -class ExpectLog(logging.Filter):
    -    """Context manager to capture and suppress expected log output.
    -
    -    Useful to make tests of error conditions less noisy, while still
    -    leaving unexpected log entries visible.  *Not thread safe.*
    -
    -    The attribute ``logged_stack`` is set to ``True`` if any exception
    -    stack trace was logged.
    -
    -    Usage::
    -
    -        with ExpectLog('tornado.application', "Uncaught exception"):
    -            error_response = self.fetch("/some_page")
    -
    -    .. versionchanged:: 4.3
    -       Added the ``logged_stack`` attribute.
    -    """
    -
    -    def __init__(
    -        self,
    -        logger: Union[logging.Logger, basestring_type],
    -        regex: str,
    -        required: bool = True,
    -        level: Optional[int] = None,
    -    ) -> None:
    -        """Constructs an ExpectLog context manager.
    -
    -        :param logger: Logger object (or name of logger) to watch.  Pass
    -            an empty string to watch the root logger.
    -        :param regex: Regular expression to match.  Any log entries on
    -            the specified logger that match this regex will be suppressed.
    -        :param required: If true, an exception will be raised if the end of
    -            the ``with`` statement is reached without matching any log entries.
    -        :param level: A constant from the ``logging`` module indicating the
    -            expected log level. If this parameter is provided, only log messages
    -            at this level will be considered to match. Additionally, the
    -            supplied ``logger`` will have its level adjusted if necessary
    -            (for the duration of the ``ExpectLog`` to enable the expected
    -            message.
    -
    -        .. versionchanged:: 6.1
    -           Added the ``level`` parameter.
    -        """
    -        if isinstance(logger, basestring_type):
    -            logger = logging.getLogger(logger)
    -        self.logger = logger
    -        self.regex = re.compile(regex)
    -        self.required = required
    -        self.matched = False
    -        self.logged_stack = False
    -        self.level = level
    -        self.orig_level = None  # type: Optional[int]
    -
    -    def filter(self, record: logging.LogRecord) -> bool:
    -        if record.exc_info:
    -            self.logged_stack = True
    -        message = record.getMessage()
    -        if self.regex.match(message):
    -            if self.level is not None and record.levelno != self.level:
    -                app_log.warning(
    -                    "Got expected log message %r at unexpected level (%s vs %s)"
    -                    % (message, logging.getLevelName(self.level), record.levelname)
    -                )
    -                return True
    -            self.matched = True
    -            return False
    -        return True
    -
    -    def __enter__(self) -> "ExpectLog":
    -        if self.level is not None and self.level < self.logger.getEffectiveLevel():
    -            self.orig_level = self.logger.level
    -            self.logger.setLevel(self.level)
    -        self.logger.addFilter(self)
    -        return self
    -
    -    def __exit__(
    -        self,
    -        typ: "Optional[Type[BaseException]]",
    -        value: Optional[BaseException],
    -        tb: Optional[TracebackType],
    -    ) -> None:
    -        if self.orig_level is not None:
    -            self.logger.setLevel(self.orig_level)
    -        self.logger.removeFilter(self)
    -        if not typ and self.required and not self.matched:
    -            raise Exception("did not get expected log message")
    -
    -
    -def main(**kwargs: Any) -> None:
    -    """A simple test runner.
    -
    -    This test runner is essentially equivalent to `unittest.main` from
    -    the standard library, but adds support for Tornado-style option
    -    parsing and log formatting. It is *not* necessary to use this
    -    `main` function to run tests using `AsyncTestCase`; these tests
    -    are self-contained and can run with any test runner.
    -
    -    The easiest way to run a test is via the command line::
    -
    -        python -m tornado.testing tornado.test.web_test
    -
    -    See the standard library ``unittest`` module for ways in which
    -    tests can be specified.
    -
    -    Projects with many tests may wish to define a test script like
    -    ``tornado/test/runtests.py``.  This script should define a method
    -    ``all()`` which returns a test suite and then call
    -    `tornado.testing.main()`.  Note that even when a test script is
    -    used, the ``all()`` test suite may be overridden by naming a
    -    single test on the command line::
    -
    -        # Runs all tests
    -        python -m tornado.test.runtests
    -        # Runs one test
    -        python -m tornado.test.runtests tornado.test.web_test
    -
    -    Additional keyword arguments passed through to ``unittest.main()``.
    -    For example, use ``tornado.testing.main(verbosity=2)``
    -    to show many test details as they are run.
    -    See http://docs.python.org/library/unittest.html#unittest.main
    -    for full argument list.
    -
    -    .. versionchanged:: 5.0
    -
    -       This function produces no output of its own; only that produced
    -       by the `unittest` module (previously it would add a PASS or FAIL
    -       log message).
    -    """
    -    from tornado.options import define, options, parse_command_line
    -
    -    define(
    -        "exception_on_interrupt",
    -        type=bool,
    -        default=True,
    -        help=(
    -            "If true (default), ctrl-c raises a KeyboardInterrupt "
    -            "exception.  This prints a stack trace but cannot interrupt "
    -            "certain operations.  If false, the process is more reliably "
    -            "killed, but does not print a stack trace."
    -        ),
    -    )
    -
    -    # support the same options as unittest's command-line interface
    -    define("verbose", type=bool)
    -    define("quiet", type=bool)
    -    define("failfast", type=bool)
    -    define("catch", type=bool)
    -    define("buffer", type=bool)
    -
    -    argv = [sys.argv[0]] + parse_command_line(sys.argv)
    -
    -    if not options.exception_on_interrupt:
    -        signal.signal(signal.SIGINT, signal.SIG_DFL)
    -
    -    if options.verbose is not None:
    -        kwargs["verbosity"] = 2
    -    if options.quiet is not None:
    -        kwargs["verbosity"] = 0
    -    if options.failfast is not None:
    -        kwargs["failfast"] = True
    -    if options.catch is not None:
    -        kwargs["catchbreak"] = True
    -    if options.buffer is not None:
    -        kwargs["buffer"] = True
    -
    -    if __name__ == "__main__" and len(argv) == 1:
    -        print("No tests specified", file=sys.stderr)
    -        sys.exit(1)
    -    # In order to be able to run tests by their fully-qualified name
    -    # on the command line without importing all tests here,
    -    # module must be set to None.  Python 3.2's unittest.main ignores
    -    # defaultTest if no module is given (it tries to do its own
    -    # test discovery, which is incompatible with auto2to3), so don't
    -    # set module if we're not asking for a specific test.
    -    if len(argv) > 1:
    -        unittest.main(module=None, argv=argv, **kwargs)  # type: ignore
    -    else:
    -        unittest.main(defaultTest="all", argv=argv, **kwargs)
    -
    -
    -if __name__ == "__main__":
    -    main()
    diff --git a/telegramer/include/tornado/util.py b/telegramer/include/tornado/util.py
    deleted file mode 100644
    index 77c5f94..0000000
    --- a/telegramer/include/tornado/util.py
    +++ /dev/null
    @@ -1,474 +0,0 @@
    -"""Miscellaneous utility functions and classes.
    -
    -This module is used internally by Tornado.  It is not necessarily expected
    -that the functions and classes defined here will be useful to other
    -applications, but they are documented here in case they are.
    -
    -The one public-facing part of this module is the `Configurable` class
    -and its `~Configurable.configure` method, which becomes a part of the
    -interface of its subclasses, including `.AsyncHTTPClient`, `.IOLoop`,
    -and `.Resolver`.
    -"""
    -
    -import array
    -import atexit
    -from inspect import getfullargspec
    -import os
    -import re
    -import typing
    -import zlib
    -
    -from typing import (
    -    Any,
    -    Optional,
    -    Dict,
    -    Mapping,
    -    List,
    -    Tuple,
    -    Match,
    -    Callable,
    -    Type,
    -    Sequence,
    -)
    -
    -if typing.TYPE_CHECKING:
    -    # Additional imports only used in type comments.
    -    # This lets us make these imports lazy.
    -    import datetime  # noqa: F401
    -    from types import TracebackType  # noqa: F401
    -    from typing import Union  # noqa: F401
    -    import unittest  # noqa: F401
    -
    -# Aliases for types that are spelled differently in different Python
    -# versions. bytes_type is deprecated and no longer used in Tornado
    -# itself but is left in case anyone outside Tornado is using it.
    -bytes_type = bytes
    -unicode_type = str
    -basestring_type = str
    -
    -try:
    -    from sys import is_finalizing
    -except ImportError:
    -    # Emulate it
    -    def _get_emulated_is_finalizing() -> Callable[[], bool]:
    -        L = []  # type: List[None]
    -        atexit.register(lambda: L.append(None))
    -
    -        def is_finalizing() -> bool:
    -            # Not referencing any globals here
    -            return L != []
    -
    -        return is_finalizing
    -
    -    is_finalizing = _get_emulated_is_finalizing()
    -
    -
    -class TimeoutError(Exception):
    -    """Exception raised by `.with_timeout` and `.IOLoop.run_sync`.
    -
    -    .. versionchanged:: 5.0:
    -       Unified ``tornado.gen.TimeoutError`` and
    -       ``tornado.ioloop.TimeoutError`` as ``tornado.util.TimeoutError``.
    -       Both former names remain as aliases.
    -    """
    -
    -
    -class ObjectDict(Dict[str, Any]):
    -    """Makes a dictionary behave like an object, with attribute-style access.
    -    """
    -
    -    def __getattr__(self, name: str) -> Any:
    -        try:
    -            return self[name]
    -        except KeyError:
    -            raise AttributeError(name)
    -
    -    def __setattr__(self, name: str, value: Any) -> None:
    -        self[name] = value
    -
    -
    -class GzipDecompressor(object):
    -    """Streaming gzip decompressor.
    -
    -    The interface is like that of `zlib.decompressobj` (without some of the
    -    optional arguments, but it understands gzip headers and checksums.
    -    """
    -
    -    def __init__(self) -> None:
    -        # Magic parameter makes zlib module understand gzip header
    -        # http://stackoverflow.com/questions/1838699/how-can-i-decompress-a-gzip-stream-with-zlib
    -        # This works on cpython and pypy, but not jython.
    -        self.decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
    -
    -    def decompress(self, value: bytes, max_length: int = 0) -> bytes:
    -        """Decompress a chunk, returning newly-available data.
    -
    -        Some data may be buffered for later processing; `flush` must
    -        be called when there is no more input data to ensure that
    -        all data was processed.
    -
    -        If ``max_length`` is given, some input data may be left over
    -        in ``unconsumed_tail``; you must retrieve this value and pass
    -        it back to a future call to `decompress` if it is not empty.
    -        """
    -        return self.decompressobj.decompress(value, max_length)
    -
    -    @property
    -    def unconsumed_tail(self) -> bytes:
    -        """Returns the unconsumed portion left over
    -        """
    -        return self.decompressobj.unconsumed_tail
    -
    -    def flush(self) -> bytes:
    -        """Return any remaining buffered data not yet returned by decompress.
    -
    -        Also checks for errors such as truncated input.
    -        No other methods may be called on this object after `flush`.
    -        """
    -        return self.decompressobj.flush()
    -
    -
    -def import_object(name: str) -> Any:
    -    """Imports an object by name.
    -
    -    ``import_object('x')`` is equivalent to ``import x``.
    -    ``import_object('x.y.z')`` is equivalent to ``from x.y import z``.
    -
    -    >>> import tornado.escape
    -    >>> import_object('tornado.escape') is tornado.escape
    -    True
    -    >>> import_object('tornado.escape.utf8') is tornado.escape.utf8
    -    True
    -    >>> import_object('tornado') is tornado
    -    True
    -    >>> import_object('tornado.missing_module')
    -    Traceback (most recent call last):
    -        ...
    -    ImportError: No module named missing_module
    -    """
    -    if name.count(".") == 0:
    -        return __import__(name)
    -
    -    parts = name.split(".")
    -    obj = __import__(".".join(parts[:-1]), fromlist=[parts[-1]])
    -    try:
    -        return getattr(obj, parts[-1])
    -    except AttributeError:
    -        raise ImportError("No module named %s" % parts[-1])
    -
    -
    -def exec_in(
    -    code: Any, glob: Dict[str, Any], loc: Optional[Optional[Mapping[str, Any]]] = None
    -) -> None:
    -    if isinstance(code, str):
    -        # exec(string) inherits the caller's future imports; compile
    -        # the string first to prevent that.
    -        code = compile(code, "", "exec", dont_inherit=True)
    -    exec(code, glob, loc)
    -
    -
    -def raise_exc_info(
    -    exc_info,  # type: Tuple[Optional[type], Optional[BaseException], Optional[TracebackType]]
    -):
    -    # type: (...) -> typing.NoReturn
    -    #
    -    # This function's type annotation must use comments instead of
    -    # real annotations because typing.NoReturn does not exist in
    -    # python 3.5's typing module. The formatting is funky because this
    -    # is apparently what flake8 wants.
    -    try:
    -        if exc_info[1] is not None:
    -            raise exc_info[1].with_traceback(exc_info[2])
    -        else:
    -            raise TypeError("raise_exc_info called with no exception")
    -    finally:
    -        # Clear the traceback reference from our stack frame to
    -        # minimize circular references that slow down GC.
    -        exc_info = (None, None, None)
    -
    -
    -def errno_from_exception(e: BaseException) -> Optional[int]:
    -    """Provides the errno from an Exception object.
    -
    -    There are cases that the errno attribute was not set so we pull
    -    the errno out of the args but if someone instantiates an Exception
    -    without any args you will get a tuple error. So this function
    -    abstracts all that behavior to give you a safe way to get the
    -    errno.
    -    """
    -
    -    if hasattr(e, "errno"):
    -        return e.errno  # type: ignore
    -    elif e.args:
    -        return e.args[0]
    -    else:
    -        return None
    -
    -
    -_alphanum = frozenset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
    -
    -
    -def _re_unescape_replacement(match: Match[str]) -> str:
    -    group = match.group(1)
    -    if group[0] in _alphanum:
    -        raise ValueError("cannot unescape '\\\\%s'" % group[0])
    -    return group
    -
    -
    -_re_unescape_pattern = re.compile(r"\\(.)", re.DOTALL)
    -
    -
    -def re_unescape(s: str) -> str:
    -    r"""Unescape a string escaped by `re.escape`.
    -
    -    May raise ``ValueError`` for regular expressions which could not
    -    have been produced by `re.escape` (for example, strings containing
    -    ``\d`` cannot be unescaped).
    -
    -    .. versionadded:: 4.4
    -    """
    -    return _re_unescape_pattern.sub(_re_unescape_replacement, s)
    -
    -
    -class Configurable(object):
    -    """Base class for configurable interfaces.
    -
    -    A configurable interface is an (abstract) class whose constructor
    -    acts as a factory function for one of its implementation subclasses.
    -    The implementation subclass as well as optional keyword arguments to
    -    its initializer can be set globally at runtime with `configure`.
    -
    -    By using the constructor as the factory method, the interface
    -    looks like a normal class, `isinstance` works as usual, etc.  This
    -    pattern is most useful when the choice of implementation is likely
    -    to be a global decision (e.g. when `~select.epoll` is available,
    -    always use it instead of `~select.select`), or when a
    -    previously-monolithic class has been split into specialized
    -    subclasses.
    -
    -    Configurable subclasses must define the class methods
    -    `configurable_base` and `configurable_default`, and use the instance
    -    method `initialize` instead of ``__init__``.
    -
    -    .. versionchanged:: 5.0
    -
    -       It is now possible for configuration to be specified at
    -       multiple levels of a class hierarchy.
    -
    -    """
    -
    -    # Type annotations on this class are mostly done with comments
    -    # because they need to refer to Configurable, which isn't defined
    -    # until after the class definition block. These can use regular
    -    # annotations when our minimum python version is 3.7.
    -    #
    -    # There may be a clever way to use generics here to get more
    -    # precise types (i.e. for a particular Configurable subclass T,
    -    # all the types are subclasses of T, not just Configurable).
    -    __impl_class = None  # type: Optional[Type[Configurable]]
    -    __impl_kwargs = None  # type: Dict[str, Any]
    -
    -    def __new__(cls, *args: Any, **kwargs: Any) -> Any:
    -        base = cls.configurable_base()
    -        init_kwargs = {}  # type: Dict[str, Any]
    -        if cls is base:
    -            impl = cls.configured_class()
    -            if base.__impl_kwargs:
    -                init_kwargs.update(base.__impl_kwargs)
    -        else:
    -            impl = cls
    -        init_kwargs.update(kwargs)
    -        if impl.configurable_base() is not base:
    -            # The impl class is itself configurable, so recurse.
    -            return impl(*args, **init_kwargs)
    -        instance = super(Configurable, cls).__new__(impl)
    -        # initialize vs __init__ chosen for compatibility with AsyncHTTPClient
    -        # singleton magic.  If we get rid of that we can switch to __init__
    -        # here too.
    -        instance.initialize(*args, **init_kwargs)
    -        return instance
    -
    -    @classmethod
    -    def configurable_base(cls):
    -        # type: () -> Type[Configurable]
    -        """Returns the base class of a configurable hierarchy.
    -
    -        This will normally return the class in which it is defined.
    -        (which is *not* necessarily the same as the ``cls`` classmethod
    -        parameter).
    -
    -        """
    -        raise NotImplementedError()
    -
    -    @classmethod
    -    def configurable_default(cls):
    -        # type: () -> Type[Configurable]
    -        """Returns the implementation class to be used if none is configured."""
    -        raise NotImplementedError()
    -
    -    def _initialize(self) -> None:
    -        pass
    -
    -    initialize = _initialize  # type: Callable[..., None]
    -    """Initialize a `Configurable` subclass instance.
    -
    -    Configurable classes should use `initialize` instead of ``__init__``.
    -
    -    .. versionchanged:: 4.2
    -       Now accepts positional arguments in addition to keyword arguments.
    -    """
    -
    -    @classmethod
    -    def configure(cls, impl, **kwargs):
    -        # type: (Union[None, str, Type[Configurable]], Any) -> None
    -        """Sets the class to use when the base class is instantiated.
    -
    -        Keyword arguments will be saved and added to the arguments passed
    -        to the constructor.  This can be used to set global defaults for
    -        some parameters.
    -        """
    -        base = cls.configurable_base()
    -        if isinstance(impl, str):
    -            impl = typing.cast(Type[Configurable], import_object(impl))
    -        if impl is not None and not issubclass(impl, cls):
    -            raise ValueError("Invalid subclass of %s" % cls)
    -        base.__impl_class = impl
    -        base.__impl_kwargs = kwargs
    -
    -    @classmethod
    -    def configured_class(cls):
    -        # type: () -> Type[Configurable]
    -        """Returns the currently configured class."""
    -        base = cls.configurable_base()
    -        # Manually mangle the private name to see whether this base
    -        # has been configured (and not another base higher in the
    -        # hierarchy).
    -        if base.__dict__.get("_Configurable__impl_class") is None:
    -            base.__impl_class = cls.configurable_default()
    -        if base.__impl_class is not None:
    -            return base.__impl_class
    -        else:
    -            # Should be impossible, but mypy wants an explicit check.
    -            raise ValueError("configured class not found")
    -
    -    @classmethod
    -    def _save_configuration(cls):
    -        # type: () -> Tuple[Optional[Type[Configurable]], Dict[str, Any]]
    -        base = cls.configurable_base()
    -        return (base.__impl_class, base.__impl_kwargs)
    -
    -    @classmethod
    -    def _restore_configuration(cls, saved):
    -        # type: (Tuple[Optional[Type[Configurable]], Dict[str, Any]]) -> None
    -        base = cls.configurable_base()
    -        base.__impl_class = saved[0]
    -        base.__impl_kwargs = saved[1]
    -
    -
    -class ArgReplacer(object):
    -    """Replaces one value in an ``args, kwargs`` pair.
    -
    -    Inspects the function signature to find an argument by name
    -    whether it is passed by position or keyword.  For use in decorators
    -    and similar wrappers.
    -    """
    -
    -    def __init__(self, func: Callable, name: str) -> None:
    -        self.name = name
    -        try:
    -            self.arg_pos = self._getargnames(func).index(name)  # type: Optional[int]
    -        except ValueError:
    -            # Not a positional parameter
    -            self.arg_pos = None
    -
    -    def _getargnames(self, func: Callable) -> List[str]:
    -        try:
    -            return getfullargspec(func).args
    -        except TypeError:
    -            if hasattr(func, "func_code"):
    -                # Cython-generated code has all the attributes needed
    -                # by inspect.getfullargspec, but the inspect module only
    -                # works with ordinary functions. Inline the portion of
    -                # getfullargspec that we need here. Note that for static
    -                # functions the @cython.binding(True) decorator must
    -                # be used (for methods it works out of the box).
    -                code = func.func_code  # type: ignore
    -                return code.co_varnames[: code.co_argcount]
    -            raise
    -
    -    def get_old_value(
    -        self, args: Sequence[Any], kwargs: Dict[str, Any], default: Any = None
    -    ) -> Any:
    -        """Returns the old value of the named argument without replacing it.
    -
    -        Returns ``default`` if the argument is not present.
    -        """
    -        if self.arg_pos is not None and len(args) > self.arg_pos:
    -            return args[self.arg_pos]
    -        else:
    -            return kwargs.get(self.name, default)
    -
    -    def replace(
    -        self, new_value: Any, args: Sequence[Any], kwargs: Dict[str, Any]
    -    ) -> Tuple[Any, Sequence[Any], Dict[str, Any]]:
    -        """Replace the named argument in ``args, kwargs`` with ``new_value``.
    -
    -        Returns ``(old_value, args, kwargs)``.  The returned ``args`` and
    -        ``kwargs`` objects may not be the same as the input objects, or
    -        the input objects may be mutated.
    -
    -        If the named argument was not found, ``new_value`` will be added
    -        to ``kwargs`` and None will be returned as ``old_value``.
    -        """
    -        if self.arg_pos is not None and len(args) > self.arg_pos:
    -            # The arg to replace is passed positionally
    -            old_value = args[self.arg_pos]
    -            args = list(args)  # *args is normally a tuple
    -            args[self.arg_pos] = new_value
    -        else:
    -            # The arg to replace is either omitted or passed by keyword.
    -            old_value = kwargs.get(self.name)
    -            kwargs[self.name] = new_value
    -        return old_value, args, kwargs
    -
    -
    -def timedelta_to_seconds(td):
    -    # type: (datetime.timedelta) -> float
    -    """Equivalent to ``td.total_seconds()`` (introduced in Python 2.7)."""
    -    return td.total_seconds()
    -
    -
    -def _websocket_mask_python(mask: bytes, data: bytes) -> bytes:
    -    """Websocket masking function.
    -
    -    `mask` is a `bytes` object of length 4; `data` is a `bytes` object of any length.
    -    Returns a `bytes` object of the same length as `data` with the mask applied
    -    as specified in section 5.3 of RFC 6455.
    -
    -    This pure-python implementation may be replaced by an optimized version when available.
    -    """
    -    mask_arr = array.array("B", mask)
    -    unmasked_arr = array.array("B", data)
    -    for i in range(len(data)):
    -        unmasked_arr[i] = unmasked_arr[i] ^ mask_arr[i % 4]
    -    return unmasked_arr.tobytes()
    -
    -
    -if os.environ.get("TORNADO_NO_EXTENSION") or os.environ.get("TORNADO_EXTENSION") == "0":
    -    # These environment variables exist to make it easier to do performance
    -    # comparisons; they are not guaranteed to remain supported in the future.
    -    _websocket_mask = _websocket_mask_python
    -else:
    -    try:
    -        from tornado.speedups import websocket_mask as _websocket_mask
    -    except ImportError:
    -        if os.environ.get("TORNADO_EXTENSION") == "1":
    -            raise
    -        _websocket_mask = _websocket_mask_python
    -
    -
    -def doctests():
    -    # type: () -> unittest.TestSuite
    -    import doctest
    -
    -    return doctest.DocTestSuite()
    diff --git a/telegramer/include/tornado/web.py b/telegramer/include/tornado/web.py
    deleted file mode 100644
    index 546e6ec..0000000
    --- a/telegramer/include/tornado/web.py
    +++ /dev/null
    @@ -1,3588 +0,0 @@
    -#
    -# Copyright 2009 Facebook
    -#
    -# Licensed under the Apache License, Version 2.0 (the "License"); you may
    -# not use this file except in compliance with the License. You may obtain
    -# a copy of the License at
    -#
    -#     http://www.apache.org/licenses/LICENSE-2.0
    -#
    -# Unless required by applicable law or agreed to in writing, software
    -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    -# License for the specific language governing permissions and limitations
    -# under the License.
    -
    -"""``tornado.web`` provides a simple web framework with asynchronous
    -features that allow it to scale to large numbers of open connections,
    -making it ideal for `long polling
    -`_.
    -
    -Here is a simple "Hello, world" example app:
    -
    -.. testcode::
    -
    -    import tornado.ioloop
    -    import tornado.web
    -
    -    class MainHandler(tornado.web.RequestHandler):
    -        def get(self):
    -            self.write("Hello, world")
    -
    -    if __name__ == "__main__":
    -        application = tornado.web.Application([
    -            (r"/", MainHandler),
    -        ])
    -        application.listen(8888)
    -        tornado.ioloop.IOLoop.current().start()
    -
    -.. testoutput::
    -   :hide:
    -
    -
    -See the :doc:`guide` for additional information.
    -
    -Thread-safety notes
    --------------------
    -
    -In general, methods on `RequestHandler` and elsewhere in Tornado are
    -not thread-safe. In particular, methods such as
    -`~RequestHandler.write()`, `~RequestHandler.finish()`, and
    -`~RequestHandler.flush()` must only be called from the main thread. If
    -you use multiple threads it is important to use `.IOLoop.add_callback`
    -to transfer control back to the main thread before finishing the
    -request, or to limit your use of other threads to
    -`.IOLoop.run_in_executor` and ensure that your callbacks running in
    -the executor do not refer to Tornado objects.
    -
    -"""
    -
    -import base64
    -import binascii
    -import datetime
    -import email.utils
    -import functools
    -import gzip
    -import hashlib
    -import hmac
    -import http.cookies
    -from inspect import isclass
    -from io import BytesIO
    -import mimetypes
    -import numbers
    -import os.path
    -import re
    -import sys
    -import threading
    -import time
    -import tornado
    -import traceback
    -import types
    -import urllib.parse
    -from urllib.parse import urlencode
    -
    -from tornado.concurrent import Future, future_set_result_unless_cancelled
    -from tornado import escape
    -from tornado import gen
    -from tornado.httpserver import HTTPServer
    -from tornado import httputil
    -from tornado import iostream
    -import tornado.locale
    -from tornado import locale
    -from tornado.log import access_log, app_log, gen_log
    -from tornado import template
    -from tornado.escape import utf8, _unicode
    -from tornado.routing import (
    -    AnyMatches,
    -    DefaultHostMatches,
    -    HostMatches,
    -    ReversibleRouter,
    -    Rule,
    -    ReversibleRuleRouter,
    -    URLSpec,
    -    _RuleList,
    -)
    -from tornado.util import ObjectDict, unicode_type, _websocket_mask
    -
    -url = URLSpec
    -
    -from typing import (
    -    Dict,
    -    Any,
    -    Union,
    -    Optional,
    -    Awaitable,
    -    Tuple,
    -    List,
    -    Callable,
    -    Iterable,
    -    Generator,
    -    Type,
    -    cast,
    -    overload,
    -)
    -from types import TracebackType
    -import typing
    -
    -if typing.TYPE_CHECKING:
    -    from typing import Set  # noqa: F401
    -
    -
    -# The following types are accepted by RequestHandler.set_header
    -# and related methods.
    -_HeaderTypes = Union[bytes, unicode_type, int, numbers.Integral, datetime.datetime]
    -
    -_CookieSecretTypes = Union[str, bytes, Dict[int, str], Dict[int, bytes]]
    -
    -
    -MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1
    -"""The oldest signed value version supported by this version of Tornado.
    -
    -Signed values older than this version cannot be decoded.
    -
    -.. versionadded:: 3.2.1
    -"""
    -
    -MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2
    -"""The newest signed value version supported by this version of Tornado.
    -
    -Signed values newer than this version cannot be decoded.
    -
    -.. versionadded:: 3.2.1
    -"""
    -
    -DEFAULT_SIGNED_VALUE_VERSION = 2
    -"""The signed value version produced by `.RequestHandler.create_signed_value`.
    -
    -May be overridden by passing a ``version`` keyword argument.
    -
    -.. versionadded:: 3.2.1
    -"""
    -
    -DEFAULT_SIGNED_VALUE_MIN_VERSION = 1
    -"""The oldest signed value accepted by `.RequestHandler.get_secure_cookie`.
    -
    -May be overridden by passing a ``min_version`` keyword argument.
    -
    -.. versionadded:: 3.2.1
    -"""
    -
    -
    -class _ArgDefaultMarker:
    -    pass
    -
    -
    -_ARG_DEFAULT = _ArgDefaultMarker()
    -
    -
    -class RequestHandler(object):
    -    """Base class for HTTP request handlers.
    -
    -    Subclasses must define at least one of the methods defined in the
    -    "Entry points" section below.
    -
    -    Applications should not construct `RequestHandler` objects
    -    directly and subclasses should not override ``__init__`` (override
    -    `~RequestHandler.initialize` instead).
    -
    -    """
    -
    -    SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS")
    -
    -    _template_loaders = {}  # type: Dict[str, template.BaseLoader]
    -    _template_loader_lock = threading.Lock()
    -    _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")
    -
    -    _stream_request_body = False
    -
    -    # Will be set in _execute.
    -    _transforms = None  # type: List[OutputTransform]
    -    path_args = None  # type: List[str]
    -    path_kwargs = None  # type: Dict[str, str]
    -
    -    def __init__(
    -        self,
    -        application: "Application",
    -        request: httputil.HTTPServerRequest,
    -        **kwargs: Any
    -    ) -> None:
    -        super().__init__()
    -
    -        self.application = application
    -        self.request = request
    -        self._headers_written = False
    -        self._finished = False
    -        self._auto_finish = True
    -        self._prepared_future = None
    -        self.ui = ObjectDict(
    -            (n, self._ui_method(m)) for n, m in application.ui_methods.items()
    -        )
    -        # UIModules are available as both `modules` and `_tt_modules` in the
    -        # template namespace.  Historically only `modules` was available
    -        # but could be clobbered by user additions to the namespace.
    -        # The template {% module %} directive looks in `_tt_modules` to avoid
    -        # possible conflicts.
    -        self.ui["_tt_modules"] = _UIModuleNamespace(self, application.ui_modules)
    -        self.ui["modules"] = self.ui["_tt_modules"]
    -        self.clear()
    -        assert self.request.connection is not None
    -        # TODO: need to add set_close_callback to HTTPConnection interface
    -        self.request.connection.set_close_callback(  # type: ignore
    -            self.on_connection_close
    -        )
    -        self.initialize(**kwargs)  # type: ignore
    -
    -    def _initialize(self) -> None:
    -        pass
    -
    -    initialize = _initialize  # type: Callable[..., None]
    -    """Hook for subclass initialization. Called for each request.
    -
    -    A dictionary passed as the third argument of a ``URLSpec`` will be
    -    supplied as keyword arguments to ``initialize()``.
    -
    -    Example::
    -
    -        class ProfileHandler(RequestHandler):
    -            def initialize(self, database):
    -                self.database = database
    -
    -            def get(self, username):
    -                ...
    -
    -        app = Application([
    -            (r'/user/(.*)', ProfileHandler, dict(database=database)),
    -            ])
    -    """
    -
    -    @property
    -    def settings(self) -> Dict[str, Any]:
    -        """An alias for `self.application.settings `."""
    -        return self.application.settings
    -
    -    def _unimplemented_method(self, *args: str, **kwargs: str) -> None:
    -        raise HTTPError(405)
    -
    -    head = _unimplemented_method  # type: Callable[..., Optional[Awaitable[None]]]
    -    get = _unimplemented_method  # type: Callable[..., Optional[Awaitable[None]]]
    -    post = _unimplemented_method  # type: Callable[..., Optional[Awaitable[None]]]
    -    delete = _unimplemented_method  # type: Callable[..., Optional[Awaitable[None]]]
    -    patch = _unimplemented_method  # type: Callable[..., Optional[Awaitable[None]]]
    -    put = _unimplemented_method  # type: Callable[..., Optional[Awaitable[None]]]
    -    options = _unimplemented_method  # type: Callable[..., Optional[Awaitable[None]]]
    -
    -    def prepare(self) -> Optional[Awaitable[None]]:
    -        """Called at the beginning of a request before  `get`/`post`/etc.
    -
    -        Override this method to perform common initialization regardless
    -        of the request method.
    -
    -        Asynchronous support: Use ``async def`` or decorate this method with
    -        `.gen.coroutine` to make it asynchronous.
    -        If this method returns an  ``Awaitable`` execution will not proceed
    -        until the ``Awaitable`` is done.
    -
    -        .. versionadded:: 3.1
    -           Asynchronous support.
    -        """
    -        pass
    -
    -    def on_finish(self) -> None:
    -        """Called after the end of a request.
    -
    -        Override this method to perform cleanup, logging, etc.
    -        This method is a counterpart to `prepare`.  ``on_finish`` may
    -        not produce any output, as it is called after the response
    -        has been sent to the client.
    -        """
    -        pass
    -
    -    def on_connection_close(self) -> None:
    -        """Called in async handlers if the client closed the connection.
    -
    -        Override this to clean up resources associated with
    -        long-lived connections.  Note that this method is called only if
    -        the connection was closed during asynchronous processing; if you
    -        need to do cleanup after every request override `on_finish`
    -        instead.
    -
    -        Proxies may keep a connection open for a time (perhaps
    -        indefinitely) after the client has gone away, so this method
    -        may not be called promptly after the end user closes their
    -        connection.
    -        """
    -        if _has_stream_request_body(self.__class__):
    -            if not self.request._body_future.done():
    -                self.request._body_future.set_exception(iostream.StreamClosedError())
    -                self.request._body_future.exception()
    -
    -    def clear(self) -> None:
    -        """Resets all headers and content for this response."""
    -        self._headers = httputil.HTTPHeaders(
    -            {
    -                "Server": "TornadoServer/%s" % tornado.version,
    -                "Content-Type": "text/html; charset=UTF-8",
    -                "Date": httputil.format_timestamp(time.time()),
    -            }
    -        )
    -        self.set_default_headers()
    -        self._write_buffer = []  # type: List[bytes]
    -        self._status_code = 200
    -        self._reason = httputil.responses[200]
    -
    -    def set_default_headers(self) -> None:
    -        """Override this to set HTTP headers at the beginning of the request.
    -
    -        For example, this is the place to set a custom ``Server`` header.
    -        Note that setting such headers in the normal flow of request
    -        processing may not do what you want, since headers may be reset
    -        during error handling.
    -        """
    -        pass
    -
    -    def set_status(self, status_code: int, reason: Optional[str] = None) -> None:
    -        """Sets the status code for our response.
    -
    -        :arg int status_code: Response status code.
    -        :arg str reason: Human-readable reason phrase describing the status
    -            code. If ``None``, it will be filled in from
    -            `http.client.responses` or "Unknown".
    -
    -        .. versionchanged:: 5.0
    -
    -           No longer validates that the response code is in
    -           `http.client.responses`.
    -        """
    -        self._status_code = status_code
    -        if reason is not None:
    -            self._reason = escape.native_str(reason)
    -        else:
    -            self._reason = httputil.responses.get(status_code, "Unknown")
    -
    -    def get_status(self) -> int:
    -        """Returns the status code for our response."""
    -        return self._status_code
    -
    -    def set_header(self, name: str, value: _HeaderTypes) -> None:
    -        """Sets the given response header name and value.
    -
    -        All header values are converted to strings (`datetime` objects
    -        are formatted according to the HTTP specification for the
    -        ``Date`` header).
    -
    -        """
    -        self._headers[name] = self._convert_header_value(value)
    -
    -    def add_header(self, name: str, value: _HeaderTypes) -> None:
    -        """Adds the given response header and value.
    -
    -        Unlike `set_header`, `add_header` may be called multiple times
    -        to return multiple values for the same header.
    -        """
    -        self._headers.add(name, self._convert_header_value(value))
    -
    -    def clear_header(self, name: str) -> None:
    -        """Clears an outgoing header, undoing a previous `set_header` call.
    -
    -        Note that this method does not apply to multi-valued headers
    -        set by `add_header`.
    -        """
    -        if name in self._headers:
    -            del self._headers[name]
    -
    -    _INVALID_HEADER_CHAR_RE = re.compile(r"[\x00-\x1f]")
    -
    -    def _convert_header_value(self, value: _HeaderTypes) -> str:
    -        # Convert the input value to a str. This type check is a bit
    -        # subtle: The bytes case only executes on python 3, and the
    -        # unicode case only executes on python 2, because the other
    -        # cases are covered by the first match for str.
    -        if isinstance(value, str):
    -            retval = value
    -        elif isinstance(value, bytes):  # py3
    -            # Non-ascii characters in headers are not well supported,
    -            # but if you pass bytes, use latin1 so they pass through as-is.
    -            retval = value.decode("latin1")
    -        elif isinstance(value, unicode_type):  # py2
    -            # TODO: This is inconsistent with the use of latin1 above,
    -            # but it's been that way for a long time. Should it change?
    -            retval = escape.utf8(value)
    -        elif isinstance(value, numbers.Integral):
    -            # return immediately since we know the converted value will be safe
    -            return str(value)
    -        elif isinstance(value, datetime.datetime):
    -            return httputil.format_timestamp(value)
    -        else:
    -            raise TypeError("Unsupported header value %r" % value)
    -        # If \n is allowed into the header, it is possible to inject
    -        # additional headers or split the request.
    -        if RequestHandler._INVALID_HEADER_CHAR_RE.search(retval):
    -            raise ValueError("Unsafe header value %r", retval)
    -        return retval
    -
    -    @overload
    -    def get_argument(self, name: str, default: str, strip: bool = True) -> str:
    -        pass
    -
    -    @overload
    -    def get_argument(  # noqa: F811
    -        self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True
    -    ) -> str:
    -        pass
    -
    -    @overload
    -    def get_argument(  # noqa: F811
    -        self, name: str, default: None, strip: bool = True
    -    ) -> Optional[str]:
    -        pass
    -
    -    def get_argument(  # noqa: F811
    -        self,
    -        name: str,
    -        default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
    -        strip: bool = True,
    -    ) -> Optional[str]:
    -        """Returns the value of the argument with the given name.
    -
    -        If default is not provided, the argument is considered to be
    -        required, and we raise a `MissingArgumentError` if it is missing.
    -
    -        If the argument appears in the request more than once, we return the
    -        last value.
    -
    -        This method searches both the query and body arguments.
    -        """
    -        return self._get_argument(name, default, self.request.arguments, strip)
    -
    -    def get_arguments(self, name: str, strip: bool = True) -> List[str]:
    -        """Returns a list of the arguments with the given name.
    -
    -        If the argument is not present, returns an empty list.
    -
    -        This method searches both the query and body arguments.
    -        """
    -
    -        # Make sure `get_arguments` isn't accidentally being called with a
    -        # positional argument that's assumed to be a default (like in
    -        # `get_argument`.)
    -        assert isinstance(strip, bool)
    -
    -        return self._get_arguments(name, self.request.arguments, strip)
    -
    -    def get_body_argument(
    -        self,
    -        name: str,
    -        default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
    -        strip: bool = True,
    -    ) -> Optional[str]:
    -        """Returns the value of the argument with the given name
    -        from the request body.
    -
    -        If default is not provided, the argument is considered to be
    -        required, and we raise a `MissingArgumentError` if it is missing.
    -
    -        If the argument appears in the url more than once, we return the
    -        last value.
    -
    -        .. versionadded:: 3.2
    -        """
    -        return self._get_argument(name, default, self.request.body_arguments, strip)
    -
    -    def get_body_arguments(self, name: str, strip: bool = True) -> List[str]:
    -        """Returns a list of the body arguments with the given name.
    -
    -        If the argument is not present, returns an empty list.
    -
    -        .. versionadded:: 3.2
    -        """
    -        return self._get_arguments(name, self.request.body_arguments, strip)
    -
    -    def get_query_argument(
    -        self,
    -        name: str,
    -        default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT,
    -        strip: bool = True,
    -    ) -> Optional[str]:
    -        """Returns the value of the argument with the given name
    -        from the request query string.
    -
    -        If default is not provided, the argument is considered to be
    -        required, and we raise a `MissingArgumentError` if it is missing.
    -
    -        If the argument appears in the url more than once, we return the
    -        last value.
    -
    -        .. versionadded:: 3.2
    -        """
    -        return self._get_argument(name, default, self.request.query_arguments, strip)
    -
    -    def get_query_arguments(self, name: str, strip: bool = True) -> List[str]:
    -        """Returns a list of the query arguments with the given name.
    -
    -        If the argument is not present, returns an empty list.
    -
    -        .. versionadded:: 3.2
    -        """
    -        return self._get_arguments(name, self.request.query_arguments, strip)
    -
    -    def _get_argument(
    -        self,
    -        name: str,
    -        default: Union[None, str, _ArgDefaultMarker],
    -        source: Dict[str, List[bytes]],
    -        strip: bool = True,
    -    ) -> Optional[str]:
    -        args = self._get_arguments(name, source, strip=strip)
    -        if not args:
    -            if isinstance(default, _ArgDefaultMarker):
    -                raise MissingArgumentError(name)
    -            return default
    -        return args[-1]
    -
    -    def _get_arguments(
    -        self, name: str, source: Dict[str, List[bytes]], strip: bool = True
    -    ) -> List[str]:
    -        values = []
    -        for v in source.get(name, []):
    -            s = self.decode_argument(v, name=name)
    -            if isinstance(s, unicode_type):
    -                # Get rid of any weird control chars (unless decoding gave
    -                # us bytes, in which case leave it alone)
    -                s = RequestHandler._remove_control_chars_regex.sub(" ", s)
    -            if strip:
    -                s = s.strip()
    -            values.append(s)
    -        return values
    -
    -    def decode_argument(self, value: bytes, name: Optional[str] = None) -> str:
    -        """Decodes an argument from the request.
    -
    -        The argument has been percent-decoded and is now a byte string.
    -        By default, this method decodes the argument as utf-8 and returns
    -        a unicode string, but this may be overridden in subclasses.
    -
    -        This method is used as a filter for both `get_argument()` and for
    -        values extracted from the url and passed to `get()`/`post()`/etc.
    -
    -        The name of the argument is provided if known, but may be None
    -        (e.g. for unnamed groups in the url regex).
    -        """
    -        try:
    -            return _unicode(value)
    -        except UnicodeDecodeError:
    -            raise HTTPError(
    -                400, "Invalid unicode in %s: %r" % (name or "url", value[:40])
    -            )
    -
    -    @property
    -    def cookies(self) -> Dict[str, http.cookies.Morsel]:
    -        """An alias for
    -        `self.request.cookies <.httputil.HTTPServerRequest.cookies>`."""
    -        return self.request.cookies
    -
    -    def get_cookie(self, name: str, default: Optional[str] = None) -> Optional[str]:
    -        """Returns the value of the request cookie with the given name.
    -
    -        If the named cookie is not present, returns ``default``.
    -
    -        This method only returns cookies that were present in the request.
    -        It does not see the outgoing cookies set by `set_cookie` in this
    -        handler.
    -        """
    -        if self.request.cookies is not None and name in self.request.cookies:
    -            return self.request.cookies[name].value
    -        return default
    -
    -    def set_cookie(
    -        self,
    -        name: str,
    -        value: Union[str, bytes],
    -        domain: Optional[str] = None,
    -        expires: Optional[Union[float, Tuple, datetime.datetime]] = None,
    -        path: str = "/",
    -        expires_days: Optional[float] = None,
    -        **kwargs: Any
    -    ) -> None:
    -        """Sets an outgoing cookie name/value with the given options.
    -
    -        Newly-set cookies are not immediately visible via `get_cookie`;
    -        they are not present until the next request.
    -
    -        expires may be a numeric timestamp as returned by `time.time`,
    -        a time tuple as returned by `time.gmtime`, or a
    -        `datetime.datetime` object.
    -
    -        Additional keyword arguments are set on the cookies.Morsel
    -        directly.
    -        See https://docs.python.org/3/library/http.cookies.html#http.cookies.Morsel
    -        for available attributes.
    -        """
    -        # The cookie library only accepts type str, in both python 2 and 3
    -        name = escape.native_str(name)
    -        value = escape.native_str(value)
    -        if re.search(r"[\x00-\x20]", name + value):
    -            # Don't let us accidentally inject bad stuff
    -            raise ValueError("Invalid cookie %r: %r" % (name, value))
    -        if not hasattr(self, "_new_cookie"):
    -            self._new_cookie = (
    -                http.cookies.SimpleCookie()
    -            )  # type: http.cookies.SimpleCookie
    -        if name in self._new_cookie:
    -            del self._new_cookie[name]
    -        self._new_cookie[name] = value
    -        morsel = self._new_cookie[name]
    -        if domain:
    -            morsel["domain"] = domain
    -        if expires_days is not None and not expires:
    -            expires = datetime.datetime.utcnow() + datetime.timedelta(days=expires_days)
    -        if expires:
    -            morsel["expires"] = httputil.format_timestamp(expires)
    -        if path:
    -            morsel["path"] = path
    -        for k, v in kwargs.items():
    -            if k == "max_age":
    -                k = "max-age"
    -
    -            # skip falsy values for httponly and secure flags because
    -            # SimpleCookie sets them regardless
    -            if k in ["httponly", "secure"] and not v:
    -                continue
    -
    -            morsel[k] = v
    -
    -    def clear_cookie(
    -        self, name: str, path: str = "/", domain: Optional[str] = None
    -    ) -> None:
    -        """Deletes the cookie with the given name.
    -
    -        Due to limitations of the cookie protocol, you must pass the same
    -        path and domain to clear a cookie as were used when that cookie
    -        was set (but there is no way to find out on the server side
    -        which values were used for a given cookie).
    -
    -        Similar to `set_cookie`, the effect of this method will not be
    -        seen until the following request.
    -        """
    -        expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
    -        self.set_cookie(name, value="", path=path, expires=expires, domain=domain)
    -
    -    def clear_all_cookies(self, path: str = "/", domain: Optional[str] = None) -> None:
    -        """Deletes all the cookies the user sent with this request.
    -
    -        See `clear_cookie` for more information on the path and domain
    -        parameters.
    -
    -        Similar to `set_cookie`, the effect of this method will not be
    -        seen until the following request.
    -
    -        .. versionchanged:: 3.2
    -
    -           Added the ``path`` and ``domain`` parameters.
    -        """
    -        for name in self.request.cookies:
    -            self.clear_cookie(name, path=path, domain=domain)
    -
    -    def set_secure_cookie(
    -        self,
    -        name: str,
    -        value: Union[str, bytes],
    -        expires_days: Optional[float] = 30,
    -        version: Optional[int] = None,
    -        **kwargs: Any
    -    ) -> None:
    -        """Signs and timestamps a cookie so it cannot be forged.
    -
    -        You must specify the ``cookie_secret`` setting in your Application
    -        to use this method. It should be a long, random sequence of bytes
    -        to be used as the HMAC secret for the signature.
    -
    -        To read a cookie set with this method, use `get_secure_cookie()`.
    -
    -        Note that the ``expires_days`` parameter sets the lifetime of the
    -        cookie in the browser, but is independent of the ``max_age_days``
    -        parameter to `get_secure_cookie`.
    -        A value of None limits the lifetime to the current browser session.
    -
    -        Secure cookies may contain arbitrary byte values, not just unicode
    -        strings (unlike regular cookies)
    -
    -        Similar to `set_cookie`, the effect of this method will not be
    -        seen until the following request.
    -
    -        .. versionchanged:: 3.2.1
    -
    -           Added the ``version`` argument.  Introduced cookie version 2
    -           and made it the default.
    -        """
    -        self.set_cookie(
    -            name,
    -            self.create_signed_value(name, value, version=version),
    -            expires_days=expires_days,
    -            **kwargs
    -        )
    -
    -    def create_signed_value(
    -        self, name: str, value: Union[str, bytes], version: Optional[int] = None
    -    ) -> bytes:
    -        """Signs and timestamps a string so it cannot be forged.
    -
    -        Normally used via set_secure_cookie, but provided as a separate
    -        method for non-cookie uses.  To decode a value not stored
    -        as a cookie use the optional value argument to get_secure_cookie.
    -
    -        .. versionchanged:: 3.2.1
    -
    -           Added the ``version`` argument.  Introduced cookie version 2
    -           and made it the default.
    -        """
    -        self.require_setting("cookie_secret", "secure cookies")
    -        secret = self.application.settings["cookie_secret"]
    -        key_version = None
    -        if isinstance(secret, dict):
    -            if self.application.settings.get("key_version") is None:
    -                raise Exception("key_version setting must be used for secret_key dicts")
    -            key_version = self.application.settings["key_version"]
    -
    -        return create_signed_value(
    -            secret, name, value, version=version, key_version=key_version
    -        )
    -
    -    def get_secure_cookie(
    -        self,
    -        name: str,
    -        value: Optional[str] = None,
    -        max_age_days: float = 31,
    -        min_version: Optional[int] = None,
    -    ) -> Optional[bytes]:
    -        """Returns the given signed cookie if it validates, or None.
    -
    -        The decoded cookie value is returned as a byte string (unlike
    -        `get_cookie`).
    -
    -        Similar to `get_cookie`, this method only returns cookies that
    -        were present in the request. It does not see outgoing cookies set by
    -        `set_secure_cookie` in this handler.
    -
    -        .. versionchanged:: 3.2.1
    -
    -           Added the ``min_version`` argument.  Introduced cookie version 2;
    -           both versions 1 and 2 are accepted by default.
    -        """
    -        self.require_setting("cookie_secret", "secure cookies")
    -        if value is None:
    -            value = self.get_cookie(name)
    -        return decode_signed_value(
    -            self.application.settings["cookie_secret"],
    -            name,
    -            value,
    -            max_age_days=max_age_days,
    -            min_version=min_version,
    -        )
    -
    -    def get_secure_cookie_key_version(
    -        self, name: str, value: Optional[str] = None
    -    ) -> Optional[int]:
    -        """Returns the signing key version of the secure cookie.
    -
    -        The version is returned as int.
    -        """
    -        self.require_setting("cookie_secret", "secure cookies")
    -        if value is None:
    -            value = self.get_cookie(name)
    -        if value is None:
    -            return None
    -        return get_signature_key_version(value)
    -
    -    def redirect(
    -        self, url: str, permanent: bool = False, status: Optional[int] = None
    -    ) -> None:
    -        """Sends a redirect to the given (optionally relative) URL.
    -
    -        If the ``status`` argument is specified, that value is used as the
    -        HTTP status code; otherwise either 301 (permanent) or 302
    -        (temporary) is chosen based on the ``permanent`` argument.
    -        The default is 302 (temporary).
    -        """
    -        if self._headers_written:
    -            raise Exception("Cannot redirect after headers have been written")
    -        if status is None:
    -            status = 301 if permanent else 302
    -        else:
    -            assert isinstance(status, int) and 300 <= status <= 399
    -        self.set_status(status)
    -        self.set_header("Location", utf8(url))
    -        self.finish()
    -
    -    def write(self, chunk: Union[str, bytes, dict]) -> None:
    -        """Writes the given chunk to the output buffer.
    -
    -        To write the output to the network, use the `flush()` method below.
    -
    -        If the given chunk is a dictionary, we write it as JSON and set
    -        the Content-Type of the response to be ``application/json``.
    -        (if you want to send JSON as a different ``Content-Type``, call
    -        ``set_header`` *after* calling ``write()``).
    -
    -        Note that lists are not converted to JSON because of a potential
    -        cross-site security vulnerability.  All JSON output should be
    -        wrapped in a dictionary.  More details at
    -        http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and
    -        https://github.com/facebook/tornado/issues/1009
    -        """
    -        if self._finished:
    -            raise RuntimeError("Cannot write() after finish()")
    -        if not isinstance(chunk, (bytes, unicode_type, dict)):
    -            message = "write() only accepts bytes, unicode, and dict objects"
    -            if isinstance(chunk, list):
    -                message += (
    -                    ". Lists not accepted for security reasons; see "
    -                    + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write"  # noqa: E501
    -                )
    -            raise TypeError(message)
    -        if isinstance(chunk, dict):
    -            chunk = escape.json_encode(chunk)
    -            self.set_header("Content-Type", "application/json; charset=UTF-8")
    -        chunk = utf8(chunk)
    -        self._write_buffer.append(chunk)
    -
    -    def render(self, template_name: str, **kwargs: Any) -> "Future[None]":
    -        """Renders the template with the given arguments as the response.
    -
    -        ``render()`` calls ``finish()``, so no other output methods can be called
    -        after it.
    -
    -        Returns a `.Future` with the same semantics as the one returned by `finish`.
    -        Awaiting this `.Future` is optional.
    -
    -        .. versionchanged:: 5.1
    -
    -           Now returns a `.Future` instead of ``None``.
    -        """
    -        if self._finished:
    -            raise RuntimeError("Cannot render() after finish()")
    -        html = self.render_string(template_name, **kwargs)
    -
    -        # Insert the additional JS and CSS added by the modules on the page
    -        js_embed = []
    -        js_files = []
    -        css_embed = []
    -        css_files = []
    -        html_heads = []
    -        html_bodies = []
    -        for module in getattr(self, "_active_modules", {}).values():
    -            embed_part = module.embedded_javascript()
    -            if embed_part:
    -                js_embed.append(utf8(embed_part))
    -            file_part = module.javascript_files()
    -            if file_part:
    -                if isinstance(file_part, (unicode_type, bytes)):
    -                    js_files.append(_unicode(file_part))
    -                else:
    -                    js_files.extend(file_part)
    -            embed_part = module.embedded_css()
    -            if embed_part:
    -                css_embed.append(utf8(embed_part))
    -            file_part = module.css_files()
    -            if file_part:
    -                if isinstance(file_part, (unicode_type, bytes)):
    -                    css_files.append(_unicode(file_part))
    -                else:
    -                    css_files.extend(file_part)
    -            head_part = module.html_head()
    -            if head_part:
    -                html_heads.append(utf8(head_part))
    -            body_part = module.html_body()
    -            if body_part:
    -                html_bodies.append(utf8(body_part))
    -
    -        if js_files:
    -            # Maintain order of JavaScript files given by modules
    -            js = self.render_linked_js(js_files)
    -            sloc = html.rindex(b"")
    -            html = html[:sloc] + utf8(js) + b"\n" + html[sloc:]
    -        if js_embed:
    -            js_bytes = self.render_embed_js(js_embed)
    -            sloc = html.rindex(b"")
    -            html = html[:sloc] + js_bytes + b"\n" + html[sloc:]
    -        if css_files:
    -            css = self.render_linked_css(css_files)
    -            hloc = html.index(b"")
    -            html = html[:hloc] + utf8(css) + b"\n" + html[hloc:]
    -        if css_embed:
    -            css_bytes = self.render_embed_css(css_embed)
    -            hloc = html.index(b"")
    -            html = html[:hloc] + css_bytes + b"\n" + html[hloc:]
    -        if html_heads:
    -            hloc = html.index(b"")
    -            html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:]
    -        if html_bodies:
    -            hloc = html.index(b"")
    -            html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:]
    -        return self.finish(html)
    -
    -    def render_linked_js(self, js_files: Iterable[str]) -> str:
    -        """Default method used to render the final js links for the
    -        rendered webpage.
    -
    -        Override this method in a sub-classed controller to change the output.
    -        """
    -        paths = []
    -        unique_paths = set()  # type: Set[str]
    -
    -        for path in js_files:
    -            if not is_absolute(path):
    -                path = self.static_url(path)
    -            if path not in unique_paths:
    -                paths.append(path)
    -                unique_paths.add(path)
    -
    -        return "".join(
    -            ''
    -            for p in paths
    -        )
    -
    -    def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes:
    -        """Default method used to render the final embedded js for the
    -        rendered webpage.
    -
    -        Override this method in a sub-classed controller to change the output.
    -        """
    -        return (
    -            b'"
    -        )
    -
    -    def render_linked_css(self, css_files: Iterable[str]) -> str:
    -        """Default method used to render the final css links for the
    -        rendered webpage.
    -
    -        Override this method in a sub-classed controller to change the output.
    -        """
    -        paths = []
    -        unique_paths = set()  # type: Set[str]
    -
    -        for path in css_files:
    -            if not is_absolute(path):
    -                path = self.static_url(path)
    -            if path not in unique_paths:
    -                paths.append(path)
    -                unique_paths.add(path)
    -
    -        return "".join(
    -            ''
    -            for p in paths
    -        )
    -
    -    def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes:
    -        """Default method used to render the final embedded css for the
    -        rendered webpage.
    -
    -        Override this method in a sub-classed controller to change the output.
    -        """
    -        return b'"
    -
    -    def render_string(self, template_name: str, **kwargs: Any) -> bytes:
    -        """Generate the given template with the given arguments.
    -
    -        We return the generated byte string (in utf8). To generate and
    -        write a template as a response, use render() above.
    -        """
    -        # If no template_path is specified, use the path of the calling file
    -        template_path = self.get_template_path()
    -        if not template_path:
    -            frame = sys._getframe(0)
    -            web_file = frame.f_code.co_filename
    -            while frame.f_code.co_filename == web_file:
    -                frame = frame.f_back
    -            assert frame.f_code.co_filename is not None
    -            template_path = os.path.dirname(frame.f_code.co_filename)
    -        with RequestHandler._template_loader_lock:
    -            if template_path not in RequestHandler._template_loaders:
    -                loader = self.create_template_loader(template_path)
    -                RequestHandler._template_loaders[template_path] = loader
    -            else:
    -                loader = RequestHandler._template_loaders[template_path]
    -        t = loader.load(template_name)
    -        namespace = self.get_template_namespace()
    -        namespace.update(kwargs)
    -        return t.generate(**namespace)
    -
    -    def get_template_namespace(self) -> Dict[str, Any]:
    -        """Returns a dictionary to be used as the default template namespace.
    -
    -        May be overridden by subclasses to add or modify values.
    -
    -        The results of this method will be combined with additional
    -        defaults in the `tornado.template` module and keyword arguments
    -        to `render` or `render_string`.
    -        """
    -        namespace = dict(
    -            handler=self,
    -            request=self.request,
    -            current_user=self.current_user,
    -            locale=self.locale,
    -            _=self.locale.translate,
    -            pgettext=self.locale.pgettext,
    -            static_url=self.static_url,
    -            xsrf_form_html=self.xsrf_form_html,
    -            reverse_url=self.reverse_url,
    -        )
    -        namespace.update(self.ui)
    -        return namespace
    -
    -    def create_template_loader(self, template_path: str) -> template.BaseLoader:
    -        """Returns a new template loader for the given path.
    -
    -        May be overridden by subclasses.  By default returns a
    -        directory-based loader on the given path, using the
    -        ``autoescape`` and ``template_whitespace`` application
    -        settings.  If a ``template_loader`` application setting is
    -        supplied, uses that instead.
    -        """
    -        settings = self.application.settings
    -        if "template_loader" in settings:
    -            return settings["template_loader"]
    -        kwargs = {}
    -        if "autoescape" in settings:
    -            # autoescape=None means "no escaping", so we have to be sure
    -            # to only pass this kwarg if the user asked for it.
    -            kwargs["autoescape"] = settings["autoescape"]
    -        if "template_whitespace" in settings:
    -            kwargs["whitespace"] = settings["template_whitespace"]
    -        return template.Loader(template_path, **kwargs)
    -
    -    def flush(self, include_footers: bool = False) -> "Future[None]":
    -        """Flushes the current output buffer to the network.
    -
    -        .. versionchanged:: 4.0
    -           Now returns a `.Future` if no callback is given.
    -
    -        .. versionchanged:: 6.0
    -
    -           The ``callback`` argument was removed.
    -        """
    -        assert self.request.connection is not None
    -        chunk = b"".join(self._write_buffer)
    -        self._write_buffer = []
    -        if not self._headers_written:
    -            self._headers_written = True
    -            for transform in self._transforms:
    -                assert chunk is not None
    -                (
    -                    self._status_code,
    -                    self._headers,
    -                    chunk,
    -                ) = transform.transform_first_chunk(
    -                    self._status_code, self._headers, chunk, include_footers
    -                )
    -            # Ignore the chunk and only write the headers for HEAD requests
    -            if self.request.method == "HEAD":
    -                chunk = b""
    -
    -            # Finalize the cookie headers (which have been stored in a side
    -            # object so an outgoing cookie could be overwritten before it
    -            # is sent).
    -            if hasattr(self, "_new_cookie"):
    -                for cookie in self._new_cookie.values():
    -                    self.add_header("Set-Cookie", cookie.OutputString(None))
    -
    -            start_line = httputil.ResponseStartLine("", self._status_code, self._reason)
    -            return self.request.connection.write_headers(
    -                start_line, self._headers, chunk
    -            )
    -        else:
    -            for transform in self._transforms:
    -                chunk = transform.transform_chunk(chunk, include_footers)
    -            # Ignore the chunk and only write the headers for HEAD requests
    -            if self.request.method != "HEAD":
    -                return self.request.connection.write(chunk)
    -            else:
    -                future = Future()  # type: Future[None]
    -                future.set_result(None)
    -                return future
    -
    -    def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]":
    -        """Finishes this response, ending the HTTP request.
    -
    -        Passing a ``chunk`` to ``finish()`` is equivalent to passing that
    -        chunk to ``write()`` and then calling ``finish()`` with no arguments.
    -
    -        Returns a `.Future` which may optionally be awaited to track the sending
    -        of the response to the client. This `.Future` resolves when all the response
    -        data has been sent, and raises an error if the connection is closed before all
    -        data can be sent.
    -
    -        .. versionchanged:: 5.1
    -
    -           Now returns a `.Future` instead of ``None``.
    -        """
    -        if self._finished:
    -            raise RuntimeError("finish() called twice")
    -
    -        if chunk is not None:
    -            self.write(chunk)
    -
    -        # Automatically support ETags and add the Content-Length header if
    -        # we have not flushed any content yet.
    -        if not self._headers_written:
    -            if (
    -                self._status_code == 200
    -                and self.request.method in ("GET", "HEAD")
    -                and "Etag" not in self._headers
    -            ):
    -                self.set_etag_header()
    -                if self.check_etag_header():
    -                    self._write_buffer = []
    -                    self.set_status(304)
    -            if self._status_code in (204, 304) or (100 <= self._status_code < 200):
    -                assert not self._write_buffer, (
    -                    "Cannot send body with %s" % self._status_code
    -                )
    -                self._clear_representation_headers()
    -            elif "Content-Length" not in self._headers:
    -                content_length = sum(len(part) for part in self._write_buffer)
    -                self.set_header("Content-Length", content_length)
    -
    -        assert self.request.connection is not None
    -        # Now that the request is finished, clear the callback we
    -        # set on the HTTPConnection (which would otherwise prevent the
    -        # garbage collection of the RequestHandler when there
    -        # are keepalive connections)
    -        self.request.connection.set_close_callback(None)  # type: ignore
    -
    -        future = self.flush(include_footers=True)
    -        self.request.connection.finish()
    -        self._log()
    -        self._finished = True
    -        self.on_finish()
    -        self._break_cycles()
    -        return future
    -
    -    def detach(self) -> iostream.IOStream:
    -        """Take control of the underlying stream.
    -
    -        Returns the underlying `.IOStream` object and stops all
    -        further HTTP processing. Intended for implementing protocols
    -        like websockets that tunnel over an HTTP handshake.
    -
    -        This method is only supported when HTTP/1.1 is used.
    -
    -        .. versionadded:: 5.1
    -        """
    -        self._finished = True
    -        # TODO: add detach to HTTPConnection?
    -        return self.request.connection.detach()  # type: ignore
    -
    -    def _break_cycles(self) -> None:
    -        # Break up a reference cycle between this handler and the
    -        # _ui_module closures to allow for faster GC on CPython.
    -        self.ui = None  # type: ignore
    -
    -    def send_error(self, status_code: int = 500, **kwargs: Any) -> None:
    -        """Sends the given HTTP error code to the browser.
    -
    -        If `flush()` has already been called, it is not possible to send
    -        an error, so this method will simply terminate the response.
    -        If output has been written but not yet flushed, it will be discarded
    -        and replaced with the error page.
    -
    -        Override `write_error()` to customize the error page that is returned.
    -        Additional keyword arguments are passed through to `write_error`.
    -        """
    -        if self._headers_written:
    -            gen_log.error("Cannot send error response after headers written")
    -            if not self._finished:
    -                # If we get an error between writing headers and finishing,
    -                # we are unlikely to be able to finish due to a
    -                # Content-Length mismatch. Try anyway to release the
    -                # socket.
    -                try:
    -                    self.finish()
    -                except Exception:
    -                    gen_log.error("Failed to flush partial response", exc_info=True)
    -            return
    -        self.clear()
    -
    -        reason = kwargs.get("reason")
    -        if "exc_info" in kwargs:
    -            exception = kwargs["exc_info"][1]
    -            if isinstance(exception, HTTPError) and exception.reason:
    -                reason = exception.reason
    -        self.set_status(status_code, reason=reason)
    -        try:
    -            self.write_error(status_code, **kwargs)
    -        except Exception:
    -            app_log.error("Uncaught exception in write_error", exc_info=True)
    -        if not self._finished:
    -            self.finish()
    -
    -    def write_error(self, status_code: int, **kwargs: Any) -> None:
    -        """Override to implement custom error pages.
    -
    -        ``write_error`` may call `write`, `render`, `set_header`, etc
    -        to produce output as usual.
    -
    -        If this error was caused by an uncaught exception (including
    -        HTTPError), an ``exc_info`` triple will be available as
    -        ``kwargs["exc_info"]``.  Note that this exception may not be
    -        the "current" exception for purposes of methods like
    -        ``sys.exc_info()`` or ``traceback.format_exc``.
    -        """
    -        if self.settings.get("serve_traceback") and "exc_info" in kwargs:
    -            # in debug mode, try to send a traceback
    -            self.set_header("Content-Type", "text/plain")
    -            for line in traceback.format_exception(*kwargs["exc_info"]):
    -                self.write(line)
    -            self.finish()
    -        else:
    -            self.finish(
    -                "%(code)d: %(message)s"
    -                "%(code)d: %(message)s"
    -                % {"code": status_code, "message": self._reason}
    -            )
    -
    -    @property
    -    def locale(self) -> tornado.locale.Locale:
    -        """The locale for the current session.
    -
    -        Determined by either `get_user_locale`, which you can override to
    -        set the locale based on, e.g., a user preference stored in a
    -        database, or `get_browser_locale`, which uses the ``Accept-Language``
    -        header.
    -
    -        .. versionchanged: 4.1
    -           Added a property setter.
    -        """
    -        if not hasattr(self, "_locale"):
    -            loc = self.get_user_locale()
    -            if loc is not None:
    -                self._locale = loc
    -            else:
    -                self._locale = self.get_browser_locale()
    -                assert self._locale
    -        return self._locale
    -
    -    @locale.setter
    -    def locale(self, value: tornado.locale.Locale) -> None:
    -        self._locale = value
    -
    -    def get_user_locale(self) -> Optional[tornado.locale.Locale]:
    -        """Override to determine the locale from the authenticated user.
    -
    -        If None is returned, we fall back to `get_browser_locale()`.
    -
    -        This method should return a `tornado.locale.Locale` object,
    -        most likely obtained via a call like ``tornado.locale.get("en")``
    -        """
    -        return None
    -
    -    def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale:
    -        """Determines the user's locale from ``Accept-Language`` header.
    -
    -        See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    -        """
    -        if "Accept-Language" in self.request.headers:
    -            languages = self.request.headers["Accept-Language"].split(",")
    -            locales = []
    -            for language in languages:
    -                parts = language.strip().split(";")
    -                if len(parts) > 1 and parts[1].startswith("q="):
    -                    try:
    -                        score = float(parts[1][2:])
    -                    except (ValueError, TypeError):
    -                        score = 0.0
    -                else:
    -                    score = 1.0
    -                locales.append((parts[0], score))
    -            if locales:
    -                locales.sort(key=lambda pair: pair[1], reverse=True)
    -                codes = [loc[0] for loc in locales]
    -                return locale.get(*codes)
    -        return locale.get(default)
    -
    -    @property
    -    def current_user(self) -> Any:
    -        """The authenticated user for this request.
    -
    -        This is set in one of two ways:
    -
    -        * A subclass may override `get_current_user()`, which will be called
    -          automatically the first time ``self.current_user`` is accessed.
    -          `get_current_user()` will only be called once per request,
    -          and is cached for future access::
    -
    -              def get_current_user(self):
    -                  user_cookie = self.get_secure_cookie("user")
    -                  if user_cookie:
    -                      return json.loads(user_cookie)
    -                  return None
    -
    -        * It may be set as a normal variable, typically from an overridden
    -          `prepare()`::
    -
    -              @gen.coroutine
    -              def prepare(self):
    -                  user_id_cookie = self.get_secure_cookie("user_id")
    -                  if user_id_cookie:
    -                      self.current_user = yield load_user(user_id_cookie)
    -
    -        Note that `prepare()` may be a coroutine while `get_current_user()`
    -        may not, so the latter form is necessary if loading the user requires
    -        asynchronous operations.
    -
    -        The user object may be any type of the application's choosing.
    -        """
    -        if not hasattr(self, "_current_user"):
    -            self._current_user = self.get_current_user()
    -        return self._current_user
    -
    -    @current_user.setter
    -    def current_user(self, value: Any) -> None:
    -        self._current_user = value
    -
    -    def get_current_user(self) -> Any:
    -        """Override to determine the current user from, e.g., a cookie.
    -
    -        This method may not be a coroutine.
    -        """
    -        return None
    -
    -    def get_login_url(self) -> str:
    -        """Override to customize the login URL based on the request.
    -
    -        By default, we use the ``login_url`` application setting.
    -        """
    -        self.require_setting("login_url", "@tornado.web.authenticated")
    -        return self.application.settings["login_url"]
    -
    -    def get_template_path(self) -> Optional[str]:
    -        """Override to customize template path for each handler.
    -
    -        By default, we use the ``template_path`` application setting.
    -        Return None to load templates relative to the calling file.
    -        """
    -        return self.application.settings.get("template_path")
    -
    -    @property
    -    def xsrf_token(self) -> bytes:
    -        """The XSRF-prevention token for the current user/session.
    -
    -        To prevent cross-site request forgery, we set an '_xsrf' cookie
    -        and include the same '_xsrf' value as an argument with all POST
    -        requests. If the two do not match, we reject the form submission
    -        as a potential forgery.
    -
    -        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
    -
    -        This property is of type `bytes`, but it contains only ASCII
    -        characters. If a character string is required, there is no
    -        need to base64-encode it; just decode the byte string as
    -        UTF-8.
    -
    -        .. versionchanged:: 3.2.2
    -           The xsrf token will now be have a random mask applied in every
    -           request, which makes it safe to include the token in pages
    -           that are compressed.  See http://breachattack.com for more
    -           information on the issue fixed by this change.  Old (version 1)
    -           cookies will be converted to version 2 when this method is called
    -           unless the ``xsrf_cookie_version`` `Application` setting is
    -           set to 1.
    -
    -        .. versionchanged:: 4.3
    -           The ``xsrf_cookie_kwargs`` `Application` setting may be
    -           used to supply additional cookie options (which will be
    -           passed directly to `set_cookie`). For example,
    -           ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)``
    -           will set the ``secure`` and ``httponly`` flags on the
    -           ``_xsrf`` cookie.
    -        """
    -        if not hasattr(self, "_xsrf_token"):
    -            version, token, timestamp = self._get_raw_xsrf_token()
    -            output_version = self.settings.get("xsrf_cookie_version", 2)
    -            cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {})
    -            if output_version == 1:
    -                self._xsrf_token = binascii.b2a_hex(token)
    -            elif output_version == 2:
    -                mask = os.urandom(4)
    -                self._xsrf_token = b"|".join(
    -                    [
    -                        b"2",
    -                        binascii.b2a_hex(mask),
    -                        binascii.b2a_hex(_websocket_mask(mask, token)),
    -                        utf8(str(int(timestamp))),
    -                    ]
    -                )
    -            else:
    -                raise ValueError("unknown xsrf cookie version %d", output_version)
    -            if version is None:
    -                if self.current_user and "expires_days" not in cookie_kwargs:
    -                    cookie_kwargs["expires_days"] = 30
    -                self.set_cookie("_xsrf", self._xsrf_token, **cookie_kwargs)
    -        return self._xsrf_token
    -
    -    def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
    -        """Read or generate the xsrf token in its raw form.
    -
    -        The raw_xsrf_token is a tuple containing:
    -
    -        * version: the version of the cookie from which this token was read,
    -          or None if we generated a new token in this request.
    -        * token: the raw token data; random (non-ascii) bytes.
    -        * timestamp: the time this token was generated (will not be accurate
    -          for version 1 cookies)
    -        """
    -        if not hasattr(self, "_raw_xsrf_token"):
    -            cookie = self.get_cookie("_xsrf")
    -            if cookie:
    -                version, token, timestamp = self._decode_xsrf_token(cookie)
    -            else:
    -                version, token, timestamp = None, None, None
    -            if token is None:
    -                version = None
    -                token = os.urandom(16)
    -                timestamp = time.time()
    -            assert token is not None
    -            assert timestamp is not None
    -            self._raw_xsrf_token = (version, token, timestamp)
    -        return self._raw_xsrf_token
    -
    -    def _decode_xsrf_token(
    -        self, cookie: str
    -    ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]:
    -        """Convert a cookie string into a the tuple form returned by
    -        _get_raw_xsrf_token.
    -        """
    -
    -        try:
    -            m = _signed_value_version_re.match(utf8(cookie))
    -
    -            if m:
    -                version = int(m.group(1))
    -                if version == 2:
    -                    _, mask_str, masked_token, timestamp_str = cookie.split("|")
    -
    -                    mask = binascii.a2b_hex(utf8(mask_str))
    -                    token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token)))
    -                    timestamp = int(timestamp_str)
    -                    return version, token, timestamp
    -                else:
    -                    # Treat unknown versions as not present instead of failing.
    -                    raise Exception("Unknown xsrf cookie version")
    -            else:
    -                version = 1
    -                try:
    -                    token = binascii.a2b_hex(utf8(cookie))
    -                except (binascii.Error, TypeError):
    -                    token = utf8(cookie)
    -                # We don't have a usable timestamp in older versions.
    -                timestamp = int(time.time())
    -                return (version, token, timestamp)
    -        except Exception:
    -            # Catch exceptions and return nothing instead of failing.
    -            gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True)
    -            return None, None, None
    -
    -    def check_xsrf_cookie(self) -> None:
    -        """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
    -
    -        To prevent cross-site request forgery, we set an ``_xsrf``
    -        cookie and include the same value as a non-cookie
    -        field with all ``POST`` requests. If the two do not match, we
    -        reject the form submission as a potential forgery.
    -
    -        The ``_xsrf`` value may be set as either a form field named ``_xsrf``
    -        or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``
    -        (the latter is accepted for compatibility with Django).
    -
    -        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
    -
    -        .. versionchanged:: 3.2.2
    -           Added support for cookie version 2.  Both versions 1 and 2 are
    -           supported.
    -        """
    -        # Prior to release 1.1.1, this check was ignored if the HTTP header
    -        # ``X-Requested-With: XMLHTTPRequest`` was present.  This exception
    -        # has been shown to be insecure and has been removed.  For more
    -        # information please see
    -        # http://www.djangoproject.com/weblog/2011/feb/08/security/
    -        # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
    -        token = (
    -            self.get_argument("_xsrf", None)
    -            or self.request.headers.get("X-Xsrftoken")
    -            or self.request.headers.get("X-Csrftoken")
    -        )
    -        if not token:
    -            raise HTTPError(403, "'_xsrf' argument missing from POST")
    -        _, token, _ = self._decode_xsrf_token(token)
    -        _, expected_token, _ = self._get_raw_xsrf_token()
    -        if not token:
    -            raise HTTPError(403, "'_xsrf' argument has invalid format")
    -        if not hmac.compare_digest(utf8(token), utf8(expected_token)):
    -            raise HTTPError(403, "XSRF cookie does not match POST argument")
    -
    -    def xsrf_form_html(self) -> str:
    -        """An HTML ```` element to be included with all POST forms.
    -
    -        It defines the ``_xsrf`` input value, which we check on all POST
    -        requests to prevent cross-site request forgery. If you have set
    -        the ``xsrf_cookies`` application setting, you must include this
    -        HTML within all of your HTML forms.
    -
    -        In a template, this method should be called with ``{% module
    -        xsrf_form_html() %}``
    -
    -        See `check_xsrf_cookie()` above for more information.
    -        """
    -        return (
    -            ''
    -        )
    -
    -    def static_url(
    -        self, path: str, include_host: Optional[bool] = None, **kwargs: Any
    -    ) -> str:
    -        """Returns a static URL for the given relative static file path.
    -
    -        This method requires you set the ``static_path`` setting in your
    -        application (which specifies the root directory of your static
    -        files).
    -
    -        This method returns a versioned url (by default appending
    -        ``?v=``), which allows the static files to be
    -        cached indefinitely.  This can be disabled by passing
    -        ``include_version=False`` (in the default implementation;
    -        other static file implementations are not required to support
    -        this, but they may support other options).
    -
    -        By default this method returns URLs relative to the current
    -        host, but if ``include_host`` is true the URL returned will be
    -        absolute.  If this handler has an ``include_host`` attribute,
    -        that value will be used as the default for all `static_url`
    -        calls that do not pass ``include_host`` as a keyword argument.
    -
    -        """
    -        self.require_setting("static_path", "static_url")
    -        get_url = self.settings.get(
    -            "static_handler_class", StaticFileHandler
    -        ).make_static_url
    -
    -        if include_host is None:
    -            include_host = getattr(self, "include_host", False)
    -
    -        if include_host:
    -            base = self.request.protocol + "://" + self.request.host
    -        else:
    -            base = ""
    -
    -        return base + get_url(self.settings, path, **kwargs)
    -
    -    def require_setting(self, name: str, feature: str = "this feature") -> None:
    -        """Raises an exception if the given app setting is not defined."""
    -        if not self.application.settings.get(name):
    -            raise Exception(
    -                "You must define the '%s' setting in your "
    -                "application to use %s" % (name, feature)
    -            )
    -
    -    def reverse_url(self, name: str, *args: Any) -> str:
    -        """Alias for `Application.reverse_url`."""
    -        return self.application.reverse_url(name, *args)
    -
    -    def compute_etag(self) -> Optional[str]:
    -        """Computes the etag header to be used for this request.
    -
    -        By default uses a hash of the content written so far.
    -
    -        May be overridden to provide custom etag implementations,
    -        or may return None to disable tornado's default etag support.
    -        """
    -        hasher = hashlib.sha1()
    -        for part in self._write_buffer:
    -            hasher.update(part)
    -        return '"%s"' % hasher.hexdigest()
    -
    -    def set_etag_header(self) -> None:
    -        """Sets the response's Etag header using ``self.compute_etag()``.
    -
    -        Note: no header will be set if ``compute_etag()`` returns ``None``.
    -
    -        This method is called automatically when the request is finished.
    -        """
    -        etag = self.compute_etag()
    -        if etag is not None:
    -            self.set_header("Etag", etag)
    -
    -    def check_etag_header(self) -> bool:
    -        """Checks the ``Etag`` header against requests's ``If-None-Match``.
    -
    -        Returns ``True`` if the request's Etag matches and a 304 should be
    -        returned. For example::
    -
    -            self.set_etag_header()
    -            if self.check_etag_header():
    -                self.set_status(304)
    -                return
    -
    -        This method is called automatically when the request is finished,
    -        but may be called earlier for applications that override
    -        `compute_etag` and want to do an early check for ``If-None-Match``
    -        before completing the request.  The ``Etag`` header should be set
    -        (perhaps with `set_etag_header`) before calling this method.
    -        """
    -        computed_etag = utf8(self._headers.get("Etag", ""))
    -        # Find all weak and strong etag values from If-None-Match header
    -        # because RFC 7232 allows multiple etag values in a single header.
    -        etags = re.findall(
    -            br'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", ""))
    -        )
    -        if not computed_etag or not etags:
    -            return False
    -
    -        match = False
    -        if etags[0] == b"*":
    -            match = True
    -        else:
    -            # Use a weak comparison when comparing entity-tags.
    -            def val(x: bytes) -> bytes:
    -                return x[2:] if x.startswith(b"W/") else x
    -
    -            for etag in etags:
    -                if val(etag) == val(computed_etag):
    -                    match = True
    -                    break
    -        return match
    -
    -    async def _execute(
    -        self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes
    -    ) -> None:
    -        """Executes this request with the given output transforms."""
    -        self._transforms = transforms
    -        try:
    -            if self.request.method not in self.SUPPORTED_METHODS:
    -                raise HTTPError(405)
    -            self.path_args = [self.decode_argument(arg) for arg in args]
    -            self.path_kwargs = dict(
    -                (k, self.decode_argument(v, name=k)) for (k, v) in kwargs.items()
    -            )
    -            # If XSRF cookies are turned on, reject form submissions without
    -            # the proper cookie
    -            if self.request.method not in (
    -                "GET",
    -                "HEAD",
    -                "OPTIONS",
    -            ) and self.application.settings.get("xsrf_cookies"):
    -                self.check_xsrf_cookie()
    -
    -            result = self.prepare()
    -            if result is not None:
    -                result = await result
    -            if self._prepared_future is not None:
    -                # Tell the Application we've finished with prepare()
    -                # and are ready for the body to arrive.
    -                future_set_result_unless_cancelled(self._prepared_future, None)
    -            if self._finished:
    -                return
    -
    -            if _has_stream_request_body(self.__class__):
    -                # In streaming mode request.body is a Future that signals
    -                # the body has been completely received.  The Future has no
    -                # result; the data has been passed to self.data_received
    -                # instead.
    -                try:
    -                    await self.request._body_future
    -                except iostream.StreamClosedError:
    -                    return
    -
    -            method = getattr(self, self.request.method.lower())
    -            result = method(*self.path_args, **self.path_kwargs)
    -            if result is not None:
    -                result = await result
    -            if self._auto_finish and not self._finished:
    -                self.finish()
    -        except Exception as e:
    -            try:
    -                self._handle_request_exception(e)
    -            except Exception:
    -                app_log.error("Exception in exception handler", exc_info=True)
    -            finally:
    -                # Unset result to avoid circular references
    -                result = None
    -            if self._prepared_future is not None and not self._prepared_future.done():
    -                # In case we failed before setting _prepared_future, do it
    -                # now (to unblock the HTTP server).  Note that this is not
    -                # in a finally block to avoid GC issues prior to Python 3.4.
    -                self._prepared_future.set_result(None)
    -
    -    def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
    -        """Implement this method to handle streamed request data.
    -
    -        Requires the `.stream_request_body` decorator.
    -
    -        May be a coroutine for flow control.
    -        """
    -        raise NotImplementedError()
    -
    -    def _log(self) -> None:
    -        """Logs the current request.
    -
    -        Sort of deprecated since this functionality was moved to the
    -        Application, but left in place for the benefit of existing apps
    -        that have overridden this method.
    -        """
    -        self.application.log_request(self)
    -
    -    def _request_summary(self) -> str:
    -        return "%s %s (%s)" % (
    -            self.request.method,
    -            self.request.uri,
    -            self.request.remote_ip,
    -        )
    -
    -    def _handle_request_exception(self, e: BaseException) -> None:
    -        if isinstance(e, Finish):
    -            # Not an error; just finish the request without logging.
    -            if not self._finished:
    -                self.finish(*e.args)
    -            return
    -        try:
    -            self.log_exception(*sys.exc_info())
    -        except Exception:
    -            # An error here should still get a best-effort send_error()
    -            # to avoid leaking the connection.
    -            app_log.error("Error in exception logger", exc_info=True)
    -        if self._finished:
    -            # Extra errors after the request has been finished should
    -            # be logged, but there is no reason to continue to try and
    -            # send a response.
    -            return
    -        if isinstance(e, HTTPError):
    -            self.send_error(e.status_code, exc_info=sys.exc_info())
    -        else:
    -            self.send_error(500, exc_info=sys.exc_info())
    -
    -    def log_exception(
    -        self,
    -        typ: "Optional[Type[BaseException]]",
    -        value: Optional[BaseException],
    -        tb: Optional[TracebackType],
    -    ) -> None:
    -        """Override to customize logging of uncaught exceptions.
    -
    -        By default logs instances of `HTTPError` as warnings without
    -        stack traces (on the ``tornado.general`` logger), and all
    -        other exceptions as errors with stack traces (on the
    -        ``tornado.application`` logger).
    -
    -        .. versionadded:: 3.1
    -        """
    -        if isinstance(value, HTTPError):
    -            if value.log_message:
    -                format = "%d %s: " + value.log_message
    -                args = [value.status_code, self._request_summary()] + list(value.args)
    -                gen_log.warning(format, *args)
    -        else:
    -            app_log.error(
    -                "Uncaught exception %s\n%r",
    -                self._request_summary(),
    -                self.request,
    -                exc_info=(typ, value, tb),  # type: ignore
    -            )
    -
    -    def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]:
    -        def render(*args, **kwargs) -> str:  # type: ignore
    -            if not hasattr(self, "_active_modules"):
    -                self._active_modules = {}  # type: Dict[str, UIModule]
    -            if name not in self._active_modules:
    -                self._active_modules[name] = module(self)
    -            rendered = self._active_modules[name].render(*args, **kwargs)
    -            return rendered
    -
    -        return render
    -
    -    def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]:
    -        return lambda *args, **kwargs: method(self, *args, **kwargs)
    -
    -    def _clear_representation_headers(self) -> None:
    -        # 304 responses should not contain representation metadata
    -        # headers (defined in
    -        # https://tools.ietf.org/html/rfc7231#section-3.1)
    -        # not explicitly allowed by
    -        # https://tools.ietf.org/html/rfc7232#section-4.1
    -        headers = ["Content-Encoding", "Content-Language", "Content-Type"]
    -        for h in headers:
    -            self.clear_header(h)
    -
    -
    -def stream_request_body(cls: Type[RequestHandler]) -> Type[RequestHandler]:
    -    """Apply to `RequestHandler` subclasses to enable streaming body support.
    -
    -    This decorator implies the following changes:
    -
    -    * `.HTTPServerRequest.body` is undefined, and body arguments will not
    -      be included in `RequestHandler.get_argument`.
    -    * `RequestHandler.prepare` is called when the request headers have been
    -      read instead of after the entire body has been read.
    -    * The subclass must define a method ``data_received(self, data):``, which
    -      will be called zero or more times as data is available.  Note that
    -      if the request has an empty body, ``data_received`` may not be called.
    -    * ``prepare`` and ``data_received`` may return Futures (such as via
    -      ``@gen.coroutine``, in which case the next method will not be called
    -      until those futures have completed.
    -    * The regular HTTP method (``post``, ``put``, etc) will be called after
    -      the entire body has been read.
    -
    -    See the `file receiver demo `_
    -    for example usage.
    -    """  # noqa: E501
    -    if not issubclass(cls, RequestHandler):
    -        raise TypeError("expected subclass of RequestHandler, got %r", cls)
    -    cls._stream_request_body = True
    -    return cls
    -
    -
    -def _has_stream_request_body(cls: Type[RequestHandler]) -> bool:
    -    if not issubclass(cls, RequestHandler):
    -        raise TypeError("expected subclass of RequestHandler, got %r", cls)
    -    return cls._stream_request_body
    -
    -
    -def removeslash(
    -    method: Callable[..., Optional[Awaitable[None]]]
    -) -> Callable[..., Optional[Awaitable[None]]]:
    -    """Use this decorator to remove trailing slashes from the request path.
    -
    -    For example, a request to ``/foo/`` would redirect to ``/foo`` with this
    -    decorator. Your request handler mapping should use a regular expression
    -    like ``r'/foo/*'`` in conjunction with using the decorator.
    -    """
    -
    -    @functools.wraps(method)
    -    def wrapper(  # type: ignore
    -        self: RequestHandler, *args, **kwargs
    -    ) -> Optional[Awaitable[None]]:
    -        if self.request.path.endswith("/"):
    -            if self.request.method in ("GET", "HEAD"):
    -                uri = self.request.path.rstrip("/")
    -                if uri:  # don't try to redirect '/' to ''
    -                    if self.request.query:
    -                        uri += "?" + self.request.query
    -                    self.redirect(uri, permanent=True)
    -                    return None
    -            else:
    -                raise HTTPError(404)
    -        return method(self, *args, **kwargs)
    -
    -    return wrapper
    -
    -
    -def addslash(
    -    method: Callable[..., Optional[Awaitable[None]]]
    -) -> Callable[..., Optional[Awaitable[None]]]:
    -    """Use this decorator to add a missing trailing slash to the request path.
    -
    -    For example, a request to ``/foo`` would redirect to ``/foo/`` with this
    -    decorator. Your request handler mapping should use a regular expression
    -    like ``r'/foo/?'`` in conjunction with using the decorator.
    -    """
    -
    -    @functools.wraps(method)
    -    def wrapper(  # type: ignore
    -        self: RequestHandler, *args, **kwargs
    -    ) -> Optional[Awaitable[None]]:
    -        if not self.request.path.endswith("/"):
    -            if self.request.method in ("GET", "HEAD"):
    -                uri = self.request.path + "/"
    -                if self.request.query:
    -                    uri += "?" + self.request.query
    -                self.redirect(uri, permanent=True)
    -                return None
    -            raise HTTPError(404)
    -        return method(self, *args, **kwargs)
    -
    -    return wrapper
    -
    -
    -class _ApplicationRouter(ReversibleRuleRouter):
    -    """Routing implementation used internally by `Application`.
    -
    -    Provides a binding between `Application` and `RequestHandler`.
    -    This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:
    -        * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and
    -        * it allows to use a list/tuple of rules as `~.routing.Rule` target.
    -        ``process_rule`` implementation will substitute this list with an appropriate
    -        `_ApplicationRouter` instance.
    -    """
    -
    -    def __init__(
    -        self, application: "Application", rules: Optional[_RuleList] = None
    -    ) -> None:
    -        assert isinstance(application, Application)
    -        self.application = application
    -        super().__init__(rules)
    -
    -    def process_rule(self, rule: Rule) -> Rule:
    -        rule = super().process_rule(rule)
    -
    -        if isinstance(rule.target, (list, tuple)):
    -            rule.target = _ApplicationRouter(
    -                self.application, rule.target  # type: ignore
    -            )
    -
    -        return rule
    -
    -    def get_target_delegate(
    -        self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any
    -    ) -> Optional[httputil.HTTPMessageDelegate]:
    -        if isclass(target) and issubclass(target, RequestHandler):
    -            return self.application.get_handler_delegate(
    -                request, target, **target_params
    -            )
    -
    -        return super().get_target_delegate(target, request, **target_params)
    -
    -
    -class Application(ReversibleRouter):
    -    r"""A collection of request handlers that make up a web application.
    -
    -    Instances of this class are callable and can be passed directly to
    -    HTTPServer to serve the application::
    -
    -        application = web.Application([
    -            (r"/", MainPageHandler),
    -        ])
    -        http_server = httpserver.HTTPServer(application)
    -        http_server.listen(8080)
    -        ioloop.IOLoop.current().start()
    -
    -    The constructor for this class takes in a list of `~.routing.Rule`
    -    objects or tuples of values corresponding to the arguments of
    -    `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``,
    -    the values in square brackets being optional. The default matcher is
    -    `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used
    -    instead of ``(PathMatches(regexp), target)``.
    -
    -    A common routing target is a `RequestHandler` subclass, but you can also
    -    use lists of rules as a target, which create a nested routing configuration::
    -
    -        application = web.Application([
    -            (HostMatches("example.com"), [
    -                (r"/", MainPageHandler),
    -                (r"/feed", FeedHandler),
    -            ]),
    -        ])
    -
    -    In addition to this you can use nested `~.routing.Router` instances,
    -    `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets
    -    (see `~.routing` module docs for more information).
    -
    -    When we receive requests, we iterate over the list in order and
    -    instantiate an instance of the first request class whose regexp
    -    matches the request path. The request class can be specified as
    -    either a class object or a (fully-qualified) name.
    -
    -    A dictionary may be passed as the third element (``target_kwargs``)
    -    of the tuple, which will be used as keyword arguments to the handler's
    -    constructor and `~RequestHandler.initialize` method. This pattern
    -    is used for the `StaticFileHandler` in this example (note that a
    -    `StaticFileHandler` can be installed automatically with the
    -    static_path setting described below)::
    -
    -        application = web.Application([
    -            (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
    -        ])
    -
    -    We support virtual hosts with the `add_handlers` method, which takes in
    -    a host regular expression as the first argument::
    -
    -        application.add_handlers(r"www\.myhost\.com", [
    -            (r"/article/([0-9]+)", ArticleHandler),
    -        ])
    -
    -    If there's no match for the current request's host, then ``default_host``
    -    parameter value is matched against host regular expressions.
    -
    -
    -    .. warning::
    -
    -       Applications that do not use TLS may be vulnerable to :ref:`DNS
    -       rebinding ` attacks. This attack is especially
    -       relevant to applications that only listen on ``127.0.0.1`` or
    -       other private networks. Appropriate host patterns must be used
    -       (instead of the default of ``r'.*'``) to prevent this risk. The
    -       ``default_host`` argument must not be used in applications that
    -       may be vulnerable to DNS rebinding.
    -
    -    You can serve static files by sending the ``static_path`` setting
    -    as a keyword argument. We will serve those files from the
    -    ``/static/`` URI (this is configurable with the
    -    ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
    -    and ``/robots.txt`` from the same directory.  A custom subclass of
    -    `StaticFileHandler` can be specified with the
    -    ``static_handler_class`` setting.
    -
    -    .. versionchanged:: 4.5
    -       Integration with the new `tornado.routing` module.
    -
    -    """
    -
    -    def __init__(
    -        self,
    -        handlers: Optional[_RuleList] = None,
    -        default_host: Optional[str] = None,
    -        transforms: Optional[List[Type["OutputTransform"]]] = None,
    -        **settings: Any
    -    ) -> None:
    -        if transforms is None:
    -            self.transforms = []  # type: List[Type[OutputTransform]]
    -            if settings.get("compress_response") or settings.get("gzip"):
    -                self.transforms.append(GZipContentEncoding)
    -        else:
    -            self.transforms = transforms
    -        self.default_host = default_host
    -        self.settings = settings
    -        self.ui_modules = {
    -            "linkify": _linkify,
    -            "xsrf_form_html": _xsrf_form_html,
    -            "Template": TemplateModule,
    -        }
    -        self.ui_methods = {}  # type: Dict[str, Callable[..., str]]
    -        self._load_ui_modules(settings.get("ui_modules", {}))
    -        self._load_ui_methods(settings.get("ui_methods", {}))
    -        if self.settings.get("static_path"):
    -            path = self.settings["static_path"]
    -            handlers = list(handlers or [])
    -            static_url_prefix = settings.get("static_url_prefix", "/static/")
    -            static_handler_class = settings.get(
    -                "static_handler_class", StaticFileHandler
    -            )
    -            static_handler_args = settings.get("static_handler_args", {})
    -            static_handler_args["path"] = path
    -            for pattern in [
    -                re.escape(static_url_prefix) + r"(.*)",
    -                r"/(favicon\.ico)",
    -                r"/(robots\.txt)",
    -            ]:
    -                handlers.insert(0, (pattern, static_handler_class, static_handler_args))
    -
    -        if self.settings.get("debug"):
    -            self.settings.setdefault("autoreload", True)
    -            self.settings.setdefault("compiled_template_cache", False)
    -            self.settings.setdefault("static_hash_cache", False)
    -            self.settings.setdefault("serve_traceback", True)
    -
    -        self.wildcard_router = _ApplicationRouter(self, handlers)
    -        self.default_router = _ApplicationRouter(
    -            self, [Rule(AnyMatches(), self.wildcard_router)]
    -        )
    -
    -        # Automatically reload modified modules
    -        if self.settings.get("autoreload"):
    -            from tornado import autoreload
    -
    -            autoreload.start()
    -
    -    def listen(self, port: int, address: str = "", **kwargs: Any) -> HTTPServer:
    -        """Starts an HTTP server for this application on the given port.
    -
    -        This is a convenience alias for creating an `.HTTPServer`
    -        object and calling its listen method.  Keyword arguments not
    -        supported by `HTTPServer.listen <.TCPServer.listen>` are passed to the
    -        `.HTTPServer` constructor.  For advanced uses
    -        (e.g. multi-process mode), do not use this method; create an
    -        `.HTTPServer` and call its
    -        `.TCPServer.bind`/`.TCPServer.start` methods directly.
    -
    -        Note that after calling this method you still need to call
    -        ``IOLoop.current().start()`` to start the server.
    -
    -        Returns the `.HTTPServer` object.
    -
    -        .. versionchanged:: 4.3
    -           Now returns the `.HTTPServer` object.
    -        """
    -        server = HTTPServer(self, **kwargs)
    -        server.listen(port, address)
    -        return server
    -
    -    def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None:
    -        """Appends the given handlers to our handler list.
    -
    -        Host patterns are processed sequentially in the order they were
    -        added. All matching patterns will be considered.
    -        """
    -        host_matcher = HostMatches(host_pattern)
    -        rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers))
    -
    -        self.default_router.rules.insert(-1, rule)
    -
    -        if self.default_host is not None:
    -            self.wildcard_router.add_rules(
    -                [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)]
    -            )
    -
    -    def add_transform(self, transform_class: Type["OutputTransform"]) -> None:
    -        self.transforms.append(transform_class)
    -
    -    def _load_ui_methods(self, methods: Any) -> None:
    -        if isinstance(methods, types.ModuleType):
    -            self._load_ui_methods(dict((n, getattr(methods, n)) for n in dir(methods)))
    -        elif isinstance(methods, list):
    -            for m in methods:
    -                self._load_ui_methods(m)
    -        else:
    -            for name, fn in methods.items():
    -                if (
    -                    not name.startswith("_")
    -                    and hasattr(fn, "__call__")
    -                    and name[0].lower() == name[0]
    -                ):
    -                    self.ui_methods[name] = fn
    -
    -    def _load_ui_modules(self, modules: Any) -> None:
    -        if isinstance(modules, types.ModuleType):
    -            self._load_ui_modules(dict((n, getattr(modules, n)) for n in dir(modules)))
    -        elif isinstance(modules, list):
    -            for m in modules:
    -                self._load_ui_modules(m)
    -        else:
    -            assert isinstance(modules, dict)
    -            for name, cls in modules.items():
    -                try:
    -                    if issubclass(cls, UIModule):
    -                        self.ui_modules[name] = cls
    -                except TypeError:
    -                    pass
    -
    -    def __call__(
    -        self, request: httputil.HTTPServerRequest
    -    ) -> Optional[Awaitable[None]]:
    -        # Legacy HTTPServer interface
    -        dispatcher = self.find_handler(request)
    -        return dispatcher.execute()
    -
    -    def find_handler(
    -        self, request: httputil.HTTPServerRequest, **kwargs: Any
    -    ) -> "_HandlerDelegate":
    -        route = self.default_router.find_handler(request)
    -        if route is not None:
    -            return cast("_HandlerDelegate", route)
    -
    -        if self.settings.get("default_handler_class"):
    -            return self.get_handler_delegate(
    -                request,
    -                self.settings["default_handler_class"],
    -                self.settings.get("default_handler_args", {}),
    -            )
    -
    -        return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404})
    -
    -    def get_handler_delegate(
    -        self,
    -        request: httputil.HTTPServerRequest,
    -        target_class: Type[RequestHandler],
    -        target_kwargs: Optional[Dict[str, Any]] = None,
    -        path_args: Optional[List[bytes]] = None,
    -        path_kwargs: Optional[Dict[str, bytes]] = None,
    -    ) -> "_HandlerDelegate":
    -        """Returns `~.httputil.HTTPMessageDelegate` that can serve a request
    -        for application and `RequestHandler` subclass.
    -
    -        :arg httputil.HTTPServerRequest request: current HTTP request.
    -        :arg RequestHandler target_class: a `RequestHandler` class.
    -        :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.
    -        :arg list path_args: positional arguments for ``target_class`` HTTP method that
    -            will be executed while handling a request (``get``, ``post`` or any other).
    -        :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.
    -        """
    -        return _HandlerDelegate(
    -            self, request, target_class, target_kwargs, path_args, path_kwargs
    -        )
    -
    -    def reverse_url(self, name: str, *args: Any) -> str:
    -        """Returns a URL path for handler named ``name``
    -
    -        The handler must be added to the application as a named `URLSpec`.
    -
    -        Args will be substituted for capturing groups in the `URLSpec` regex.
    -        They will be converted to strings if necessary, encoded as utf8,
    -        and url-escaped.
    -        """
    -        reversed_url = self.default_router.reverse_url(name, *args)
    -        if reversed_url is not None:
    -            return reversed_url
    -
    -        raise KeyError("%s not found in named urls" % name)
    -
    -    def log_request(self, handler: RequestHandler) -> None:
    -        """Writes a completed HTTP request to the logs.
    -
    -        By default writes to the python root logger.  To change
    -        this behavior either subclass Application and override this method,
    -        or pass a function in the application settings dictionary as
    -        ``log_function``.
    -        """
    -        if "log_function" in self.settings:
    -            self.settings["log_function"](handler)
    -            return
    -        if handler.get_status() < 400:
    -            log_method = access_log.info
    -        elif handler.get_status() < 500:
    -            log_method = access_log.warning
    -        else:
    -            log_method = access_log.error
    -        request_time = 1000.0 * handler.request.request_time()
    -        log_method(
    -            "%d %s %.2fms",
    -            handler.get_status(),
    -            handler._request_summary(),
    -            request_time,
    -        )
    -
    -
    -class _HandlerDelegate(httputil.HTTPMessageDelegate):
    -    def __init__(
    -        self,
    -        application: Application,
    -        request: httputil.HTTPServerRequest,
    -        handler_class: Type[RequestHandler],
    -        handler_kwargs: Optional[Dict[str, Any]],
    -        path_args: Optional[List[bytes]],
    -        path_kwargs: Optional[Dict[str, bytes]],
    -    ) -> None:
    -        self.application = application
    -        self.connection = request.connection
    -        self.request = request
    -        self.handler_class = handler_class
    -        self.handler_kwargs = handler_kwargs or {}
    -        self.path_args = path_args or []
    -        self.path_kwargs = path_kwargs or {}
    -        self.chunks = []  # type: List[bytes]
    -        self.stream_request_body = _has_stream_request_body(self.handler_class)
    -
    -    def headers_received(
    -        self,
    -        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
    -        headers: httputil.HTTPHeaders,
    -    ) -> Optional[Awaitable[None]]:
    -        if self.stream_request_body:
    -            self.request._body_future = Future()
    -            return self.execute()
    -        return None
    -
    -    def data_received(self, data: bytes) -> Optional[Awaitable[None]]:
    -        if self.stream_request_body:
    -            return self.handler.data_received(data)
    -        else:
    -            self.chunks.append(data)
    -            return None
    -
    -    def finish(self) -> None:
    -        if self.stream_request_body:
    -            future_set_result_unless_cancelled(self.request._body_future, None)
    -        else:
    -            self.request.body = b"".join(self.chunks)
    -            self.request._parse_body()
    -            self.execute()
    -
    -    def on_connection_close(self) -> None:
    -        if self.stream_request_body:
    -            self.handler.on_connection_close()
    -        else:
    -            self.chunks = None  # type: ignore
    -
    -    def execute(self) -> Optional[Awaitable[None]]:
    -        # If template cache is disabled (usually in the debug mode),
    -        # re-compile templates and reload static files on every
    -        # request so you don't need to restart to see changes
    -        if not self.application.settings.get("compiled_template_cache", True):
    -            with RequestHandler._template_loader_lock:
    -                for loader in RequestHandler._template_loaders.values():
    -                    loader.reset()
    -        if not self.application.settings.get("static_hash_cache", True):
    -            StaticFileHandler.reset()
    -
    -        self.handler = self.handler_class(
    -            self.application, self.request, **self.handler_kwargs
    -        )
    -        transforms = [t(self.request) for t in self.application.transforms]
    -
    -        if self.stream_request_body:
    -            self.handler._prepared_future = Future()
    -        # Note that if an exception escapes handler._execute it will be
    -        # trapped in the Future it returns (which we are ignoring here,
    -        # leaving it to be logged when the Future is GC'd).
    -        # However, that shouldn't happen because _execute has a blanket
    -        # except handler, and we cannot easily access the IOLoop here to
    -        # call add_future (because of the requirement to remain compatible
    -        # with WSGI)
    -        fut = gen.convert_yielded(
    -            self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
    -        )
    -        fut.add_done_callback(lambda f: f.result())
    -        # If we are streaming the request body, then execute() is finished
    -        # when the handler has prepared to receive the body.  If not,
    -        # it doesn't matter when execute() finishes (so we return None)
    -        return self.handler._prepared_future
    -
    -
    -class HTTPError(Exception):
    -    """An exception that will turn into an HTTP error response.
    -
    -    Raising an `HTTPError` is a convenient alternative to calling
    -    `RequestHandler.send_error` since it automatically ends the
    -    current function.
    -
    -    To customize the response sent with an `HTTPError`, override
    -    `RequestHandler.write_error`.
    -
    -    :arg int status_code: HTTP status code.  Must be listed in
    -        `httplib.responses ` unless the ``reason``
    -        keyword argument is given.
    -    :arg str log_message: Message to be written to the log for this error
    -        (will not be shown to the user unless the `Application` is in debug
    -        mode).  May contain ``%s``-style placeholders, which will be filled
    -        in with remaining positional parameters.
    -    :arg str reason: Keyword-only argument.  The HTTP "reason" phrase
    -        to pass in the status line along with ``status_code``.  Normally
    -        determined automatically from ``status_code``, but can be used
    -        to use a non-standard numeric code.
    -    """
    -
    -    def __init__(
    -        self,
    -        status_code: int = 500,
    -        log_message: Optional[str] = None,
    -        *args: Any,
    -        **kwargs: Any
    -    ) -> None:
    -        self.status_code = status_code
    -        self.log_message = log_message
    -        self.args = args
    -        self.reason = kwargs.get("reason", None)
    -        if log_message and not args:
    -            self.log_message = log_message.replace("%", "%%")
    -
    -    def __str__(self) -> str:
    -        message = "HTTP %d: %s" % (
    -            self.status_code,
    -            self.reason or httputil.responses.get(self.status_code, "Unknown"),
    -        )
    -        if self.log_message:
    -            return message + " (" + (self.log_message % self.args) + ")"
    -        else:
    -            return message
    -
    -
    -class Finish(Exception):
    -    """An exception that ends the request without producing an error response.
    -
    -    When `Finish` is raised in a `RequestHandler`, the request will
    -    end (calling `RequestHandler.finish` if it hasn't already been
    -    called), but the error-handling methods (including
    -    `RequestHandler.write_error`) will not be called.
    -
    -    If `Finish()` was created with no arguments, the pending response
    -    will be sent as-is. If `Finish()` was given an argument, that
    -    argument will be passed to `RequestHandler.finish()`.
    -
    -    This can be a more convenient way to implement custom error pages
    -    than overriding ``write_error`` (especially in library code)::
    -
    -        if self.current_user is None:
    -            self.set_status(401)
    -            self.set_header('WWW-Authenticate', 'Basic realm="something"')
    -            raise Finish()
    -
    -    .. versionchanged:: 4.3
    -       Arguments passed to ``Finish()`` will be passed on to
    -       `RequestHandler.finish`.
    -    """
    -
    -    pass
    -
    -
    -class MissingArgumentError(HTTPError):
    -    """Exception raised by `RequestHandler.get_argument`.
    -
    -    This is a subclass of `HTTPError`, so if it is uncaught a 400 response
    -    code will be used instead of 500 (and a stack trace will not be logged).
    -
    -    .. versionadded:: 3.1
    -    """
    -
    -    def __init__(self, arg_name: str) -> None:
    -        super().__init__(400, "Missing argument %s" % arg_name)
    -        self.arg_name = arg_name
    -
    -
    -class ErrorHandler(RequestHandler):
    -    """Generates an error response with ``status_code`` for all requests."""
    -
    -    def initialize(self, status_code: int) -> None:
    -        self.set_status(status_code)
    -
    -    def prepare(self) -> None:
    -        raise HTTPError(self._status_code)
    -
    -    def check_xsrf_cookie(self) -> None:
    -        # POSTs to an ErrorHandler don't actually have side effects,
    -        # so we don't need to check the xsrf token.  This allows POSTs
    -        # to the wrong url to return a 404 instead of 403.
    -        pass
    -
    -
    -class RedirectHandler(RequestHandler):
    -    """Redirects the client to the given URL for all GET requests.
    -
    -    You should provide the keyword argument ``url`` to the handler, e.g.::
    -
    -        application = web.Application([
    -            (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
    -        ])
    -
    -    `RedirectHandler` supports regular expression substitutions. E.g., to
    -    swap the first and second parts of a path while preserving the remainder::
    -
    -        application = web.Application([
    -            (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
    -        ])
    -
    -    The final URL is formatted with `str.format` and the substrings that match
    -    the capturing groups. In the above example, a request to "/a/b/c" would be
    -    formatted like::
    -
    -        str.format("/{1}/{0}/{2}", "a", "b", "c")  # -> "/b/a/c"
    -
    -    Use Python's :ref:`format string syntax ` to customize how
    -    values are substituted.
    -
    -    .. versionchanged:: 4.5
    -       Added support for substitutions into the destination URL.
    -
    -    .. versionchanged:: 5.0
    -       If any query arguments are present, they will be copied to the
    -       destination URL.
    -    """
    -
    -    def initialize(self, url: str, permanent: bool = True) -> None:
    -        self._url = url
    -        self._permanent = permanent
    -
    -    def get(self, *args: Any, **kwargs: Any) -> None:
    -        to_url = self._url.format(*args, **kwargs)
    -        if self.request.query_arguments:
    -            # TODO: figure out typing for the next line.
    -            to_url = httputil.url_concat(
    -                to_url,
    -                list(httputil.qs_to_qsl(self.request.query_arguments)),  # type: ignore
    -            )
    -        self.redirect(to_url, permanent=self._permanent)
    -
    -
    -class StaticFileHandler(RequestHandler):
    -    """A simple handler that can serve static content from a directory.
    -
    -    A `StaticFileHandler` is configured automatically if you pass the
    -    ``static_path`` keyword argument to `Application`.  This handler
    -    can be customized with the ``static_url_prefix``, ``static_handler_class``,
    -    and ``static_handler_args`` settings.
    -
    -    To map an additional path to this handler for a static data directory
    -    you would add a line to your application like::
    -
    -        application = web.Application([
    -            (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
    -        ])
    -
    -    The handler constructor requires a ``path`` argument, which specifies the
    -    local root directory of the content to be served.
    -
    -    Note that a capture group in the regex is required to parse the value for
    -    the ``path`` argument to the get() method (different than the constructor
    -    argument above); see `URLSpec` for details.
    -
    -    To serve a file like ``index.html`` automatically when a directory is
    -    requested, set ``static_handler_args=dict(default_filename="index.html")``
    -    in your application settings, or add ``default_filename`` as an initializer
    -    argument for your ``StaticFileHandler``.
    -
    -    To maximize the effectiveness of browser caching, this class supports
    -    versioned urls (by default using the argument ``?v=``).  If a version
    -    is given, we instruct the browser to cache this file indefinitely.
    -    `make_static_url` (also available as `RequestHandler.static_url`) can
    -    be used to construct a versioned url.
    -
    -    This handler is intended primarily for use in development and light-duty
    -    file serving; for heavy traffic it will be more efficient to use
    -    a dedicated static file server (such as nginx or Apache).  We support
    -    the HTTP ``Accept-Ranges`` mechanism to return partial content (because
    -    some browsers require this functionality to be present to seek in
    -    HTML5 audio or video).
    -
    -    **Subclassing notes**
    -
    -    This class is designed to be extensible by subclassing, but because
    -    of the way static urls are generated with class methods rather than
    -    instance methods, the inheritance patterns are somewhat unusual.
    -    Be sure to use the ``@classmethod`` decorator when overriding a
    -    class method.  Instance methods may use the attributes ``self.path``
    -    ``self.absolute_path``, and ``self.modified``.
    -
    -    Subclasses should only override methods discussed in this section;
    -    overriding other methods is error-prone.  Overriding
    -    ``StaticFileHandler.get`` is particularly problematic due to the
    -    tight coupling with ``compute_etag`` and other methods.
    -
    -    To change the way static urls are generated (e.g. to match the behavior
    -    of another server or CDN), override `make_static_url`, `parse_url_path`,
    -    `get_cache_time`, and/or `get_version`.
    -
    -    To replace all interaction with the filesystem (e.g. to serve
    -    static content from a database), override `get_content`,
    -    `get_content_size`, `get_modified_time`, `get_absolute_path`, and
    -    `validate_absolute_path`.
    -
    -    .. versionchanged:: 3.1
    -       Many of the methods for subclasses were added in Tornado 3.1.
    -    """
    -
    -    CACHE_MAX_AGE = 86400 * 365 * 10  # 10 years
    -
    -    _static_hashes = {}  # type: Dict[str, Optional[str]]
    -    _lock = threading.Lock()  # protects _static_hashes
    -
    -    def initialize(self, path: str, default_filename: Optional[str] = None) -> None:
    -        self.root = path
    -        self.default_filename = default_filename
    -
    -    @classmethod
    -    def reset(cls) -> None:
    -        with cls._lock:
    -            cls._static_hashes = {}
    -
    -    def head(self, path: str) -> Awaitable[None]:
    -        return self.get(path, include_body=False)
    -
    -    async def get(self, path: str, include_body: bool = True) -> None:
    -        # Set up our path instance variables.
    -        self.path = self.parse_url_path(path)
    -        del path  # make sure we don't refer to path instead of self.path again
    -        absolute_path = self.get_absolute_path(self.root, self.path)
    -        self.absolute_path = self.validate_absolute_path(self.root, absolute_path)
    -        if self.absolute_path is None:
    -            return
    -
    -        self.modified = self.get_modified_time()
    -        self.set_headers()
    -
    -        if self.should_return_304():
    -            self.set_status(304)
    -            return
    -
    -        request_range = None
    -        range_header = self.request.headers.get("Range")
    -        if range_header:
    -            # As per RFC 2616 14.16, if an invalid Range header is specified,
    -            # the request will be treated as if the header didn't exist.
    -            request_range = httputil._parse_request_range(range_header)
    -
    -        size = self.get_content_size()
    -        if request_range:
    -            start, end = request_range
    -            if start is not None and start < 0:
    -                start += size
    -                if start < 0:
    -                    start = 0
    -            if (
    -                start is not None
    -                and (start >= size or (end is not None and start >= end))
    -            ) or end == 0:
    -                # As per RFC 2616 14.35.1, a range is not satisfiable only: if
    -                # the first requested byte is equal to or greater than the
    -                # content, or when a suffix with length 0 is specified.
    -                # https://tools.ietf.org/html/rfc7233#section-2.1
    -                # A byte-range-spec is invalid if the last-byte-pos value is present
    -                # and less than the first-byte-pos.
    -                self.set_status(416)  # Range Not Satisfiable
    -                self.set_header("Content-Type", "text/plain")
    -                self.set_header("Content-Range", "bytes */%s" % (size,))
    -                return
    -            if end is not None and end > size:
    -                # Clients sometimes blindly use a large range to limit their
    -                # download size; cap the endpoint at the actual file size.
    -                end = size
    -            # Note: only return HTTP 206 if less than the entire range has been
    -            # requested. Not only is this semantically correct, but Chrome
    -            # refuses to play audio if it gets an HTTP 206 in response to
    -            # ``Range: bytes=0-``.
    -            if size != (end or size) - (start or 0):
    -                self.set_status(206)  # Partial Content
    -                self.set_header(
    -                    "Content-Range", httputil._get_content_range(start, end, size)
    -                )
    -        else:
    -            start = end = None
    -
    -        if start is not None and end is not None:
    -            content_length = end - start
    -        elif end is not None:
    -            content_length = end
    -        elif start is not None:
    -            content_length = size - start
    -        else:
    -            content_length = size
    -        self.set_header("Content-Length", content_length)
    -
    -        if include_body:
    -            content = self.get_content(self.absolute_path, start, end)
    -            if isinstance(content, bytes):
    -                content = [content]
    -            for chunk in content:
    -                try:
    -                    self.write(chunk)
    -                    await self.flush()
    -                except iostream.StreamClosedError:
    -                    return
    -        else:
    -            assert self.request.method == "HEAD"
    -
    -    def compute_etag(self) -> Optional[str]:
    -        """Sets the ``Etag`` header based on static url version.
    -
    -        This allows efficient ``If-None-Match`` checks against cached
    -        versions, and sends the correct ``Etag`` for a partial response
    -        (i.e. the same ``Etag`` as the full file).
    -
    -        .. versionadded:: 3.1
    -        """
    -        assert self.absolute_path is not None
    -        version_hash = self._get_cached_version(self.absolute_path)
    -        if not version_hash:
    -            return None
    -        return '"%s"' % (version_hash,)
    -
    -    def set_headers(self) -> None:
    -        """Sets the content and caching headers on the response.
    -
    -        .. versionadded:: 3.1
    -        """
    -        self.set_header("Accept-Ranges", "bytes")
    -        self.set_etag_header()
    -
    -        if self.modified is not None:
    -            self.set_header("Last-Modified", self.modified)
    -
    -        content_type = self.get_content_type()
    -        if content_type:
    -            self.set_header("Content-Type", content_type)
    -
    -        cache_time = self.get_cache_time(self.path, self.modified, content_type)
    -        if cache_time > 0:
    -            self.set_header(
    -                "Expires",
    -                datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time),
    -            )
    -            self.set_header("Cache-Control", "max-age=" + str(cache_time))
    -
    -        self.set_extra_headers(self.path)
    -
    -    def should_return_304(self) -> bool:
    -        """Returns True if the headers indicate that we should return 304.
    -
    -        .. versionadded:: 3.1
    -        """
    -        # If client sent If-None-Match, use it, ignore If-Modified-Since
    -        if self.request.headers.get("If-None-Match"):
    -            return self.check_etag_header()
    -
    -        # Check the If-Modified-Since, and don't send the result if the
    -        # content has not been modified
    -        ims_value = self.request.headers.get("If-Modified-Since")
    -        if ims_value is not None:
    -            date_tuple = email.utils.parsedate(ims_value)
    -            if date_tuple is not None:
    -                if_since = datetime.datetime(*date_tuple[:6])
    -                assert self.modified is not None
    -                if if_since >= self.modified:
    -                    return True
    -
    -        return False
    -
    -    @classmethod
    -    def get_absolute_path(cls, root: str, path: str) -> str:
    -        """Returns the absolute location of ``path`` relative to ``root``.
    -
    -        ``root`` is the path configured for this `StaticFileHandler`
    -        (in most cases the ``static_path`` `Application` setting).
    -
    -        This class method may be overridden in subclasses.  By default
    -        it returns a filesystem path, but other strings may be used
    -        as long as they are unique and understood by the subclass's
    -        overridden `get_content`.
    -
    -        .. versionadded:: 3.1
    -        """
    -        abspath = os.path.abspath(os.path.join(root, path))
    -        return abspath
    -
    -    def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
    -        """Validate and return the absolute path.
    -
    -        ``root`` is the configured path for the `StaticFileHandler`,
    -        and ``path`` is the result of `get_absolute_path`
    -
    -        This is an instance method called during request processing,
    -        so it may raise `HTTPError` or use methods like
    -        `RequestHandler.redirect` (return None after redirecting to
    -        halt further processing).  This is where 404 errors for missing files
    -        are generated.
    -
    -        This method may modify the path before returning it, but note that
    -        any such modifications will not be understood by `make_static_url`.
    -
    -        In instance methods, this method's result is available as
    -        ``self.absolute_path``.
    -
    -        .. versionadded:: 3.1
    -        """
    -        # os.path.abspath strips a trailing /.
    -        # We must add it back to `root` so that we only match files
    -        # in a directory named `root` instead of files starting with
    -        # that prefix.
    -        root = os.path.abspath(root)
    -        if not root.endswith(os.path.sep):
    -            # abspath always removes a trailing slash, except when
    -            # root is '/'. This is an unusual case, but several projects
    -            # have independently discovered this technique to disable
    -            # Tornado's path validation and (hopefully) do their own,
    -            # so we need to support it.
    -            root += os.path.sep
    -        # The trailing slash also needs to be temporarily added back
    -        # the requested path so a request to root/ will match.
    -        if not (absolute_path + os.path.sep).startswith(root):
    -            raise HTTPError(403, "%s is not in root static directory", self.path)
    -        if os.path.isdir(absolute_path) and self.default_filename is not None:
    -            # need to look at the request.path here for when path is empty
    -            # but there is some prefix to the path that was already
    -            # trimmed by the routing
    -            if not self.request.path.endswith("/"):
    -                self.redirect(self.request.path + "/", permanent=True)
    -                return None
    -            absolute_path = os.path.join(absolute_path, self.default_filename)
    -        if not os.path.exists(absolute_path):
    -            raise HTTPError(404)
    -        if not os.path.isfile(absolute_path):
    -            raise HTTPError(403, "%s is not a file", self.path)
    -        return absolute_path
    -
    -    @classmethod
    -    def get_content(
    -        cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None
    -    ) -> Generator[bytes, None, None]:
    -        """Retrieve the content of the requested resource which is located
    -        at the given absolute path.
    -
    -        This class method may be overridden by subclasses.  Note that its
    -        signature is different from other overridable class methods
    -        (no ``settings`` argument); this is deliberate to ensure that
    -        ``abspath`` is able to stand on its own as a cache key.
    -
    -        This method should either return a byte string or an iterator
    -        of byte strings.  The latter is preferred for large files
    -        as it helps reduce memory fragmentation.
    -
    -        .. versionadded:: 3.1
    -        """
    -        with open(abspath, "rb") as file:
    -            if start is not None:
    -                file.seek(start)
    -            if end is not None:
    -                remaining = end - (start or 0)  # type: Optional[int]
    -            else:
    -                remaining = None
    -            while True:
    -                chunk_size = 64 * 1024
    -                if remaining is not None and remaining < chunk_size:
    -                    chunk_size = remaining
    -                chunk = file.read(chunk_size)
    -                if chunk:
    -                    if remaining is not None:
    -                        remaining -= len(chunk)
    -                    yield chunk
    -                else:
    -                    if remaining is not None:
    -                        assert remaining == 0
    -                    return
    -
    -    @classmethod
    -    def get_content_version(cls, abspath: str) -> str:
    -        """Returns a version string for the resource at the given path.
    -
    -        This class method may be overridden by subclasses.  The
    -        default implementation is a SHA-512 hash of the file's contents.
    -
    -        .. versionadded:: 3.1
    -        """
    -        data = cls.get_content(abspath)
    -        hasher = hashlib.sha512()
    -        if isinstance(data, bytes):
    -            hasher.update(data)
    -        else:
    -            for chunk in data:
    -                hasher.update(chunk)
    -        return hasher.hexdigest()
    -
    -    def _stat(self) -> os.stat_result:
    -        assert self.absolute_path is not None
    -        if not hasattr(self, "_stat_result"):
    -            self._stat_result = os.stat(self.absolute_path)
    -        return self._stat_result
    -
    -    def get_content_size(self) -> int:
    -        """Retrieve the total size of the resource at the given path.
    -
    -        This method may be overridden by subclasses.
    -
    -        .. versionadded:: 3.1
    -
    -        .. versionchanged:: 4.0
    -           This method is now always called, instead of only when
    -           partial results are requested.
    -        """
    -        stat_result = self._stat()
    -        return stat_result.st_size
    -
    -    def get_modified_time(self) -> Optional[datetime.datetime]:
    -        """Returns the time that ``self.absolute_path`` was last modified.
    -
    -        May be overridden in subclasses.  Should return a `~datetime.datetime`
    -        object or None.
    -
    -        .. versionadded:: 3.1
    -        """
    -        stat_result = self._stat()
    -        # NOTE: Historically, this used stat_result[stat.ST_MTIME],
    -        # which truncates the fractional portion of the timestamp. It
    -        # was changed from that form to stat_result.st_mtime to
    -        # satisfy mypy (which disallows the bracket operator), but the
    -        # latter form returns a float instead of an int. For
    -        # consistency with the past (and because we have a unit test
    -        # that relies on this), we truncate the float here, although
    -        # I'm not sure that's the right thing to do.
    -        modified = datetime.datetime.utcfromtimestamp(int(stat_result.st_mtime))
    -        return modified
    -
    -    def get_content_type(self) -> str:
    -        """Returns the ``Content-Type`` header to be used for this request.
    -
    -        .. versionadded:: 3.1
    -        """
    -        assert self.absolute_path is not None
    -        mime_type, encoding = mimetypes.guess_type(self.absolute_path)
    -        # per RFC 6713, use the appropriate type for a gzip compressed file
    -        if encoding == "gzip":
    -            return "application/gzip"
    -        # As of 2015-07-21 there is no bzip2 encoding defined at
    -        # http://www.iana.org/assignments/media-types/media-types.xhtml
    -        # So for that (and any other encoding), use octet-stream.
    -        elif encoding is not None:
    -            return "application/octet-stream"
    -        elif mime_type is not None:
    -            return mime_type
    -        # if mime_type not detected, use application/octet-stream
    -        else:
    -            return "application/octet-stream"
    -
    -    def set_extra_headers(self, path: str) -> None:
    -        """For subclass to add extra headers to the response"""
    -        pass
    -
    -    def get_cache_time(
    -        self, path: str, modified: Optional[datetime.datetime], mime_type: str
    -    ) -> int:
    -        """Override to customize cache control behavior.
    -
    -        Return a positive number of seconds to make the result
    -        cacheable for that amount of time or 0 to mark resource as
    -        cacheable for an unspecified amount of time (subject to
    -        browser heuristics).
    -
    -        By default returns cache expiry of 10 years for resources requested
    -        with ``v`` argument.
    -        """
    -        return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
    -
    -    @classmethod
    -    def make_static_url(
    -        cls, settings: Dict[str, Any], path: str, include_version: bool = True
    -    ) -> str:
    -        """Constructs a versioned url for the given path.
    -
    -        This method may be overridden in subclasses (but note that it
    -        is a class method rather than an instance method).  Subclasses
    -        are only required to implement the signature
    -        ``make_static_url(cls, settings, path)``; other keyword
    -        arguments may be passed through `~RequestHandler.static_url`
    -        but are not standard.
    -
    -        ``settings`` is the `Application.settings` dictionary.  ``path``
    -        is the static path being requested.  The url returned should be
    -        relative to the current host.
    -
    -        ``include_version`` determines whether the generated URL should
    -        include the query string containing the version hash of the
    -        file corresponding to the given ``path``.
    -
    -        """
    -        url = settings.get("static_url_prefix", "/static/") + path
    -        if not include_version:
    -            return url
    -
    -        version_hash = cls.get_version(settings, path)
    -        if not version_hash:
    -            return url
    -
    -        return "%s?v=%s" % (url, version_hash)
    -
    -    def parse_url_path(self, url_path: str) -> str:
    -        """Converts a static URL path into a filesystem path.
    -
    -        ``url_path`` is the path component of the URL with
    -        ``static_url_prefix`` removed.  The return value should be
    -        filesystem path relative to ``static_path``.
    -
    -        This is the inverse of `make_static_url`.
    -        """
    -        if os.path.sep != "/":
    -            url_path = url_path.replace("/", os.path.sep)
    -        return url_path
    -
    -    @classmethod
    -    def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]:
    -        """Generate the version string to be used in static URLs.
    -
    -        ``settings`` is the `Application.settings` dictionary and ``path``
    -        is the relative location of the requested asset on the filesystem.
    -        The returned value should be a string, or ``None`` if no version
    -        could be determined.
    -
    -        .. versionchanged:: 3.1
    -           This method was previously recommended for subclasses to override;
    -           `get_content_version` is now preferred as it allows the base
    -           class to handle caching of the result.
    -        """
    -        abs_path = cls.get_absolute_path(settings["static_path"], path)
    -        return cls._get_cached_version(abs_path)
    -
    -    @classmethod
    -    def _get_cached_version(cls, abs_path: str) -> Optional[str]:
    -        with cls._lock:
    -            hashes = cls._static_hashes
    -            if abs_path not in hashes:
    -                try:
    -                    hashes[abs_path] = cls.get_content_version(abs_path)
    -                except Exception:
    -                    gen_log.error("Could not open static file %r", abs_path)
    -                    hashes[abs_path] = None
    -            hsh = hashes.get(abs_path)
    -            if hsh:
    -                return hsh
    -        return None
    -
    -
    -class FallbackHandler(RequestHandler):
    -    """A `RequestHandler` that wraps another HTTP server callback.
    -
    -    The fallback is a callable object that accepts an
    -    `~.httputil.HTTPServerRequest`, such as an `Application` or
    -    `tornado.wsgi.WSGIContainer`.  This is most useful to use both
    -    Tornado ``RequestHandlers`` and WSGI in the same server.  Typical
    -    usage::
    -
    -        wsgi_app = tornado.wsgi.WSGIContainer(
    -            django.core.handlers.wsgi.WSGIHandler())
    -        application = tornado.web.Application([
    -            (r"/foo", FooHandler),
    -            (r".*", FallbackHandler, dict(fallback=wsgi_app),
    -        ])
    -    """
    -
    -    def initialize(
    -        self, fallback: Callable[[httputil.HTTPServerRequest], None]
    -    ) -> None:
    -        self.fallback = fallback
    -
    -    def prepare(self) -> None:
    -        self.fallback(self.request)
    -        self._finished = True
    -        self.on_finish()
    -
    -
    -class OutputTransform(object):
    -    """A transform modifies the result of an HTTP request (e.g., GZip encoding)
    -
    -    Applications are not expected to create their own OutputTransforms
    -    or interact with them directly; the framework chooses which transforms
    -    (if any) to apply.
    -    """
    -
    -    def __init__(self, request: httputil.HTTPServerRequest) -> None:
    -        pass
    -
    -    def transform_first_chunk(
    -        self,
    -        status_code: int,
    -        headers: httputil.HTTPHeaders,
    -        chunk: bytes,
    -        finishing: bool,
    -    ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
    -        return status_code, headers, chunk
    -
    -    def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
    -        return chunk
    -
    -
    -class GZipContentEncoding(OutputTransform):
    -    """Applies the gzip content encoding to the response.
    -
    -    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
    -
    -    .. versionchanged:: 4.0
    -        Now compresses all mime types beginning with ``text/``, instead
    -        of just a whitelist. (the whitelist is still used for certain
    -        non-text mime types).
    -    """
    -
    -    # Whitelist of compressible mime types (in addition to any types
    -    # beginning with "text/").
    -    CONTENT_TYPES = set(
    -        [
    -            "application/javascript",
    -            "application/x-javascript",
    -            "application/xml",
    -            "application/atom+xml",
    -            "application/json",
    -            "application/xhtml+xml",
    -            "image/svg+xml",
    -        ]
    -    )
    -    # Python's GzipFile defaults to level 9, while most other gzip
    -    # tools (including gzip itself) default to 6, which is probably a
    -    # better CPU/size tradeoff.
    -    GZIP_LEVEL = 6
    -    # Responses that are too short are unlikely to benefit from gzipping
    -    # after considering the "Content-Encoding: gzip" header and the header
    -    # inside the gzip encoding.
    -    # Note that responses written in multiple chunks will be compressed
    -    # regardless of size.
    -    MIN_LENGTH = 1024
    -
    -    def __init__(self, request: httputil.HTTPServerRequest) -> None:
    -        self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
    -
    -    def _compressible_type(self, ctype: str) -> bool:
    -        return ctype.startswith("text/") or ctype in self.CONTENT_TYPES
    -
    -    def transform_first_chunk(
    -        self,
    -        status_code: int,
    -        headers: httputil.HTTPHeaders,
    -        chunk: bytes,
    -        finishing: bool,
    -    ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
    -        # TODO: can/should this type be inherited from the superclass?
    -        if "Vary" in headers:
    -            headers["Vary"] += ", Accept-Encoding"
    -        else:
    -            headers["Vary"] = "Accept-Encoding"
    -        if self._gzipping:
    -            ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
    -            self._gzipping = (
    -                self._compressible_type(ctype)
    -                and (not finishing or len(chunk) >= self.MIN_LENGTH)
    -                and ("Content-Encoding" not in headers)
    -            )
    -        if self._gzipping:
    -            headers["Content-Encoding"] = "gzip"
    -            self._gzip_value = BytesIO()
    -            self._gzip_file = gzip.GzipFile(
    -                mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL
    -            )
    -            chunk = self.transform_chunk(chunk, finishing)
    -            if "Content-Length" in headers:
    -                # The original content length is no longer correct.
    -                # If this is the last (and only) chunk, we can set the new
    -                # content-length; otherwise we remove it and fall back to
    -                # chunked encoding.
    -                if finishing:
    -                    headers["Content-Length"] = str(len(chunk))
    -                else:
    -                    del headers["Content-Length"]
    -        return status_code, headers, chunk
    -
    -    def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
    -        if self._gzipping:
    -            self._gzip_file.write(chunk)
    -            if finishing:
    -                self._gzip_file.close()
    -            else:
    -                self._gzip_file.flush()
    -            chunk = self._gzip_value.getvalue()
    -            self._gzip_value.truncate(0)
    -            self._gzip_value.seek(0)
    -        return chunk
    -
    -
    -def authenticated(
    -    method: Callable[..., Optional[Awaitable[None]]]
    -) -> Callable[..., Optional[Awaitable[None]]]:
    -    """Decorate methods with this to require that the user be logged in.
    -
    -    If the user is not logged in, they will be redirected to the configured
    -    `login url `.
    -
    -    If you configure a login url with a query parameter, Tornado will
    -    assume you know what you're doing and use it as-is.  If not, it
    -    will add a `next` parameter so the login page knows where to send
    -    you once you're logged in.
    -    """
    -
    -    @functools.wraps(method)
    -    def wrapper(  # type: ignore
    -        self: RequestHandler, *args, **kwargs
    -    ) -> Optional[Awaitable[None]]:
    -        if not self.current_user:
    -            if self.request.method in ("GET", "HEAD"):
    -                url = self.get_login_url()
    -                if "?" not in url:
    -                    if urllib.parse.urlsplit(url).scheme:
    -                        # if login url is absolute, make next absolute too
    -                        next_url = self.request.full_url()
    -                    else:
    -                        assert self.request.uri is not None
    -                        next_url = self.request.uri
    -                    url += "?" + urlencode(dict(next=next_url))
    -                self.redirect(url)
    -                return None
    -            raise HTTPError(403)
    -        return method(self, *args, **kwargs)
    -
    -    return wrapper
    -
    -
    -class UIModule(object):
    -    """A re-usable, modular UI unit on a page.
    -
    -    UI modules often execute additional queries, and they can include
    -    additional CSS and JavaScript that will be included in the output
    -    page, which is automatically inserted on page render.
    -
    -    Subclasses of UIModule must override the `render` method.
    -    """
    -
    -    def __init__(self, handler: RequestHandler) -> None:
    -        self.handler = handler
    -        self.request = handler.request
    -        self.ui = handler.ui
    -        self.locale = handler.locale
    -
    -    @property
    -    def current_user(self) -> Any:
    -        return self.handler.current_user
    -
    -    def render(self, *args: Any, **kwargs: Any) -> str:
    -        """Override in subclasses to return this module's output."""
    -        raise NotImplementedError()
    -
    -    def embedded_javascript(self) -> Optional[str]:
    -        """Override to return a JavaScript string
    -        to be embedded in the page."""
    -        return None
    -
    -    def javascript_files(self) -> Optional[Iterable[str]]:
    -        """Override to return a list of JavaScript files needed by this module.
    -
    -        If the return values are relative paths, they will be passed to
    -        `RequestHandler.static_url`; otherwise they will be used as-is.
    -        """
    -        return None
    -
    -    def embedded_css(self) -> Optional[str]:
    -        """Override to return a CSS string
    -        that will be embedded in the page."""
    -        return None
    -
    -    def css_files(self) -> Optional[Iterable[str]]:
    -        """Override to returns a list of CSS files required by this module.
    -
    -        If the return values are relative paths, they will be passed to
    -        `RequestHandler.static_url`; otherwise they will be used as-is.
    -        """
    -        return None
    -
    -    def html_head(self) -> Optional[str]:
    -        """Override to return an HTML string that will be put in the 
    -        element.
    -        """
    -        return None
    -
    -    def html_body(self) -> Optional[str]:
    -        """Override to return an HTML string that will be put at the end of
    -        the  element.
    -        """
    -        return None
    -
    -    def render_string(self, path: str, **kwargs: Any) -> bytes:
    -        """Renders a template and returns it as a string."""
    -        return self.handler.render_string(path, **kwargs)
    -
    -
    -class _linkify(UIModule):
    -    def render(self, text: str, **kwargs: Any) -> str:  # type: ignore
    -        return escape.linkify(text, **kwargs)
    -
    -
    -class _xsrf_form_html(UIModule):
    -    def render(self) -> str:  # type: ignore
    -        return self.handler.xsrf_form_html()
    -
    -
    -class TemplateModule(UIModule):
    -    """UIModule that simply renders the given template.
    -
    -    {% module Template("foo.html") %} is similar to {% include "foo.html" %},
    -    but the module version gets its own namespace (with kwargs passed to
    -    Template()) instead of inheriting the outer template's namespace.
    -
    -    Templates rendered through this module also get access to UIModule's
    -    automatic JavaScript/CSS features.  Simply call set_resources
    -    inside the template and give it keyword arguments corresponding to
    -    the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
    -    Note that these resources are output once per template file, not once
    -    per instantiation of the template, so they must not depend on
    -    any arguments to the template.
    -    """
    -
    -    def __init__(self, handler: RequestHandler) -> None:
    -        super().__init__(handler)
    -        # keep resources in both a list and a dict to preserve order
    -        self._resource_list = []  # type: List[Dict[str, Any]]
    -        self._resource_dict = {}  # type: Dict[str, Dict[str, Any]]
    -
    -    def render(self, path: str, **kwargs: Any) -> bytes:  # type: ignore
    -        def set_resources(**kwargs) -> str:  # type: ignore
    -            if path not in self._resource_dict:
    -                self._resource_list.append(kwargs)
    -                self._resource_dict[path] = kwargs
    -            else:
    -                if self._resource_dict[path] != kwargs:
    -                    raise ValueError(
    -                        "set_resources called with different "
    -                        "resources for the same template"
    -                    )
    -            return ""
    -
    -        return self.render_string(path, set_resources=set_resources, **kwargs)
    -
    -    def _get_resources(self, key: str) -> Iterable[str]:
    -        return (r[key] for r in self._resource_list if key in r)
    -
    -    def embedded_javascript(self) -> str:
    -        return "\n".join(self._get_resources("embedded_javascript"))
    -
    -    def javascript_files(self) -> Iterable[str]:
    -        result = []
    -        for f in self._get_resources("javascript_files"):
    -            if isinstance(f, (unicode_type, bytes)):
    -                result.append(f)
    -            else:
    -                result.extend(f)
    -        return result
    -
    -    def embedded_css(self) -> str:
    -        return "\n".join(self._get_resources("embedded_css"))
    -
    -    def css_files(self) -> Iterable[str]:
    -        result = []
    -        for f in self._get_resources("css_files"):
    -            if isinstance(f, (unicode_type, bytes)):
    -                result.append(f)
    -            else:
    -                result.extend(f)
    -        return result
    -
    -    def html_head(self) -> str:
    -        return "".join(self._get_resources("html_head"))
    -
    -    def html_body(self) -> str:
    -        return "".join(self._get_resources("html_body"))
    -
    -
    -class _UIModuleNamespace(object):
    -    """Lazy namespace which creates UIModule proxies bound to a handler."""
    -
    -    def __init__(
    -        self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]]
    -    ) -> None:
    -        self.handler = handler
    -        self.ui_modules = ui_modules
    -
    -    def __getitem__(self, key: str) -> Callable[..., str]:
    -        return self.handler._ui_module(key, self.ui_modules[key])
    -
    -    def __getattr__(self, key: str) -> Callable[..., str]:
    -        try:
    -            return self[key]
    -        except KeyError as e:
    -            raise AttributeError(str(e))
    -
    -
    -def create_signed_value(
    -    secret: _CookieSecretTypes,
    -    name: str,
    -    value: Union[str, bytes],
    -    version: Optional[int] = None,
    -    clock: Optional[Callable[[], float]] = None,
    -    key_version: Optional[int] = None,
    -) -> bytes:
    -    if version is None:
    -        version = DEFAULT_SIGNED_VALUE_VERSION
    -    if clock is None:
    -        clock = time.time
    -
    -    timestamp = utf8(str(int(clock())))
    -    value = base64.b64encode(utf8(value))
    -    if version == 1:
    -        assert not isinstance(secret, dict)
    -        signature = _create_signature_v1(secret, name, value, timestamp)
    -        value = b"|".join([value, timestamp, signature])
    -        return value
    -    elif version == 2:
    -        # The v2 format consists of a version number and a series of
    -        # length-prefixed fields "%d:%s", the last of which is a
    -        # signature, all separated by pipes.  All numbers are in
    -        # decimal format with no leading zeros.  The signature is an
    -        # HMAC-SHA256 of the whole string up to that point, including
    -        # the final pipe.
    -        #
    -        # The fields are:
    -        # - format version (i.e. 2; no length prefix)
    -        # - key version (integer, default is 0)
    -        # - timestamp (integer seconds since epoch)
    -        # - name (not encoded; assumed to be ~alphanumeric)
    -        # - value (base64-encoded)
    -        # - signature (hex-encoded; no length prefix)
    -        def format_field(s: Union[str, bytes]) -> bytes:
    -            return utf8("%d:" % len(s)) + utf8(s)
    -
    -        to_sign = b"|".join(
    -            [
    -                b"2",
    -                format_field(str(key_version or 0)),
    -                format_field(timestamp),
    -                format_field(name),
    -                format_field(value),
    -                b"",
    -            ]
    -        )
    -
    -        if isinstance(secret, dict):
    -            assert (
    -                key_version is not None
    -            ), "Key version must be set when sign key dict is used"
    -            assert version >= 2, "Version must be at least 2 for key version support"
    -            secret = secret[key_version]
    -
    -        signature = _create_signature_v2(secret, to_sign)
    -        return to_sign + signature
    -    else:
    -        raise ValueError("Unsupported version %d" % version)
    -
    -
    -# A leading version number in decimal
    -# with no leading zeros, followed by a pipe.
    -_signed_value_version_re = re.compile(br"^([1-9][0-9]*)\|(.*)$")
    -
    -
    -def _get_version(value: bytes) -> int:
    -    # Figures out what version value is.  Version 1 did not include an
    -    # explicit version field and started with arbitrary base64 data,
    -    # which makes this tricky.
    -    m = _signed_value_version_re.match(value)
    -    if m is None:
    -        version = 1
    -    else:
    -        try:
    -            version = int(m.group(1))
    -            if version > 999:
    -                # Certain payloads from the version-less v1 format may
    -                # be parsed as valid integers.  Due to base64 padding
    -                # restrictions, this can only happen for numbers whose
    -                # length is a multiple of 4, so we can treat all
    -                # numbers up to 999 as versions, and for the rest we
    -                # fall back to v1 format.
    -                version = 1
    -        except ValueError:
    -            version = 1
    -    return version
    -
    -
    -def decode_signed_value(
    -    secret: _CookieSecretTypes,
    -    name: str,
    -    value: Union[None, str, bytes],
    -    max_age_days: float = 31,
    -    clock: Optional[Callable[[], float]] = None,
    -    min_version: Optional[int] = None,
    -) -> Optional[bytes]:
    -    if clock is None:
    -        clock = time.time
    -    if min_version is None:
    -        min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION
    -    if min_version > 2:
    -        raise ValueError("Unsupported min_version %d" % min_version)
    -    if not value:
    -        return None
    -
    -    value = utf8(value)
    -    version = _get_version(value)
    -
    -    if version < min_version:
    -        return None
    -    if version == 1:
    -        assert not isinstance(secret, dict)
    -        return _decode_signed_value_v1(secret, name, value, max_age_days, clock)
    -    elif version == 2:
    -        return _decode_signed_value_v2(secret, name, value, max_age_days, clock)
    -    else:
    -        return None
    -
    -
    -def _decode_signed_value_v1(
    -    secret: Union[str, bytes],
    -    name: str,
    -    value: bytes,
    -    max_age_days: float,
    -    clock: Callable[[], float],
    -) -> Optional[bytes]:
    -    parts = utf8(value).split(b"|")
    -    if len(parts) != 3:
    -        return None
    -    signature = _create_signature_v1(secret, name, parts[0], parts[1])
    -    if not hmac.compare_digest(parts[2], signature):
    -        gen_log.warning("Invalid cookie signature %r", value)
    -        return None
    -    timestamp = int(parts[1])
    -    if timestamp < clock() - max_age_days * 86400:
    -        gen_log.warning("Expired cookie %r", value)
    -        return None
    -    if timestamp > clock() + 31 * 86400:
    -        # _cookie_signature does not hash a delimiter between the
    -        # parts of the cookie, so an attacker could transfer trailing
    -        # digits from the payload to the timestamp without altering the
    -        # signature.  For backwards compatibility, sanity-check timestamp
    -        # here instead of modifying _cookie_signature.
    -        gen_log.warning("Cookie timestamp in future; possible tampering %r", value)
    -        return None
    -    if parts[1].startswith(b"0"):
    -        gen_log.warning("Tampered cookie %r", value)
    -        return None
    -    try:
    -        return base64.b64decode(parts[0])
    -    except Exception:
    -        return None
    -
    -
    -def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]:
    -    def _consume_field(s: bytes) -> Tuple[bytes, bytes]:
    -        length, _, rest = s.partition(b":")
    -        n = int(length)
    -        field_value = rest[:n]
    -        # In python 3, indexing bytes returns small integers; we must
    -        # use a slice to get a byte string as in python 2.
    -        if rest[n : n + 1] != b"|":
    -            raise ValueError("malformed v2 signed value field")
    -        rest = rest[n + 1 :]
    -        return field_value, rest
    -
    -    rest = value[2:]  # remove version number
    -    key_version, rest = _consume_field(rest)
    -    timestamp, rest = _consume_field(rest)
    -    name_field, rest = _consume_field(rest)
    -    value_field, passed_sig = _consume_field(rest)
    -    return int(key_version), timestamp, name_field, value_field, passed_sig
    -
    -
    -def _decode_signed_value_v2(
    -    secret: _CookieSecretTypes,
    -    name: str,
    -    value: bytes,
    -    max_age_days: float,
    -    clock: Callable[[], float],
    -) -> Optional[bytes]:
    -    try:
    -        (
    -            key_version,
    -            timestamp_bytes,
    -            name_field,
    -            value_field,
    -            passed_sig,
    -        ) = _decode_fields_v2(value)
    -    except ValueError:
    -        return None
    -    signed_string = value[: -len(passed_sig)]
    -
    -    if isinstance(secret, dict):
    -        try:
    -            secret = secret[key_version]
    -        except KeyError:
    -            return None
    -
    -    expected_sig = _create_signature_v2(secret, signed_string)
    -    if not hmac.compare_digest(passed_sig, expected_sig):
    -        return None
    -    if name_field != utf8(name):
    -        return None
    -    timestamp = int(timestamp_bytes)
    -    if timestamp < clock() - max_age_days * 86400:
    -        # The signature has expired.
    -        return None
    -    try:
    -        return base64.b64decode(value_field)
    -    except Exception:
    -        return None
    -
    -
    -def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]:
    -    value = utf8(value)
    -    version = _get_version(value)
    -    if version < 2:
    -        return None
    -    try:
    -        key_version, _, _, _, _ = _decode_fields_v2(value)
    -    except ValueError:
    -        return None
    -
    -    return key_version
    -
    -
    -def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes:
    -    hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
    -    for part in parts:
    -        hash.update(utf8(part))
    -    return utf8(hash.hexdigest())
    -
    -
    -def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes:
    -    hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
    -    hash.update(utf8(s))
    -    return utf8(hash.hexdigest())
    -
    -
    -def is_absolute(path: str) -> bool:
    -    return any(path.startswith(x) for x in ["/", "http:", "https:"])
    diff --git a/telegramer/include/tornado/websocket.py b/telegramer/include/tornado/websocket.py
    deleted file mode 100644
    index eef49e7..0000000
    --- a/telegramer/include/tornado/websocket.py
    +++ /dev/null
    @@ -1,1666 +0,0 @@
    -"""Implementation of the WebSocket protocol.
    -
    -`WebSockets `_ allow for bidirectional
    -communication between the browser and server.
    -
    -WebSockets are supported in the current versions of all major browsers,
    -although older versions that do not support WebSockets are still in use
    -(refer to http://caniuse.com/websockets for details).
    -
    -This module implements the final version of the WebSocket protocol as
    -defined in `RFC 6455 `_.  Certain
    -browser versions (notably Safari 5.x) implemented an earlier draft of
    -the protocol (known as "draft 76") and are not compatible with this module.
    -
    -.. versionchanged:: 4.0
    -   Removed support for the draft 76 protocol version.
    -"""
    -
    -import abc
    -import asyncio
    -import base64
    -import hashlib
    -import os
    -import sys
    -import struct
    -import tornado.escape
    -import tornado.web
    -from urllib.parse import urlparse
    -import zlib
    -
    -from tornado.concurrent import Future, future_set_result_unless_cancelled
    -from tornado.escape import utf8, native_str, to_unicode
    -from tornado import gen, httpclient, httputil
    -from tornado.ioloop import IOLoop, PeriodicCallback
    -from tornado.iostream import StreamClosedError, IOStream
    -from tornado.log import gen_log, app_log
    -from tornado import simple_httpclient
    -from tornado.queues import Queue
    -from tornado.tcpclient import TCPClient
    -from tornado.util import _websocket_mask
    -
    -from typing import (
    -    TYPE_CHECKING,
    -    cast,
    -    Any,
    -    Optional,
    -    Dict,
    -    Union,
    -    List,
    -    Awaitable,
    -    Callable,
    -    Tuple,
    -    Type,
    -)
    -from types import TracebackType
    -
    -if TYPE_CHECKING:
    -    from typing_extensions import Protocol
    -
    -    # The zlib compressor types aren't actually exposed anywhere
    -    # publicly, so declare protocols for the portions we use.
    -    class _Compressor(Protocol):
    -        def compress(self, data: bytes) -> bytes:
    -            pass
    -
    -        def flush(self, mode: int) -> bytes:
    -            pass
    -
    -    class _Decompressor(Protocol):
    -        unconsumed_tail = b""  # type: bytes
    -
    -        def decompress(self, data: bytes, max_length: int) -> bytes:
    -            pass
    -
    -    class _WebSocketDelegate(Protocol):
    -        # The common base interface implemented by WebSocketHandler on
    -        # the server side and WebSocketClientConnection on the client
    -        # side.
    -        def on_ws_connection_close(
    -            self, close_code: Optional[int] = None, close_reason: Optional[str] = None
    -        ) -> None:
    -            pass
    -
    -        def on_message(self, message: Union[str, bytes]) -> Optional["Awaitable[None]"]:
    -            pass
    -
    -        def on_ping(self, data: bytes) -> None:
    -            pass
    -
    -        def on_pong(self, data: bytes) -> None:
    -            pass
    -
    -        def log_exception(
    -            self,
    -            typ: Optional[Type[BaseException]],
    -            value: Optional[BaseException],
    -            tb: Optional[TracebackType],
    -        ) -> None:
    -            pass
    -
    -
    -_default_max_message_size = 10 * 1024 * 1024
    -
    -
    -class WebSocketError(Exception):
    -    pass
    -
    -
    -class WebSocketClosedError(WebSocketError):
    -    """Raised by operations on a closed connection.
    -
    -    .. versionadded:: 3.2
    -    """
    -
    -    pass
    -
    -
    -class _DecompressTooLargeError(Exception):
    -    pass
    -
    -
    -class _WebSocketParams(object):
    -    def __init__(
    -        self,
    -        ping_interval: Optional[float] = None,
    -        ping_timeout: Optional[float] = None,
    -        max_message_size: int = _default_max_message_size,
    -        compression_options: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        self.ping_interval = ping_interval
    -        self.ping_timeout = ping_timeout
    -        self.max_message_size = max_message_size
    -        self.compression_options = compression_options
    -
    -
    -class WebSocketHandler(tornado.web.RequestHandler):
    -    """Subclass this class to create a basic WebSocket handler.
    -
    -    Override `on_message` to handle incoming messages, and use
    -    `write_message` to send messages to the client. You can also
    -    override `open` and `on_close` to handle opened and closed
    -    connections.
    -
    -    Custom upgrade response headers can be sent by overriding
    -    `~tornado.web.RequestHandler.set_default_headers` or
    -    `~tornado.web.RequestHandler.prepare`.
    -
    -    See http://dev.w3.org/html5/websockets/ for details on the
    -    JavaScript interface.  The protocol is specified at
    -    http://tools.ietf.org/html/rfc6455.
    -
    -    Here is an example WebSocket handler that echos back all received messages
    -    back to the client:
    -
    -    .. testcode::
    -
    -      class EchoWebSocket(tornado.websocket.WebSocketHandler):
    -          def open(self):
    -              print("WebSocket opened")
    -
    -          def on_message(self, message):
    -              self.write_message(u"You said: " + message)
    -
    -          def on_close(self):
    -              print("WebSocket closed")
    -
    -    .. testoutput::
    -       :hide:
    -
    -    WebSockets are not standard HTTP connections. The "handshake" is
    -    HTTP, but after the handshake, the protocol is
    -    message-based. Consequently, most of the Tornado HTTP facilities
    -    are not available in handlers of this type. The only communication
    -    methods available to you are `write_message()`, `ping()`, and
    -    `close()`. Likewise, your request handler class should implement
    -    `open()` method rather than ``get()`` or ``post()``.
    -
    -    If you map the handler above to ``/websocket`` in your application, you can
    -    invoke it in JavaScript with::
    -
    -      var ws = new WebSocket("ws://localhost:8888/websocket");
    -      ws.onopen = function() {
    -         ws.send("Hello, world");
    -      };
    -      ws.onmessage = function (evt) {
    -         alert(evt.data);
    -      };
    -
    -    This script pops up an alert box that says "You said: Hello, world".
    -
    -    Web browsers allow any site to open a websocket connection to any other,
    -    instead of using the same-origin policy that governs other network
    -    access from JavaScript.  This can be surprising and is a potential
    -    security hole, so since Tornado 4.0 `WebSocketHandler` requires
    -    applications that wish to receive cross-origin websockets to opt in
    -    by overriding the `~WebSocketHandler.check_origin` method (see that
    -    method's docs for details).  Failure to do so is the most likely
    -    cause of 403 errors when making a websocket connection.
    -
    -    When using a secure websocket connection (``wss://``) with a self-signed
    -    certificate, the connection from a browser may fail because it wants
    -    to show the "accept this certificate" dialog but has nowhere to show it.
    -    You must first visit a regular HTML page using the same certificate
    -    to accept it before the websocket connection will succeed.
    -
    -    If the application setting ``websocket_ping_interval`` has a non-zero
    -    value, a ping will be sent periodically, and the connection will be
    -    closed if a response is not received before the ``websocket_ping_timeout``.
    -
    -    Messages larger than the ``websocket_max_message_size`` application setting
    -    (default 10MiB) will not be accepted.
    -
    -    .. versionchanged:: 4.5
    -       Added ``websocket_ping_interval``, ``websocket_ping_timeout``, and
    -       ``websocket_max_message_size``.
    -    """
    -
    -    def __init__(
    -        self,
    -        application: tornado.web.Application,
    -        request: httputil.HTTPServerRequest,
    -        **kwargs: Any
    -    ) -> None:
    -        super().__init__(application, request, **kwargs)
    -        self.ws_connection = None  # type: Optional[WebSocketProtocol]
    -        self.close_code = None  # type: Optional[int]
    -        self.close_reason = None  # type: Optional[str]
    -        self.stream = None  # type: Optional[IOStream]
    -        self._on_close_called = False
    -
    -    async def get(self, *args: Any, **kwargs: Any) -> None:
    -        self.open_args = args
    -        self.open_kwargs = kwargs
    -
    -        # Upgrade header should be present and should be equal to WebSocket
    -        if self.request.headers.get("Upgrade", "").lower() != "websocket":
    -            self.set_status(400)
    -            log_msg = 'Can "Upgrade" only to "WebSocket".'
    -            self.finish(log_msg)
    -            gen_log.debug(log_msg)
    -            return
    -
    -        # Connection header should be upgrade.
    -        # Some proxy servers/load balancers
    -        # might mess with it.
    -        headers = self.request.headers
    -        connection = map(
    -            lambda s: s.strip().lower(), headers.get("Connection", "").split(",")
    -        )
    -        if "upgrade" not in connection:
    -            self.set_status(400)
    -            log_msg = '"Connection" must be "Upgrade".'
    -            self.finish(log_msg)
    -            gen_log.debug(log_msg)
    -            return
    -
    -        # Handle WebSocket Origin naming convention differences
    -        # The difference between version 8 and 13 is that in 8 the
    -        # client sends a "Sec-Websocket-Origin" header and in 13 it's
    -        # simply "Origin".
    -        if "Origin" in self.request.headers:
    -            origin = self.request.headers.get("Origin")
    -        else:
    -            origin = self.request.headers.get("Sec-Websocket-Origin", None)
    -
    -        # If there was an origin header, check to make sure it matches
    -        # according to check_origin. When the origin is None, we assume it
    -        # did not come from a browser and that it can be passed on.
    -        if origin is not None and not self.check_origin(origin):
    -            self.set_status(403)
    -            log_msg = "Cross origin websockets not allowed"
    -            self.finish(log_msg)
    -            gen_log.debug(log_msg)
    -            return
    -
    -        self.ws_connection = self.get_websocket_protocol()
    -        if self.ws_connection:
    -            await self.ws_connection.accept_connection(self)
    -        else:
    -            self.set_status(426, "Upgrade Required")
    -            self.set_header("Sec-WebSocket-Version", "7, 8, 13")
    -
    -    @property
    -    def ping_interval(self) -> Optional[float]:
    -        """The interval for websocket keep-alive pings.
    -
    -        Set websocket_ping_interval = 0 to disable pings.
    -        """
    -        return self.settings.get("websocket_ping_interval", None)
    -
    -    @property
    -    def ping_timeout(self) -> Optional[float]:
    -        """If no ping is received in this many seconds,
    -        close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).
    -        Default is max of 3 pings or 30 seconds.
    -        """
    -        return self.settings.get("websocket_ping_timeout", None)
    -
    -    @property
    -    def max_message_size(self) -> int:
    -        """Maximum allowed message size.
    -
    -        If the remote peer sends a message larger than this, the connection
    -        will be closed.
    -
    -        Default is 10MiB.
    -        """
    -        return self.settings.get(
    -            "websocket_max_message_size", _default_max_message_size
    -        )
    -
    -    def write_message(
    -        self, message: Union[bytes, str, Dict[str, Any]], binary: bool = False
    -    ) -> "Future[None]":
    -        """Sends the given message to the client of this Web Socket.
    -
    -        The message may be either a string or a dict (which will be
    -        encoded as json).  If the ``binary`` argument is false, the
    -        message will be sent as utf8; in binary mode any byte string
    -        is allowed.
    -
    -        If the connection is already closed, raises `WebSocketClosedError`.
    -        Returns a `.Future` which can be used for flow control.
    -
    -        .. versionchanged:: 3.2
    -           `WebSocketClosedError` was added (previously a closed connection
    -           would raise an `AttributeError`)
    -
    -        .. versionchanged:: 4.3
    -           Returns a `.Future` which can be used for flow control.
    -
    -        .. versionchanged:: 5.0
    -           Consistently raises `WebSocketClosedError`. Previously could
    -           sometimes raise `.StreamClosedError`.
    -        """
    -        if self.ws_connection is None or self.ws_connection.is_closing():
    -            raise WebSocketClosedError()
    -        if isinstance(message, dict):
    -            message = tornado.escape.json_encode(message)
    -        return self.ws_connection.write_message(message, binary=binary)
    -
    -    def select_subprotocol(self, subprotocols: List[str]) -> Optional[str]:
    -        """Override to implement subprotocol negotiation.
    -
    -        ``subprotocols`` is a list of strings identifying the
    -        subprotocols proposed by the client.  This method may be
    -        overridden to return one of those strings to select it, or
    -        ``None`` to not select a subprotocol.
    -
    -        Failure to select a subprotocol does not automatically abort
    -        the connection, although clients may close the connection if
    -        none of their proposed subprotocols was selected.
    -
    -        The list may be empty, in which case this method must return
    -        None. This method is always called exactly once even if no
    -        subprotocols were proposed so that the handler can be advised
    -        of this fact.
    -
    -        .. versionchanged:: 5.1
    -
    -           Previously, this method was called with a list containing
    -           an empty string instead of an empty list if no subprotocols
    -           were proposed by the client.
    -        """
    -        return None
    -
    -    @property
    -    def selected_subprotocol(self) -> Optional[str]:
    -        """The subprotocol returned by `select_subprotocol`.
    -
    -        .. versionadded:: 5.1
    -        """
    -        assert self.ws_connection is not None
    -        return self.ws_connection.selected_subprotocol
    -
    -    def get_compression_options(self) -> Optional[Dict[str, Any]]:
    -        """Override to return compression options for the connection.
    -
    -        If this method returns None (the default), compression will
    -        be disabled.  If it returns a dict (even an empty one), it
    -        will be enabled.  The contents of the dict may be used to
    -        control the following compression options:
    -
    -        ``compression_level`` specifies the compression level.
    -
    -        ``mem_level`` specifies the amount of memory used for the internal compression state.
    -
    -         These parameters are documented in details here:
    -         https://docs.python.org/3.6/library/zlib.html#zlib.compressobj
    -
    -        .. versionadded:: 4.1
    -
    -        .. versionchanged:: 4.5
    -
    -           Added ``compression_level`` and ``mem_level``.
    -        """
    -        # TODO: Add wbits option.
    -        return None
    -
    -    def open(self, *args: str, **kwargs: str) -> Optional[Awaitable[None]]:
    -        """Invoked when a new WebSocket is opened.
    -
    -        The arguments to `open` are extracted from the `tornado.web.URLSpec`
    -        regular expression, just like the arguments to
    -        `tornado.web.RequestHandler.get`.
    -
    -        `open` may be a coroutine. `on_message` will not be called until
    -        `open` has returned.
    -
    -        .. versionchanged:: 5.1
    -
    -           ``open`` may be a coroutine.
    -        """
    -        pass
    -
    -    def on_message(self, message: Union[str, bytes]) -> Optional[Awaitable[None]]:
    -        """Handle incoming messages on the WebSocket
    -
    -        This method must be overridden.
    -
    -        .. versionchanged:: 4.5
    -
    -           ``on_message`` can be a coroutine.
    -        """
    -        raise NotImplementedError
    -
    -    def ping(self, data: Union[str, bytes] = b"") -> None:
    -        """Send ping frame to the remote end.
    -
    -        The data argument allows a small amount of data (up to 125
    -        bytes) to be sent as a part of the ping message. Note that not
    -        all websocket implementations expose this data to
    -        applications.
    -
    -        Consider using the ``websocket_ping_interval`` application
    -        setting instead of sending pings manually.
    -
    -        .. versionchanged:: 5.1
    -
    -           The data argument is now optional.
    -
    -        """
    -        data = utf8(data)
    -        if self.ws_connection is None or self.ws_connection.is_closing():
    -            raise WebSocketClosedError()
    -        self.ws_connection.write_ping(data)
    -
    -    def on_pong(self, data: bytes) -> None:
    -        """Invoked when the response to a ping frame is received."""
    -        pass
    -
    -    def on_ping(self, data: bytes) -> None:
    -        """Invoked when the a ping frame is received."""
    -        pass
    -
    -    def on_close(self) -> None:
    -        """Invoked when the WebSocket is closed.
    -
    -        If the connection was closed cleanly and a status code or reason
    -        phrase was supplied, these values will be available as the attributes
    -        ``self.close_code`` and ``self.close_reason``.
    -
    -        .. versionchanged:: 4.0
    -
    -           Added ``close_code`` and ``close_reason`` attributes.
    -        """
    -        pass
    -
    -    def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> None:
    -        """Closes this Web Socket.
    -
    -        Once the close handshake is successful the socket will be closed.
    -
    -        ``code`` may be a numeric status code, taken from the values
    -        defined in `RFC 6455 section 7.4.1
    -        `_.
    -        ``reason`` may be a textual message about why the connection is
    -        closing.  These values are made available to the client, but are
    -        not otherwise interpreted by the websocket protocol.
    -
    -        .. versionchanged:: 4.0
    -
    -           Added the ``code`` and ``reason`` arguments.
    -        """
    -        if self.ws_connection:
    -            self.ws_connection.close(code, reason)
    -            self.ws_connection = None
    -
    -    def check_origin(self, origin: str) -> bool:
    -        """Override to enable support for allowing alternate origins.
    -
    -        The ``origin`` argument is the value of the ``Origin`` HTTP
    -        header, the url responsible for initiating this request.  This
    -        method is not called for clients that do not send this header;
    -        such requests are always allowed (because all browsers that
    -        implement WebSockets support this header, and non-browser
    -        clients do not have the same cross-site security concerns).
    -
    -        Should return ``True`` to accept the request or ``False`` to
    -        reject it. By default, rejects all requests with an origin on
    -        a host other than this one.
    -
    -        This is a security protection against cross site scripting attacks on
    -        browsers, since WebSockets are allowed to bypass the usual same-origin
    -        policies and don't use CORS headers.
    -
    -        .. warning::
    -
    -           This is an important security measure; don't disable it
    -           without understanding the security implications. In
    -           particular, if your authentication is cookie-based, you
    -           must either restrict the origins allowed by
    -           ``check_origin()`` or implement your own XSRF-like
    -           protection for websocket connections. See `these
    -           `_
    -           `articles
    -           `_
    -           for more.
    -
    -        To accept all cross-origin traffic (which was the default prior to
    -        Tornado 4.0), simply override this method to always return ``True``::
    -
    -            def check_origin(self, origin):
    -                return True
    -
    -        To allow connections from any subdomain of your site, you might
    -        do something like::
    -
    -            def check_origin(self, origin):
    -                parsed_origin = urllib.parse.urlparse(origin)
    -                return parsed_origin.netloc.endswith(".mydomain.com")
    -
    -        .. versionadded:: 4.0
    -
    -        """
    -        parsed_origin = urlparse(origin)
    -        origin = parsed_origin.netloc
    -        origin = origin.lower()
    -
    -        host = self.request.headers.get("Host")
    -
    -        # Check to see that origin matches host directly, including ports
    -        return origin == host
    -
    -    def set_nodelay(self, value: bool) -> None:
    -        """Set the no-delay flag for this stream.
    -
    -        By default, small messages may be delayed and/or combined to minimize
    -        the number of packets sent.  This can sometimes cause 200-500ms delays
    -        due to the interaction between Nagle's algorithm and TCP delayed
    -        ACKs.  To reduce this delay (at the expense of possibly increasing
    -        bandwidth usage), call ``self.set_nodelay(True)`` once the websocket
    -        connection is established.
    -
    -        See `.BaseIOStream.set_nodelay` for additional details.
    -
    -        .. versionadded:: 3.1
    -        """
    -        assert self.ws_connection is not None
    -        self.ws_connection.set_nodelay(value)
    -
    -    def on_connection_close(self) -> None:
    -        if self.ws_connection:
    -            self.ws_connection.on_connection_close()
    -            self.ws_connection = None
    -        if not self._on_close_called:
    -            self._on_close_called = True
    -            self.on_close()
    -            self._break_cycles()
    -
    -    def on_ws_connection_close(
    -        self, close_code: Optional[int] = None, close_reason: Optional[str] = None
    -    ) -> None:
    -        self.close_code = close_code
    -        self.close_reason = close_reason
    -        self.on_connection_close()
    -
    -    def _break_cycles(self) -> None:
    -        # WebSocketHandlers call finish() early, but we don't want to
    -        # break up reference cycles (which makes it impossible to call
    -        # self.render_string) until after we've really closed the
    -        # connection (if it was established in the first place,
    -        # indicated by status code 101).
    -        if self.get_status() != 101 or self._on_close_called:
    -            super()._break_cycles()
    -
    -    def send_error(self, *args: Any, **kwargs: Any) -> None:
    -        if self.stream is None:
    -            super().send_error(*args, **kwargs)
    -        else:
    -            # If we get an uncaught exception during the handshake,
    -            # we have no choice but to abruptly close the connection.
    -            # TODO: for uncaught exceptions after the handshake,
    -            # we can close the connection more gracefully.
    -            self.stream.close()
    -
    -    def get_websocket_protocol(self) -> Optional["WebSocketProtocol"]:
    -        websocket_version = self.request.headers.get("Sec-WebSocket-Version")
    -        if websocket_version in ("7", "8", "13"):
    -            params = _WebSocketParams(
    -                ping_interval=self.ping_interval,
    -                ping_timeout=self.ping_timeout,
    -                max_message_size=self.max_message_size,
    -                compression_options=self.get_compression_options(),
    -            )
    -            return WebSocketProtocol13(self, False, params)
    -        return None
    -
    -    def _detach_stream(self) -> IOStream:
    -        # disable non-WS methods
    -        for method in [
    -            "write",
    -            "redirect",
    -            "set_header",
    -            "set_cookie",
    -            "set_status",
    -            "flush",
    -            "finish",
    -        ]:
    -            setattr(self, method, _raise_not_supported_for_websockets)
    -        return self.detach()
    -
    -
    -def _raise_not_supported_for_websockets(*args: Any, **kwargs: Any) -> None:
    -    raise RuntimeError("Method not supported for Web Sockets")
    -
    -
    -class WebSocketProtocol(abc.ABC):
    -    """Base class for WebSocket protocol versions.
    -    """
    -
    -    def __init__(self, handler: "_WebSocketDelegate") -> None:
    -        self.handler = handler
    -        self.stream = None  # type: Optional[IOStream]
    -        self.client_terminated = False
    -        self.server_terminated = False
    -
    -    def _run_callback(
    -        self, callback: Callable, *args: Any, **kwargs: Any
    -    ) -> "Optional[Future[Any]]":
    -        """Runs the given callback with exception handling.
    -
    -        If the callback is a coroutine, returns its Future. On error, aborts the
    -        websocket connection and returns None.
    -        """
    -        try:
    -            result = callback(*args, **kwargs)
    -        except Exception:
    -            self.handler.log_exception(*sys.exc_info())
    -            self._abort()
    -            return None
    -        else:
    -            if result is not None:
    -                result = gen.convert_yielded(result)
    -                assert self.stream is not None
    -                self.stream.io_loop.add_future(result, lambda f: f.result())
    -            return result
    -
    -    def on_connection_close(self) -> None:
    -        self._abort()
    -
    -    def _abort(self) -> None:
    -        """Instantly aborts the WebSocket connection by closing the socket"""
    -        self.client_terminated = True
    -        self.server_terminated = True
    -        if self.stream is not None:
    -            self.stream.close()  # forcibly tear down the connection
    -        self.close()  # let the subclass cleanup
    -
    -    @abc.abstractmethod
    -    def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> None:
    -        raise NotImplementedError()
    -
    -    @abc.abstractmethod
    -    def is_closing(self) -> bool:
    -        raise NotImplementedError()
    -
    -    @abc.abstractmethod
    -    async def accept_connection(self, handler: WebSocketHandler) -> None:
    -        raise NotImplementedError()
    -
    -    @abc.abstractmethod
    -    def write_message(
    -        self, message: Union[str, bytes], binary: bool = False
    -    ) -> "Future[None]":
    -        raise NotImplementedError()
    -
    -    @property
    -    @abc.abstractmethod
    -    def selected_subprotocol(self) -> Optional[str]:
    -        raise NotImplementedError()
    -
    -    @abc.abstractmethod
    -    def write_ping(self, data: bytes) -> None:
    -        raise NotImplementedError()
    -
    -    # The entry points below are used by WebSocketClientConnection,
    -    # which was introduced after we only supported a single version of
    -    # WebSocketProtocol. The WebSocketProtocol/WebSocketProtocol13
    -    # boundary is currently pretty ad-hoc.
    -    @abc.abstractmethod
    -    def _process_server_headers(
    -        self, key: Union[str, bytes], headers: httputil.HTTPHeaders
    -    ) -> None:
    -        raise NotImplementedError()
    -
    -    @abc.abstractmethod
    -    def start_pinging(self) -> None:
    -        raise NotImplementedError()
    -
    -    @abc.abstractmethod
    -    async def _receive_frame_loop(self) -> None:
    -        raise NotImplementedError()
    -
    -    @abc.abstractmethod
    -    def set_nodelay(self, x: bool) -> None:
    -        raise NotImplementedError()
    -
    -
    -class _PerMessageDeflateCompressor(object):
    -    def __init__(
    -        self,
    -        persistent: bool,
    -        max_wbits: Optional[int],
    -        compression_options: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        if max_wbits is None:
    -            max_wbits = zlib.MAX_WBITS
    -        # There is no symbolic constant for the minimum wbits value.
    -        if not (8 <= max_wbits <= zlib.MAX_WBITS):
    -            raise ValueError(
    -                "Invalid max_wbits value %r; allowed range 8-%d",
    -                max_wbits,
    -                zlib.MAX_WBITS,
    -            )
    -        self._max_wbits = max_wbits
    -
    -        if (
    -            compression_options is None
    -            or "compression_level" not in compression_options
    -        ):
    -            self._compression_level = tornado.web.GZipContentEncoding.GZIP_LEVEL
    -        else:
    -            self._compression_level = compression_options["compression_level"]
    -
    -        if compression_options is None or "mem_level" not in compression_options:
    -            self._mem_level = 8
    -        else:
    -            self._mem_level = compression_options["mem_level"]
    -
    -        if persistent:
    -            self._compressor = self._create_compressor()  # type: Optional[_Compressor]
    -        else:
    -            self._compressor = None
    -
    -    def _create_compressor(self) -> "_Compressor":
    -        return zlib.compressobj(
    -            self._compression_level, zlib.DEFLATED, -self._max_wbits, self._mem_level
    -        )
    -
    -    def compress(self, data: bytes) -> bytes:
    -        compressor = self._compressor or self._create_compressor()
    -        data = compressor.compress(data) + compressor.flush(zlib.Z_SYNC_FLUSH)
    -        assert data.endswith(b"\x00\x00\xff\xff")
    -        return data[:-4]
    -
    -
    -class _PerMessageDeflateDecompressor(object):
    -    def __init__(
    -        self,
    -        persistent: bool,
    -        max_wbits: Optional[int],
    -        max_message_size: int,
    -        compression_options: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        self._max_message_size = max_message_size
    -        if max_wbits is None:
    -            max_wbits = zlib.MAX_WBITS
    -        if not (8 <= max_wbits <= zlib.MAX_WBITS):
    -            raise ValueError(
    -                "Invalid max_wbits value %r; allowed range 8-%d",
    -                max_wbits,
    -                zlib.MAX_WBITS,
    -            )
    -        self._max_wbits = max_wbits
    -        if persistent:
    -            self._decompressor = (
    -                self._create_decompressor()
    -            )  # type: Optional[_Decompressor]
    -        else:
    -            self._decompressor = None
    -
    -    def _create_decompressor(self) -> "_Decompressor":
    -        return zlib.decompressobj(-self._max_wbits)
    -
    -    def decompress(self, data: bytes) -> bytes:
    -        decompressor = self._decompressor or self._create_decompressor()
    -        result = decompressor.decompress(
    -            data + b"\x00\x00\xff\xff", self._max_message_size
    -        )
    -        if decompressor.unconsumed_tail:
    -            raise _DecompressTooLargeError()
    -        return result
    -
    -
    -class WebSocketProtocol13(WebSocketProtocol):
    -    """Implementation of the WebSocket protocol from RFC 6455.
    -
    -    This class supports versions 7 and 8 of the protocol in addition to the
    -    final version 13.
    -    """
    -
    -    # Bit masks for the first byte of a frame.
    -    FIN = 0x80
    -    RSV1 = 0x40
    -    RSV2 = 0x20
    -    RSV3 = 0x10
    -    RSV_MASK = RSV1 | RSV2 | RSV3
    -    OPCODE_MASK = 0x0F
    -
    -    stream = None  # type: IOStream
    -
    -    def __init__(
    -        self,
    -        handler: "_WebSocketDelegate",
    -        mask_outgoing: bool,
    -        params: _WebSocketParams,
    -    ) -> None:
    -        WebSocketProtocol.__init__(self, handler)
    -        self.mask_outgoing = mask_outgoing
    -        self.params = params
    -        self._final_frame = False
    -        self._frame_opcode = None
    -        self._masked_frame = None
    -        self._frame_mask = None  # type: Optional[bytes]
    -        self._frame_length = None
    -        self._fragmented_message_buffer = None  # type: Optional[bytes]
    -        self._fragmented_message_opcode = None
    -        self._waiting = None  # type: object
    -        self._compression_options = params.compression_options
    -        self._decompressor = None  # type: Optional[_PerMessageDeflateDecompressor]
    -        self._compressor = None  # type: Optional[_PerMessageDeflateCompressor]
    -        self._frame_compressed = None  # type: Optional[bool]
    -        # The total uncompressed size of all messages received or sent.
    -        # Unicode messages are encoded to utf8.
    -        # Only for testing; subject to change.
    -        self._message_bytes_in = 0
    -        self._message_bytes_out = 0
    -        # The total size of all packets received or sent.  Includes
    -        # the effect of compression, frame overhead, and control frames.
    -        self._wire_bytes_in = 0
    -        self._wire_bytes_out = 0
    -        self.ping_callback = None  # type: Optional[PeriodicCallback]
    -        self.last_ping = 0.0
    -        self.last_pong = 0.0
    -        self.close_code = None  # type: Optional[int]
    -        self.close_reason = None  # type: Optional[str]
    -
    -    # Use a property for this to satisfy the abc.
    -    @property
    -    def selected_subprotocol(self) -> Optional[str]:
    -        return self._selected_subprotocol
    -
    -    @selected_subprotocol.setter
    -    def selected_subprotocol(self, value: Optional[str]) -> None:
    -        self._selected_subprotocol = value
    -
    -    async def accept_connection(self, handler: WebSocketHandler) -> None:
    -        try:
    -            self._handle_websocket_headers(handler)
    -        except ValueError:
    -            handler.set_status(400)
    -            log_msg = "Missing/Invalid WebSocket headers"
    -            handler.finish(log_msg)
    -            gen_log.debug(log_msg)
    -            return
    -
    -        try:
    -            await self._accept_connection(handler)
    -        except asyncio.CancelledError:
    -            self._abort()
    -            return
    -        except ValueError:
    -            gen_log.debug("Malformed WebSocket request received", exc_info=True)
    -            self._abort()
    -            return
    -
    -    def _handle_websocket_headers(self, handler: WebSocketHandler) -> None:
    -        """Verifies all invariant- and required headers
    -
    -        If a header is missing or have an incorrect value ValueError will be
    -        raised
    -        """
    -        fields = ("Host", "Sec-Websocket-Key", "Sec-Websocket-Version")
    -        if not all(map(lambda f: handler.request.headers.get(f), fields)):
    -            raise ValueError("Missing/Invalid WebSocket headers")
    -
    -    @staticmethod
    -    def compute_accept_value(key: Union[str, bytes]) -> str:
    -        """Computes the value for the Sec-WebSocket-Accept header,
    -        given the value for Sec-WebSocket-Key.
    -        """
    -        sha1 = hashlib.sha1()
    -        sha1.update(utf8(key))
    -        sha1.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")  # Magic value
    -        return native_str(base64.b64encode(sha1.digest()))
    -
    -    def _challenge_response(self, handler: WebSocketHandler) -> str:
    -        return WebSocketProtocol13.compute_accept_value(
    -            cast(str, handler.request.headers.get("Sec-Websocket-Key"))
    -        )
    -
    -    async def _accept_connection(self, handler: WebSocketHandler) -> None:
    -        subprotocol_header = handler.request.headers.get("Sec-WebSocket-Protocol")
    -        if subprotocol_header:
    -            subprotocols = [s.strip() for s in subprotocol_header.split(",")]
    -        else:
    -            subprotocols = []
    -        self.selected_subprotocol = handler.select_subprotocol(subprotocols)
    -        if self.selected_subprotocol:
    -            assert self.selected_subprotocol in subprotocols
    -            handler.set_header("Sec-WebSocket-Protocol", self.selected_subprotocol)
    -
    -        extensions = self._parse_extensions_header(handler.request.headers)
    -        for ext in extensions:
    -            if ext[0] == "permessage-deflate" and self._compression_options is not None:
    -                # TODO: negotiate parameters if compression_options
    -                # specifies limits.
    -                self._create_compressors("server", ext[1], self._compression_options)
    -                if (
    -                    "client_max_window_bits" in ext[1]
    -                    and ext[1]["client_max_window_bits"] is None
    -                ):
    -                    # Don't echo an offered client_max_window_bits
    -                    # parameter with no value.
    -                    del ext[1]["client_max_window_bits"]
    -                handler.set_header(
    -                    "Sec-WebSocket-Extensions",
    -                    httputil._encode_header("permessage-deflate", ext[1]),
    -                )
    -                break
    -
    -        handler.clear_header("Content-Type")
    -        handler.set_status(101)
    -        handler.set_header("Upgrade", "websocket")
    -        handler.set_header("Connection", "Upgrade")
    -        handler.set_header("Sec-WebSocket-Accept", self._challenge_response(handler))
    -        handler.finish()
    -
    -        self.stream = handler._detach_stream()
    -
    -        self.start_pinging()
    -        try:
    -            open_result = handler.open(*handler.open_args, **handler.open_kwargs)
    -            if open_result is not None:
    -                await open_result
    -        except Exception:
    -            handler.log_exception(*sys.exc_info())
    -            self._abort()
    -            return
    -
    -        await self._receive_frame_loop()
    -
    -    def _parse_extensions_header(
    -        self, headers: httputil.HTTPHeaders
    -    ) -> List[Tuple[str, Dict[str, str]]]:
    -        extensions = headers.get("Sec-WebSocket-Extensions", "")
    -        if extensions:
    -            return [httputil._parse_header(e.strip()) for e in extensions.split(",")]
    -        return []
    -
    -    def _process_server_headers(
    -        self, key: Union[str, bytes], headers: httputil.HTTPHeaders
    -    ) -> None:
    -        """Process the headers sent by the server to this client connection.
    -
    -        'key' is the websocket handshake challenge/response key.
    -        """
    -        assert headers["Upgrade"].lower() == "websocket"
    -        assert headers["Connection"].lower() == "upgrade"
    -        accept = self.compute_accept_value(key)
    -        assert headers["Sec-Websocket-Accept"] == accept
    -
    -        extensions = self._parse_extensions_header(headers)
    -        for ext in extensions:
    -            if ext[0] == "permessage-deflate" and self._compression_options is not None:
    -                self._create_compressors("client", ext[1])
    -            else:
    -                raise ValueError("unsupported extension %r", ext)
    -
    -        self.selected_subprotocol = headers.get("Sec-WebSocket-Protocol", None)
    -
    -    def _get_compressor_options(
    -        self,
    -        side: str,
    -        agreed_parameters: Dict[str, Any],
    -        compression_options: Optional[Dict[str, Any]] = None,
    -    ) -> Dict[str, Any]:
    -        """Converts a websocket agreed_parameters set to keyword arguments
    -        for our compressor objects.
    -        """
    -        options = dict(
    -            persistent=(side + "_no_context_takeover") not in agreed_parameters
    -        )  # type: Dict[str, Any]
    -        wbits_header = agreed_parameters.get(side + "_max_window_bits", None)
    -        if wbits_header is None:
    -            options["max_wbits"] = zlib.MAX_WBITS
    -        else:
    -            options["max_wbits"] = int(wbits_header)
    -        options["compression_options"] = compression_options
    -        return options
    -
    -    def _create_compressors(
    -        self,
    -        side: str,
    -        agreed_parameters: Dict[str, Any],
    -        compression_options: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        # TODO: handle invalid parameters gracefully
    -        allowed_keys = set(
    -            [
    -                "server_no_context_takeover",
    -                "client_no_context_takeover",
    -                "server_max_window_bits",
    -                "client_max_window_bits",
    -            ]
    -        )
    -        for key in agreed_parameters:
    -            if key not in allowed_keys:
    -                raise ValueError("unsupported compression parameter %r" % key)
    -        other_side = "client" if (side == "server") else "server"
    -        self._compressor = _PerMessageDeflateCompressor(
    -            **self._get_compressor_options(side, agreed_parameters, compression_options)
    -        )
    -        self._decompressor = _PerMessageDeflateDecompressor(
    -            max_message_size=self.params.max_message_size,
    -            **self._get_compressor_options(
    -                other_side, agreed_parameters, compression_options
    -            )
    -        )
    -
    -    def _write_frame(
    -        self, fin: bool, opcode: int, data: bytes, flags: int = 0
    -    ) -> "Future[None]":
    -        data_len = len(data)
    -        if opcode & 0x8:
    -            # All control frames MUST have a payload length of 125
    -            # bytes or less and MUST NOT be fragmented.
    -            if not fin:
    -                raise ValueError("control frames may not be fragmented")
    -            if data_len > 125:
    -                raise ValueError("control frame payloads may not exceed 125 bytes")
    -        if fin:
    -            finbit = self.FIN
    -        else:
    -            finbit = 0
    -        frame = struct.pack("B", finbit | opcode | flags)
    -        if self.mask_outgoing:
    -            mask_bit = 0x80
    -        else:
    -            mask_bit = 0
    -        if data_len < 126:
    -            frame += struct.pack("B", data_len | mask_bit)
    -        elif data_len <= 0xFFFF:
    -            frame += struct.pack("!BH", 126 | mask_bit, data_len)
    -        else:
    -            frame += struct.pack("!BQ", 127 | mask_bit, data_len)
    -        if self.mask_outgoing:
    -            mask = os.urandom(4)
    -            data = mask + _websocket_mask(mask, data)
    -        frame += data
    -        self._wire_bytes_out += len(frame)
    -        return self.stream.write(frame)
    -
    -    def write_message(
    -        self, message: Union[str, bytes], binary: bool = False
    -    ) -> "Future[None]":
    -        """Sends the given message to the client of this Web Socket."""
    -        if binary:
    -            opcode = 0x2
    -        else:
    -            opcode = 0x1
    -        message = tornado.escape.utf8(message)
    -        assert isinstance(message, bytes)
    -        self._message_bytes_out += len(message)
    -        flags = 0
    -        if self._compressor:
    -            message = self._compressor.compress(message)
    -            flags |= self.RSV1
    -        # For historical reasons, write methods in Tornado operate in a semi-synchronous
    -        # mode in which awaiting the Future they return is optional (But errors can
    -        # still be raised). This requires us to go through an awkward dance here
    -        # to transform the errors that may be returned while presenting the same
    -        # semi-synchronous interface.
    -        try:
    -            fut = self._write_frame(True, opcode, message, flags=flags)
    -        except StreamClosedError:
    -            raise WebSocketClosedError()
    -
    -        async def wrapper() -> None:
    -            try:
    -                await fut
    -            except StreamClosedError:
    -                raise WebSocketClosedError()
    -
    -        return asyncio.ensure_future(wrapper())
    -
    -    def write_ping(self, data: bytes) -> None:
    -        """Send ping frame."""
    -        assert isinstance(data, bytes)
    -        self._write_frame(True, 0x9, data)
    -
    -    async def _receive_frame_loop(self) -> None:
    -        try:
    -            while not self.client_terminated:
    -                await self._receive_frame()
    -        except StreamClosedError:
    -            self._abort()
    -        self.handler.on_ws_connection_close(self.close_code, self.close_reason)
    -
    -    async def _read_bytes(self, n: int) -> bytes:
    -        data = await self.stream.read_bytes(n)
    -        self._wire_bytes_in += n
    -        return data
    -
    -    async def _receive_frame(self) -> None:
    -        # Read the frame header.
    -        data = await self._read_bytes(2)
    -        header, mask_payloadlen = struct.unpack("BB", data)
    -        is_final_frame = header & self.FIN
    -        reserved_bits = header & self.RSV_MASK
    -        opcode = header & self.OPCODE_MASK
    -        opcode_is_control = opcode & 0x8
    -        if self._decompressor is not None and opcode != 0:
    -            # Compression flag is present in the first frame's header,
    -            # but we can't decompress until we have all the frames of
    -            # the message.
    -            self._frame_compressed = bool(reserved_bits & self.RSV1)
    -            reserved_bits &= ~self.RSV1
    -        if reserved_bits:
    -            # client is using as-yet-undefined extensions; abort
    -            self._abort()
    -            return
    -        is_masked = bool(mask_payloadlen & 0x80)
    -        payloadlen = mask_payloadlen & 0x7F
    -
    -        # Parse and validate the length.
    -        if opcode_is_control and payloadlen >= 126:
    -            # control frames must have payload < 126
    -            self._abort()
    -            return
    -        if payloadlen < 126:
    -            self._frame_length = payloadlen
    -        elif payloadlen == 126:
    -            data = await self._read_bytes(2)
    -            payloadlen = struct.unpack("!H", data)[0]
    -        elif payloadlen == 127:
    -            data = await self._read_bytes(8)
    -            payloadlen = struct.unpack("!Q", data)[0]
    -        new_len = payloadlen
    -        if self._fragmented_message_buffer is not None:
    -            new_len += len(self._fragmented_message_buffer)
    -        if new_len > self.params.max_message_size:
    -            self.close(1009, "message too big")
    -            self._abort()
    -            return
    -
    -        # Read the payload, unmasking if necessary.
    -        if is_masked:
    -            self._frame_mask = await self._read_bytes(4)
    -        data = await self._read_bytes(payloadlen)
    -        if is_masked:
    -            assert self._frame_mask is not None
    -            data = _websocket_mask(self._frame_mask, data)
    -
    -        # Decide what to do with this frame.
    -        if opcode_is_control:
    -            # control frames may be interleaved with a series of fragmented
    -            # data frames, so control frames must not interact with
    -            # self._fragmented_*
    -            if not is_final_frame:
    -                # control frames must not be fragmented
    -                self._abort()
    -                return
    -        elif opcode == 0:  # continuation frame
    -            if self._fragmented_message_buffer is None:
    -                # nothing to continue
    -                self._abort()
    -                return
    -            self._fragmented_message_buffer += data
    -            if is_final_frame:
    -                opcode = self._fragmented_message_opcode
    -                data = self._fragmented_message_buffer
    -                self._fragmented_message_buffer = None
    -        else:  # start of new data message
    -            if self._fragmented_message_buffer is not None:
    -                # can't start new message until the old one is finished
    -                self._abort()
    -                return
    -            if not is_final_frame:
    -                self._fragmented_message_opcode = opcode
    -                self._fragmented_message_buffer = data
    -
    -        if is_final_frame:
    -            handled_future = self._handle_message(opcode, data)
    -            if handled_future is not None:
    -                await handled_future
    -
    -    def _handle_message(self, opcode: int, data: bytes) -> "Optional[Future[None]]":
    -        """Execute on_message, returning its Future if it is a coroutine."""
    -        if self.client_terminated:
    -            return None
    -
    -        if self._frame_compressed:
    -            assert self._decompressor is not None
    -            try:
    -                data = self._decompressor.decompress(data)
    -            except _DecompressTooLargeError:
    -                self.close(1009, "message too big after decompression")
    -                self._abort()
    -                return None
    -
    -        if opcode == 0x1:
    -            # UTF-8 data
    -            self._message_bytes_in += len(data)
    -            try:
    -                decoded = data.decode("utf-8")
    -            except UnicodeDecodeError:
    -                self._abort()
    -                return None
    -            return self._run_callback(self.handler.on_message, decoded)
    -        elif opcode == 0x2:
    -            # Binary data
    -            self._message_bytes_in += len(data)
    -            return self._run_callback(self.handler.on_message, data)
    -        elif opcode == 0x8:
    -            # Close
    -            self.client_terminated = True
    -            if len(data) >= 2:
    -                self.close_code = struct.unpack(">H", data[:2])[0]
    -            if len(data) > 2:
    -                self.close_reason = to_unicode(data[2:])
    -            # Echo the received close code, if any (RFC 6455 section 5.5.1).
    -            self.close(self.close_code)
    -        elif opcode == 0x9:
    -            # Ping
    -            try:
    -                self._write_frame(True, 0xA, data)
    -            except StreamClosedError:
    -                self._abort()
    -            self._run_callback(self.handler.on_ping, data)
    -        elif opcode == 0xA:
    -            # Pong
    -            self.last_pong = IOLoop.current().time()
    -            return self._run_callback(self.handler.on_pong, data)
    -        else:
    -            self._abort()
    -        return None
    -
    -    def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> None:
    -        """Closes the WebSocket connection."""
    -        if not self.server_terminated:
    -            if not self.stream.closed():
    -                if code is None and reason is not None:
    -                    code = 1000  # "normal closure" status code
    -                if code is None:
    -                    close_data = b""
    -                else:
    -                    close_data = struct.pack(">H", code)
    -                if reason is not None:
    -                    close_data += utf8(reason)
    -                try:
    -                    self._write_frame(True, 0x8, close_data)
    -                except StreamClosedError:
    -                    self._abort()
    -            self.server_terminated = True
    -        if self.client_terminated:
    -            if self._waiting is not None:
    -                self.stream.io_loop.remove_timeout(self._waiting)
    -                self._waiting = None
    -            self.stream.close()
    -        elif self._waiting is None:
    -            # Give the client a few seconds to complete a clean shutdown,
    -            # otherwise just close the connection.
    -            self._waiting = self.stream.io_loop.add_timeout(
    -                self.stream.io_loop.time() + 5, self._abort
    -            )
    -        if self.ping_callback:
    -            self.ping_callback.stop()
    -            self.ping_callback = None
    -
    -    def is_closing(self) -> bool:
    -        """Return ``True`` if this connection is closing.
    -
    -        The connection is considered closing if either side has
    -        initiated its closing handshake or if the stream has been
    -        shut down uncleanly.
    -        """
    -        return self.stream.closed() or self.client_terminated or self.server_terminated
    -
    -    @property
    -    def ping_interval(self) -> Optional[float]:
    -        interval = self.params.ping_interval
    -        if interval is not None:
    -            return interval
    -        return 0
    -
    -    @property
    -    def ping_timeout(self) -> Optional[float]:
    -        timeout = self.params.ping_timeout
    -        if timeout is not None:
    -            return timeout
    -        assert self.ping_interval is not None
    -        return max(3 * self.ping_interval, 30)
    -
    -    def start_pinging(self) -> None:
    -        """Start sending periodic pings to keep the connection alive"""
    -        assert self.ping_interval is not None
    -        if self.ping_interval > 0:
    -            self.last_ping = self.last_pong = IOLoop.current().time()
    -            self.ping_callback = PeriodicCallback(
    -                self.periodic_ping, self.ping_interval * 1000
    -            )
    -            self.ping_callback.start()
    -
    -    def periodic_ping(self) -> None:
    -        """Send a ping to keep the websocket alive
    -
    -        Called periodically if the websocket_ping_interval is set and non-zero.
    -        """
    -        if self.is_closing() and self.ping_callback is not None:
    -            self.ping_callback.stop()
    -            return
    -
    -        # Check for timeout on pong. Make sure that we really have
    -        # sent a recent ping in case the machine with both server and
    -        # client has been suspended since the last ping.
    -        now = IOLoop.current().time()
    -        since_last_pong = now - self.last_pong
    -        since_last_ping = now - self.last_ping
    -        assert self.ping_interval is not None
    -        assert self.ping_timeout is not None
    -        if (
    -            since_last_ping < 2 * self.ping_interval
    -            and since_last_pong > self.ping_timeout
    -        ):
    -            self.close()
    -            return
    -
    -        self.write_ping(b"")
    -        self.last_ping = now
    -
    -    def set_nodelay(self, x: bool) -> None:
    -        self.stream.set_nodelay(x)
    -
    -
    -class WebSocketClientConnection(simple_httpclient._HTTPConnection):
    -    """WebSocket client connection.
    -
    -    This class should not be instantiated directly; use the
    -    `websocket_connect` function instead.
    -    """
    -
    -    protocol = None  # type: WebSocketProtocol
    -
    -    def __init__(
    -        self,
    -        request: httpclient.HTTPRequest,
    -        on_message_callback: Optional[Callable[[Union[None, str, bytes]], None]] = None,
    -        compression_options: Optional[Dict[str, Any]] = None,
    -        ping_interval: Optional[float] = None,
    -        ping_timeout: Optional[float] = None,
    -        max_message_size: int = _default_max_message_size,
    -        subprotocols: Optional[List[str]] = [],
    -    ) -> None:
    -        self.connect_future = Future()  # type: Future[WebSocketClientConnection]
    -        self.read_queue = Queue(1)  # type: Queue[Union[None, str, bytes]]
    -        self.key = base64.b64encode(os.urandom(16))
    -        self._on_message_callback = on_message_callback
    -        self.close_code = None  # type: Optional[int]
    -        self.close_reason = None  # type: Optional[str]
    -        self.params = _WebSocketParams(
    -            ping_interval=ping_interval,
    -            ping_timeout=ping_timeout,
    -            max_message_size=max_message_size,
    -            compression_options=compression_options,
    -        )
    -
    -        scheme, sep, rest = request.url.partition(":")
    -        scheme = {"ws": "http", "wss": "https"}[scheme]
    -        request.url = scheme + sep + rest
    -        request.headers.update(
    -            {
    -                "Upgrade": "websocket",
    -                "Connection": "Upgrade",
    -                "Sec-WebSocket-Key": self.key,
    -                "Sec-WebSocket-Version": "13",
    -            }
    -        )
    -        if subprotocols is not None:
    -            request.headers["Sec-WebSocket-Protocol"] = ",".join(subprotocols)
    -        if compression_options is not None:
    -            # Always offer to let the server set our max_wbits (and even though
    -            # we don't offer it, we will accept a client_no_context_takeover
    -            # from the server).
    -            # TODO: set server parameters for deflate extension
    -            # if requested in self.compression_options.
    -            request.headers[
    -                "Sec-WebSocket-Extensions"
    -            ] = "permessage-deflate; client_max_window_bits"
    -
    -        # Websocket connection is currently unable to follow redirects
    -        request.follow_redirects = False
    -
    -        self.tcp_client = TCPClient()
    -        super().__init__(
    -            None,
    -            request,
    -            lambda: None,
    -            self._on_http_response,
    -            104857600,
    -            self.tcp_client,
    -            65536,
    -            104857600,
    -        )
    -
    -    def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> None:
    -        """Closes the websocket connection.
    -
    -        ``code`` and ``reason`` are documented under
    -        `WebSocketHandler.close`.
    -
    -        .. versionadded:: 3.2
    -
    -        .. versionchanged:: 4.0
    -
    -           Added the ``code`` and ``reason`` arguments.
    -        """
    -        if self.protocol is not None:
    -            self.protocol.close(code, reason)
    -            self.protocol = None  # type: ignore
    -
    -    def on_connection_close(self) -> None:
    -        if not self.connect_future.done():
    -            self.connect_future.set_exception(StreamClosedError())
    -        self._on_message(None)
    -        self.tcp_client.close()
    -        super().on_connection_close()
    -
    -    def on_ws_connection_close(
    -        self, close_code: Optional[int] = None, close_reason: Optional[str] = None
    -    ) -> None:
    -        self.close_code = close_code
    -        self.close_reason = close_reason
    -        self.on_connection_close()
    -
    -    def _on_http_response(self, response: httpclient.HTTPResponse) -> None:
    -        if not self.connect_future.done():
    -            if response.error:
    -                self.connect_future.set_exception(response.error)
    -            else:
    -                self.connect_future.set_exception(
    -                    WebSocketError("Non-websocket response")
    -                )
    -
    -    async def headers_received(
    -        self,
    -        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
    -        headers: httputil.HTTPHeaders,
    -    ) -> None:
    -        assert isinstance(start_line, httputil.ResponseStartLine)
    -        if start_line.code != 101:
    -            await super().headers_received(start_line, headers)
    -            return
    -
    -        if self._timeout is not None:
    -            self.io_loop.remove_timeout(self._timeout)
    -            self._timeout = None
    -
    -        self.headers = headers
    -        self.protocol = self.get_websocket_protocol()
    -        self.protocol._process_server_headers(self.key, self.headers)
    -        self.protocol.stream = self.connection.detach()
    -
    -        IOLoop.current().add_callback(self.protocol._receive_frame_loop)
    -        self.protocol.start_pinging()
    -
    -        # Once we've taken over the connection, clear the final callback
    -        # we set on the http request.  This deactivates the error handling
    -        # in simple_httpclient that would otherwise interfere with our
    -        # ability to see exceptions.
    -        self.final_callback = None  # type: ignore
    -
    -        future_set_result_unless_cancelled(self.connect_future, self)
    -
    -    def write_message(
    -        self, message: Union[str, bytes], binary: bool = False
    -    ) -> "Future[None]":
    -        """Sends a message to the WebSocket server.
    -
    -        If the stream is closed, raises `WebSocketClosedError`.
    -        Returns a `.Future` which can be used for flow control.
    -
    -        .. versionchanged:: 5.0
    -           Exception raised on a closed stream changed from `.StreamClosedError`
    -           to `WebSocketClosedError`.
    -        """
    -        return self.protocol.write_message(message, binary=binary)
    -
    -    def read_message(
    -        self,
    -        callback: Optional[Callable[["Future[Union[None, str, bytes]]"], None]] = None,
    -    ) -> Awaitable[Union[None, str, bytes]]:
    -        """Reads a message from the WebSocket server.
    -
    -        If on_message_callback was specified at WebSocket
    -        initialization, this function will never return messages
    -
    -        Returns a future whose result is the message, or None
    -        if the connection is closed.  If a callback argument
    -        is given it will be called with the future when it is
    -        ready.
    -        """
    -
    -        awaitable = self.read_queue.get()
    -        if callback is not None:
    -            self.io_loop.add_future(asyncio.ensure_future(awaitable), callback)
    -        return awaitable
    -
    -    def on_message(self, message: Union[str, bytes]) -> Optional[Awaitable[None]]:
    -        return self._on_message(message)
    -
    -    def _on_message(
    -        self, message: Union[None, str, bytes]
    -    ) -> Optional[Awaitable[None]]:
    -        if self._on_message_callback:
    -            self._on_message_callback(message)
    -            return None
    -        else:
    -            return self.read_queue.put(message)
    -
    -    def ping(self, data: bytes = b"") -> None:
    -        """Send ping frame to the remote end.
    -
    -        The data argument allows a small amount of data (up to 125
    -        bytes) to be sent as a part of the ping message. Note that not
    -        all websocket implementations expose this data to
    -        applications.
    -
    -        Consider using the ``ping_interval`` argument to
    -        `websocket_connect` instead of sending pings manually.
    -
    -        .. versionadded:: 5.1
    -
    -        """
    -        data = utf8(data)
    -        if self.protocol is None:
    -            raise WebSocketClosedError()
    -        self.protocol.write_ping(data)
    -
    -    def on_pong(self, data: bytes) -> None:
    -        pass
    -
    -    def on_ping(self, data: bytes) -> None:
    -        pass
    -
    -    def get_websocket_protocol(self) -> WebSocketProtocol:
    -        return WebSocketProtocol13(self, mask_outgoing=True, params=self.params)
    -
    -    @property
    -    def selected_subprotocol(self) -> Optional[str]:
    -        """The subprotocol selected by the server.
    -
    -        .. versionadded:: 5.1
    -        """
    -        return self.protocol.selected_subprotocol
    -
    -    def log_exception(
    -        self,
    -        typ: "Optional[Type[BaseException]]",
    -        value: Optional[BaseException],
    -        tb: Optional[TracebackType],
    -    ) -> None:
    -        assert typ is not None
    -        assert value is not None
    -        app_log.error("Uncaught exception %s", value, exc_info=(typ, value, tb))
    -
    -
    -def websocket_connect(
    -    url: Union[str, httpclient.HTTPRequest],
    -    callback: Optional[Callable[["Future[WebSocketClientConnection]"], None]] = None,
    -    connect_timeout: Optional[float] = None,
    -    on_message_callback: Optional[Callable[[Union[None, str, bytes]], None]] = None,
    -    compression_options: Optional[Dict[str, Any]] = None,
    -    ping_interval: Optional[float] = None,
    -    ping_timeout: Optional[float] = None,
    -    max_message_size: int = _default_max_message_size,
    -    subprotocols: Optional[List[str]] = None,
    -) -> "Awaitable[WebSocketClientConnection]":
    -    """Client-side websocket support.
    -
    -    Takes a url and returns a Future whose result is a
    -    `WebSocketClientConnection`.
    -
    -    ``compression_options`` is interpreted in the same way as the
    -    return value of `.WebSocketHandler.get_compression_options`.
    -
    -    The connection supports two styles of operation. In the coroutine
    -    style, the application typically calls
    -    `~.WebSocketClientConnection.read_message` in a loop::
    -
    -        conn = yield websocket_connect(url)
    -        while True:
    -            msg = yield conn.read_message()
    -            if msg is None: break
    -            # Do something with msg
    -
    -    In the callback style, pass an ``on_message_callback`` to
    -    ``websocket_connect``. In both styles, a message of ``None``
    -    indicates that the connection has been closed.
    -
    -    ``subprotocols`` may be a list of strings specifying proposed
    -    subprotocols. The selected protocol may be found on the
    -    ``selected_subprotocol`` attribute of the connection object
    -    when the connection is complete.
    -
    -    .. versionchanged:: 3.2
    -       Also accepts ``HTTPRequest`` objects in place of urls.
    -
    -    .. versionchanged:: 4.1
    -       Added ``compression_options`` and ``on_message_callback``.
    -
    -    .. versionchanged:: 4.5
    -       Added the ``ping_interval``, ``ping_timeout``, and ``max_message_size``
    -       arguments, which have the same meaning as in `WebSocketHandler`.
    -
    -    .. versionchanged:: 5.0
    -       The ``io_loop`` argument (deprecated since version 4.1) has been removed.
    -
    -    .. versionchanged:: 5.1
    -       Added the ``subprotocols`` argument.
    -    """
    -    if isinstance(url, httpclient.HTTPRequest):
    -        assert connect_timeout is None
    -        request = url
    -        # Copy and convert the headers dict/object (see comments in
    -        # AsyncHTTPClient.fetch)
    -        request.headers = httputil.HTTPHeaders(request.headers)
    -    else:
    -        request = httpclient.HTTPRequest(url, connect_timeout=connect_timeout)
    -    request = cast(
    -        httpclient.HTTPRequest,
    -        httpclient._RequestProxy(request, httpclient.HTTPRequest._DEFAULTS),
    -    )
    -    conn = WebSocketClientConnection(
    -        request,
    -        on_message_callback=on_message_callback,
    -        compression_options=compression_options,
    -        ping_interval=ping_interval,
    -        ping_timeout=ping_timeout,
    -        max_message_size=max_message_size,
    -        subprotocols=subprotocols,
    -    )
    -    if callback is not None:
    -        IOLoop.current().add_future(conn.connect_future, callback)
    -    return conn.connect_future
    diff --git a/telegramer/include/tornado/wsgi.py b/telegramer/include/tornado/wsgi.py
    deleted file mode 100644
    index 77124aa..0000000
    --- a/telegramer/include/tornado/wsgi.py
    +++ /dev/null
    @@ -1,199 +0,0 @@
    -#
    -# Copyright 2009 Facebook
    -#
    -# Licensed under the Apache License, Version 2.0 (the "License"); you may
    -# not use this file except in compliance with the License. You may obtain
    -# a copy of the License at
    -#
    -#     http://www.apache.org/licenses/LICENSE-2.0
    -#
    -# Unless required by applicable law or agreed to in writing, software
    -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    -# License for the specific language governing permissions and limitations
    -# under the License.
    -
    -"""WSGI support for the Tornado web framework.
    -
    -WSGI is the Python standard for web servers, and allows for interoperability
    -between Tornado and other Python web frameworks and servers.
    -
    -This module provides WSGI support via the `WSGIContainer` class, which
    -makes it possible to run applications using other WSGI frameworks on
    -the Tornado HTTP server. The reverse is not supported; the Tornado
    -`.Application` and `.RequestHandler` classes are designed for use with
    -the Tornado `.HTTPServer` and cannot be used in a generic WSGI
    -container.
    -
    -"""
    -
    -import sys
    -from io import BytesIO
    -import tornado
    -
    -from tornado import escape
    -from tornado import httputil
    -from tornado.log import access_log
    -
    -from typing import List, Tuple, Optional, Callable, Any, Dict, Text
    -from types import TracebackType
    -import typing
    -
    -if typing.TYPE_CHECKING:
    -    from typing import Type  # noqa: F401
    -    from wsgiref.types import WSGIApplication as WSGIAppType  # noqa: F401
    -
    -
    -# PEP 3333 specifies that WSGI on python 3 generally deals with byte strings
    -# that are smuggled inside objects of type unicode (via the latin1 encoding).
    -# This function is like those in the tornado.escape module, but defined
    -# here to minimize the temptation to use it in non-wsgi contexts.
    -def to_wsgi_str(s: bytes) -> str:
    -    assert isinstance(s, bytes)
    -    return s.decode("latin1")
    -
    -
    -class WSGIContainer(object):
    -    r"""Makes a WSGI-compatible function runnable on Tornado's HTTP server.
    -
    -    .. warning::
    -
    -       WSGI is a *synchronous* interface, while Tornado's concurrency model
    -       is based on single-threaded asynchronous execution.  This means that
    -       running a WSGI app with Tornado's `WSGIContainer` is *less scalable*
    -       than running the same app in a multi-threaded WSGI server like
    -       ``gunicorn`` or ``uwsgi``.  Use `WSGIContainer` only when there are
    -       benefits to combining Tornado and WSGI in the same process that
    -       outweigh the reduced scalability.
    -
    -    Wrap a WSGI function in a `WSGIContainer` and pass it to `.HTTPServer` to
    -    run it. For example::
    -
    -        def simple_app(environ, start_response):
    -            status = "200 OK"
    -            response_headers = [("Content-type", "text/plain")]
    -            start_response(status, response_headers)
    -            return ["Hello world!\n"]
    -
    -        container = tornado.wsgi.WSGIContainer(simple_app)
    -        http_server = tornado.httpserver.HTTPServer(container)
    -        http_server.listen(8888)
    -        tornado.ioloop.IOLoop.current().start()
    -
    -    This class is intended to let other frameworks (Django, web.py, etc)
    -    run on the Tornado HTTP server and I/O loop.
    -
    -    The `tornado.web.FallbackHandler` class is often useful for mixing
    -    Tornado and WSGI apps in the same server.  See
    -    https://github.com/bdarnell/django-tornado-demo for a complete example.
    -    """
    -
    -    def __init__(self, wsgi_application: "WSGIAppType") -> None:
    -        self.wsgi_application = wsgi_application
    -
    -    def __call__(self, request: httputil.HTTPServerRequest) -> None:
    -        data = {}  # type: Dict[str, Any]
    -        response = []  # type: List[bytes]
    -
    -        def start_response(
    -            status: str,
    -            headers: List[Tuple[str, str]],
    -            exc_info: Optional[
    -                Tuple[
    -                    "Optional[Type[BaseException]]",
    -                    Optional[BaseException],
    -                    Optional[TracebackType],
    -                ]
    -            ] = None,
    -        ) -> Callable[[bytes], Any]:
    -            data["status"] = status
    -            data["headers"] = headers
    -            return response.append
    -
    -        app_response = self.wsgi_application(
    -            WSGIContainer.environ(request), start_response
    -        )
    -        try:
    -            response.extend(app_response)
    -            body = b"".join(response)
    -        finally:
    -            if hasattr(app_response, "close"):
    -                app_response.close()  # type: ignore
    -        if not data:
    -            raise Exception("WSGI app did not call start_response")
    -
    -        status_code_str, reason = data["status"].split(" ", 1)
    -        status_code = int(status_code_str)
    -        headers = data["headers"]  # type: List[Tuple[str, str]]
    -        header_set = set(k.lower() for (k, v) in headers)
    -        body = escape.utf8(body)
    -        if status_code != 304:
    -            if "content-length" not in header_set:
    -                headers.append(("Content-Length", str(len(body))))
    -            if "content-type" not in header_set:
    -                headers.append(("Content-Type", "text/html; charset=UTF-8"))
    -        if "server" not in header_set:
    -            headers.append(("Server", "TornadoServer/%s" % tornado.version))
    -
    -        start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
    -        header_obj = httputil.HTTPHeaders()
    -        for key, value in headers:
    -            header_obj.add(key, value)
    -        assert request.connection is not None
    -        request.connection.write_headers(start_line, header_obj, chunk=body)
    -        request.connection.finish()
    -        self._log(status_code, request)
    -
    -    @staticmethod
    -    def environ(request: httputil.HTTPServerRequest) -> Dict[Text, Any]:
    -        """Converts a `tornado.httputil.HTTPServerRequest` to a WSGI environment.
    -        """
    -        hostport = request.host.split(":")
    -        if len(hostport) == 2:
    -            host = hostport[0]
    -            port = int(hostport[1])
    -        else:
    -            host = request.host
    -            port = 443 if request.protocol == "https" else 80
    -        environ = {
    -            "REQUEST_METHOD": request.method,
    -            "SCRIPT_NAME": "",
    -            "PATH_INFO": to_wsgi_str(
    -                escape.url_unescape(request.path, encoding=None, plus=False)
    -            ),
    -            "QUERY_STRING": request.query,
    -            "REMOTE_ADDR": request.remote_ip,
    -            "SERVER_NAME": host,
    -            "SERVER_PORT": str(port),
    -            "SERVER_PROTOCOL": request.version,
    -            "wsgi.version": (1, 0),
    -            "wsgi.url_scheme": request.protocol,
    -            "wsgi.input": BytesIO(escape.utf8(request.body)),
    -            "wsgi.errors": sys.stderr,
    -            "wsgi.multithread": False,
    -            "wsgi.multiprocess": True,
    -            "wsgi.run_once": False,
    -        }
    -        if "Content-Type" in request.headers:
    -            environ["CONTENT_TYPE"] = request.headers.pop("Content-Type")
    -        if "Content-Length" in request.headers:
    -            environ["CONTENT_LENGTH"] = request.headers.pop("Content-Length")
    -        for key, value in request.headers.items():
    -            environ["HTTP_" + key.replace("-", "_").upper()] = value
    -        return environ
    -
    -    def _log(self, status_code: int, request: httputil.HTTPServerRequest) -> None:
    -        if status_code < 400:
    -            log_method = access_log.info
    -        elif status_code < 500:
    -            log_method = access_log.warning
    -        else:
    -            log_method = access_log.error
    -        request_time = 1000.0 * request.request_time()
    -        assert request.method is not None
    -        assert request.uri is not None
    -        summary = request.method + " " + request.uri + " (" + request.remote_ip + ")"
    -        log_method("%d %s %.2fms", status_code, summary, request_time)
    -
    -
    -HTTPRequest = httputil.HTTPServerRequest
    diff --git a/telegramer/include/tzlocal/__init__.py b/telegramer/include/tzlocal/__init__.py
    deleted file mode 100644
    index 98ed04f..0000000
    --- a/telegramer/include/tzlocal/__init__.py
    +++ /dev/null
    @@ -1,13 +0,0 @@
    -import sys
    -
    -if sys.platform == "win32":
    -    from tzlocal.win32 import (
    -        get_localzone,
    -        get_localzone_name,
    -        reload_localzone,
    -    )  # pragma: no cover
    -else:
    -    from tzlocal.unix import get_localzone, get_localzone_name, reload_localzone
    -
    -
    -__all__ = ["get_localzone", "get_localzone_name", "reload_localzone"]
    diff --git a/telegramer/include/tzlocal/unix.py b/telegramer/include/tzlocal/unix.py
    deleted file mode 100644
    index eaf96d9..0000000
    --- a/telegramer/include/tzlocal/unix.py
    +++ /dev/null
    @@ -1,215 +0,0 @@
    -import os
    -import re
    -import sys
    -import warnings
    -from datetime import timezone
    -import pytz_deprecation_shim as pds
    -
    -from tzlocal import utils
    -
    -if sys.version_info >= (3, 9):
    -    from zoneinfo import ZoneInfo  # pragma: no cover
    -else:
    -    from backports.zoneinfo import ZoneInfo  # pragma: no cover
    -
    -_cache_tz = None
    -_cache_tz_name = None
    -
    -
    -def _get_localzone_name(_root="/"):
    -    """Tries to find the local timezone configuration.
    -
    -    This method finds the timezone name, if it can, or it returns None.
    -
    -    The parameter _root makes the function look for files like /etc/localtime
    -    beneath the _root directory. This is primarily used by the tests.
    -    In normal usage you call the function without parameters."""
    -
    -    # First try the ENV setting.
    -    tzenv = utils._tz_name_from_env()
    -    if tzenv:
    -        return tzenv
    -
    -    # Are we under Termux on Android?
    -    if os.path.exists(os.path.join(_root, "system/bin/getprop")):
    -        import subprocess
    -
    -        androidtz = (
    -            subprocess.check_output(["getprop", "persist.sys.timezone"])
    -            .strip()
    -            .decode()
    -        )
    -        return androidtz
    -
    -    # Now look for distribution specific configuration files
    -    # that contain the timezone name.
    -
    -    # Stick all of them in a dict, to compare later.
    -    found_configs = {}
    -
    -    for configfile in ("etc/timezone", "var/db/zoneinfo"):
    -        tzpath = os.path.join(_root, configfile)
    -        try:
    -            with open(tzpath, "rt") as tzfile:
    -                data = tzfile.read()
    -
    -                etctz = data.strip('/ \t\r\n')
    -                if not etctz:
    -                    # Empty file, skip
    -                    continue
    -                for etctz in etctz.splitlines():
    -                    # Get rid of host definitions and comments:
    -                    if " " in etctz:
    -                        etctz, dummy = etctz.split(" ", 1)
    -                    if "#" in etctz:
    -                        etctz, dummy = etctz.split("#", 1)
    -                    if not etctz:
    -                        continue
    -
    -                    found_configs[tzpath] = etctz.replace(" ", "_")
    -
    -        except (IOError, UnicodeDecodeError):
    -            # File doesn't exist or is a directory, or it's a binary file.
    -            continue
    -
    -    # CentOS has a ZONE setting in /etc/sysconfig/clock,
    -    # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
    -    # Gentoo has a TIMEZONE setting in /etc/conf.d/clock
    -    # We look through these files for a timezone:
    -
    -    zone_re = re.compile(r"\s*ZONE\s*=\s*\"")
    -    timezone_re = re.compile(r"\s*TIMEZONE\s*=\s*\"")
    -    end_re = re.compile('"')
    -
    -    for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
    -        tzpath = os.path.join(_root, filename)
    -        try:
    -            with open(tzpath, "rt") as tzfile:
    -                data = tzfile.readlines()
    -
    -            for line in data:
    -                # Look for the ZONE= setting.
    -                match = zone_re.match(line)
    -                if match is None:
    -                    # No ZONE= setting. Look for the TIMEZONE= setting.
    -                    match = timezone_re.match(line)
    -                if match is not None:
    -                    # Some setting existed
    -                    line = line[match.end():]
    -                    etctz = line[: end_re.search(line).start()]
    -
    -                    # We found a timezone
    -                    found_configs[tzpath] = etctz.replace(" ", "_")
    -
    -        except (IOError, UnicodeDecodeError):
    -            # UnicodeDecode handles when clock is symlink to /etc/localtime
    -            continue
    -
    -    # systemd distributions use symlinks that include the zone name,
    -    # see manpage of localtime(5) and timedatectl(1)
    -    tzpath = os.path.join(_root, "etc/localtime")
    -    if os.path.exists(tzpath) and os.path.islink(tzpath):
    -        etctz = realtzpath = os.path.realpath(tzpath)
    -        start = etctz.find("/") + 1
    -        while start != 0:
    -            etctz = etctz[start:]
    -            try:
    -                pds.timezone(etctz)
    -                tzinfo = f"{tzpath} is a symlink to"
    -                found_configs[tzinfo] = etctz.replace(" ", "_")
    -            except pds.UnknownTimeZoneError:
    -                pass
    -            start = etctz.find("/") + 1
    -
    -    if len(found_configs) > 0:
    -        # We found some explicit config of some sort!
    -        if len(found_configs) > 1:
    -            # Uh-oh, multiple configs. See if they match:
    -            unique_tzs = set()
    -            zoneinfo = os.path.join(_root, "usr", "share", "zoneinfo")
    -            directory_depth = len(zoneinfo.split(os.path.sep))
    -
    -            for tzname in found_configs.values():
    -                # Look them up in /usr/share/zoneinfo, and find what they
    -                # really point to:
    -                path = os.path.realpath(os.path.join(zoneinfo, *tzname.split("/")))
    -                real_zone_name = "/".join(path.split(os.path.sep)[directory_depth:])
    -                unique_tzs.add(real_zone_name)
    -
    -            if len(unique_tzs) != 1:
    -                message = "Multiple conflicting time zone configurations found:\n"
    -                for key, value in found_configs.items():
    -                    message += f"{key}: {value}\n"
    -                message += "Fix the configuration, or set the time zone in a TZ environment variable.\n"
    -                raise utils.ZoneInfoNotFoundError(message)
    -
    -        # We found exactly one config! Use it.
    -        return list(found_configs.values())[0]
    -
    -
    -def _get_localzone(_root="/"):
    -    """Creates a timezone object from the timezone name.
    -
    -    If there is no timezone config, it will try to create a file from the
    -    localtime timezone, and if there isn't one, it will default to UTC.
    -
    -    The parameter _root makes the function look for files like /etc/localtime
    -    beneath the _root directory. This is primarily used by the tests.
    -    In normal usage you call the function without parameters."""
    -
    -    # First try the ENV setting.
    -    tzenv = utils._tz_from_env()
    -    if tzenv:
    -        return tzenv
    -
    -    tzname = _get_localzone_name(_root)
    -    if tzname is None:
    -        # No explicit setting existed. Use localtime
    -        for filename in ("etc/localtime", "usr/local/etc/localtime"):
    -            tzpath = os.path.join(_root, filename)
    -
    -            if not os.path.exists(tzpath):
    -                continue
    -            with open(tzpath, "rb") as tzfile:
    -                tz = pds.wrap_zone(ZoneInfo.from_file(tzfile, key="local"))
    -                break
    -        else:
    -            warnings.warn("Can not find any timezone configuration, defaulting to UTC.")
    -            tz = timezone.utc
    -    else:
    -        tz = pds.timezone(tzname)
    -
    -    if _root == "/":
    -        # We are using a file in etc to name the timezone.
    -        # Verify that the timezone specified there is actually used:
    -        utils.assert_tz_offset(tz)
    -    return tz
    -
    -
    -def get_localzone_name():
    -    """Get the computers configured local timezone name, if any."""
    -    global _cache_tz_name
    -    if _cache_tz_name is None:
    -        _cache_tz_name = _get_localzone_name()
    -
    -    return _cache_tz_name
    -
    -
    -def get_localzone():
    -    """Get the computers configured local timezone, if any."""
    -
    -    global _cache_tz
    -    if _cache_tz is None:
    -        _cache_tz = _get_localzone()
    -
    -    return _cache_tz
    -
    -
    -def reload_localzone():
    -    """Reload the cached localzone. You need to call this if the timezone has changed."""
    -    global _cache_tz_name
    -    global _cache_tz
    -    _cache_tz_name = _get_localzone_name()
    -    _cache_tz = _get_localzone()
    -
    -    return _cache_tz
    diff --git a/telegramer/include/tzlocal/utils.py b/telegramer/include/tzlocal/utils.py
    deleted file mode 100644
    index d3f9242..0000000
    --- a/telegramer/include/tzlocal/utils.py
    +++ /dev/null
    @@ -1,125 +0,0 @@
    -# -*- coding: utf-8 -*-
    -import os
    -import time
    -import datetime
    -import calendar
    -import pytz_deprecation_shim as pds
    -
    -try:
    -    import zoneinfo  # pragma: no cover
    -except ImportError:
    -    from backports import zoneinfo  # pragma: no cover
    -
    -from tzlocal import windows_tz
    -
    -
    -class ZoneInfoNotFoundError(pds.UnknownTimeZoneError, zoneinfo.ZoneInfoNotFoundError):
    -    """An exception derived from both pytz and zoneinfo
    -
    -    This exception will be trappable both by pytz expecting clients and
    -    zoneinfo expecting clients.
    -    """
    -
    -
    -def get_system_offset():
    -    """Get system's timezone offset using built-in library time.
    -
    -    For the Timezone constants (altzone, daylight, timezone, and tzname), the
    -    value is determined by the timezone rules in effect at module load time or
    -    the last time tzset() is called and may be incorrect for times in the past.
    -
    -    To keep compatibility with Windows, we're always importing time module here.
    -    """
    -
    -    localtime = calendar.timegm(time.localtime())
    -    gmtime = calendar.timegm(time.gmtime())
    -    offset = gmtime - localtime
    -    # We could get the localtime and gmtime on either side of a second switch
    -    # so we check that the difference is less than one minute, because nobody
    -    # has that small DST differences.
    -    if abs(offset - time.altzone) < 60:
    -        return -time.altzone  # pragma: no cover
    -    else:
    -        return -time.timezone  # pragma: no cover
    -
    -
    -def get_tz_offset(tz):
    -    """Get timezone's offset using built-in function datetime.utcoffset()."""
    -    return int(datetime.datetime.now(tz).utcoffset().total_seconds())
    -
    -
    -def assert_tz_offset(tz):
    -    """Assert that system's timezone offset equals to the timezone offset found.
    -
    -    If they don't match, we probably have a misconfiguration, for example, an
    -    incorrect timezone set in /etc/timezone file in systemd distributions."""
    -    tz_offset = get_tz_offset(tz)
    -    system_offset = get_system_offset()
    -    if tz_offset != system_offset:
    -        msg = (
    -            "Timezone offset does not match system offset: {} != {}. "
    -            "Please, check your config files."
    -        ).format(tz_offset, system_offset)
    -        raise ValueError(msg)
    -
    -
    -def _tz_name_from_env(tzenv=None):
    -    if tzenv is None:
    -        tzenv = os.environ.get("TZ")
    -
    -    if not tzenv:
    -        return None
    -
    -    if tzenv in windows_tz.tz_win:
    -        # Yup, it's a timezone
    -        return tzenv
    -
    -    if os.path.isabs(tzenv) and os.path.exists(tzenv):
    -        # It's a file specification
    -        parts = tzenv.split(os.sep)
    -
    -        # Is it a zone info zone?
    -        possible_tz = "/".join(parts[-2:])
    -        if possible_tz in windows_tz.tz_win:
    -            # Yup, it is
    -            return possible_tz
    -
    -        # Maybe it's a short one, like UTC?
    -        if parts[-1] in windows_tz.tz_win:
    -            # Indeed
    -            return parts[-1]
    -
    -
    -def _tz_from_env(tzenv=None):
    -    if tzenv is None:
    -        tzenv = os.environ.get("TZ")
    -
    -    if not tzenv:
    -        return None
    -
    -    # Some weird format that exists:
    -    if tzenv[0] == ":":
    -        tzenv = tzenv[1:]
    -
    -    # TZ specifies a file
    -    if os.path.isabs(tzenv) and os.path.exists(tzenv):
    -        # Try to see if we can figure out the name
    -        tzname = _tz_name_from_env(tzenv)
    -        if not tzname:
    -            # Nope, not a standard timezone name, just take the filename
    -            tzname = tzenv.split(os.sep)[-1]
    -        with open(tzenv, "rb") as tzfile:
    -            zone = zoneinfo.ZoneInfo.from_file(tzfile, key=tzname)
    -            return pds.wrap_zone(zone)
    -
    -    # TZ must specify a zoneinfo zone.
    -    try:
    -        tz = pds.timezone(tzenv)
    -        # That worked, so we return this:
    -        return tz
    -    except pds.UnknownTimeZoneError:
    -        # Nope, it's something like "PST4DST" etc, we can't handle that.
    -        raise ZoneInfoNotFoundError(
    -            "tzlocal() does not support non-zoneinfo timezones like %s. \n"
    -            "Please use a timezone in the form of Continent/City"
    -        ) from None
    diff --git a/telegramer/include/tzlocal/win32.py b/telegramer/include/tzlocal/win32.py
    deleted file mode 100644
    index 720ab2b..0000000
    --- a/telegramer/include/tzlocal/win32.py
    +++ /dev/null
    @@ -1,137 +0,0 @@
    -from datetime import datetime
    -import pytz_deprecation_shim as pds
    -
    -try:
    -    import _winreg as winreg
    -except ImportError:
    -    import winreg
    -
    -from tzlocal.windows_tz import win_tz
    -from tzlocal import utils
    -
    -_cache_tz = None
    -_cache_tz_name = None
    -
    -
    -def valuestodict(key):
    -    """Convert a registry key's values to a dictionary."""
    -    result = {}
    -    size = winreg.QueryInfoKey(key)[1]
    -    for i in range(size):
    -        data = winreg.EnumValue(key, i)
    -        result[data[0]] = data[1]
    -    return result
    -
    -
    -def _get_dst_info(tz):
    -    # Find the offset for when it doesn't have DST:
    -    dst_offset = std_offset = None
    -    has_dst = False
    -    year = datetime.now().year
    -    for dt in (datetime(year, 1, 1), datetime(year, 6, 1)):
    -        if tz.dst(dt).total_seconds() == 0.0:
    -            # OK, no DST during winter, get this offset
    -            std_offset = tz.utcoffset(dt).total_seconds()
    -        else:
    -            has_dst = True
    -
    -    return has_dst, std_offset, dst_offset
    -
    -
    -def _get_localzone_name():
    -    # Windows is special. It has unique time zone names (in several
    -    # meanings of the word) available, but unfortunately, they can be
    -    # translated to the language of the operating system, so we need to
    -    # do a backwards lookup, by going through all time zones and see which
    -    # one matches.
    -    tzenv = utils._tz_name_from_env()
    -    if tzenv:
    -        return tzenv
    -
    -    handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
    -
    -    TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
    -    localtz = winreg.OpenKey(handle, TZLOCALKEYNAME)
    -    keyvalues = valuestodict(localtz)
    -    localtz.Close()
    -
    -    if "TimeZoneKeyName" in keyvalues:
    -        # Windows 7 and later
    -
    -        # For some reason this returns a string with loads of NUL bytes at
    -        # least on some systems. I don't know if this is a bug somewhere, I
    -        # just work around it.
    -        tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0]
    -    else:
    -        # Don't support XP any longer
    -        raise LookupError("Can not find Windows timezone configuration")
    -
    -    timezone = win_tz.get(tzkeyname)
    -    if timezone is None:
    -        # Nope, that didn't work. Try adding "Standard Time",
    -        # it seems to work a lot of times:
    -        timezone = win_tz.get(tzkeyname + " Standard Time")
    -
    -    # Return what we have.
    -    if timezone is None:
    -        raise utils.ZoneInfoNotFoundError(tzkeyname)
    -
    -    if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1:
    -        # DST is disabled, so don't return the timezone name,
    -        # instead return Etc/GMT+offset
    -
    -        tz = pds.timezone(timezone)
    -        has_dst, std_offset, dst_offset = _get_dst_info(tz)
    -        if not has_dst:
    -            # The DST is turned off in the windows configuration,
    -            # but this timezone doesn't have DST so it doesn't matter
    -            return timezone
    -
    -        if std_offset is None:
    -            raise utils.ZoneInfoNotFoundError(
    -                f"{tzkeyname} claims to not have a non-DST time!?")
    -
    -        if std_offset % 3600:
    -            # I can't convert this to an hourly offset
    -            raise utils.ZoneInfoNotFoundError(
    -                f"tzlocal can't support disabling DST in the {timezone} zone.")
    -
    -        # This has whole hours as offset, return it as Etc/GMT
    -        return f"Etc/GMT{-std_offset//3600:+.0f}"
    -
    -    return timezone
    -
    -
    -def get_localzone_name():
    -    """Get the zoneinfo timezone name that matches the Windows-configured timezone."""
    -    global _cache_tz_name
    -    if _cache_tz_name is None:
    -        _cache_tz_name = _get_localzone_name()
    -
    -    return _cache_tz_name
    -
    -
    -def get_localzone():
    -    """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone."""
    -
    -    global _cache_tz
    -    if _cache_tz is None:
    -        _cache_tz = pds.timezone(get_localzone_name())
    -
    -    if not utils._tz_name_from_env():
    -        # If the timezone does NOT come from a TZ environment variable,
    -        # verify that it's correct. If it's from the environment,
    -        # we accept it, this is so you can run tests with different timezones.
    -        utils.assert_tz_offset(_cache_tz)
    -
    -    return _cache_tz
    -
    -
    -def reload_localzone():
    -    """Reload the cached localzone. You need to call this if the timezone has changed."""
    -    global _cache_tz
    -    global _cache_tz_name
    -    _cache_tz_name = _get_localzone_name()
    -    _cache_tz = pds.timezone(_cache_tz_name)
    -    utils.assert_tz_offset(_cache_tz)
    -    return _cache_tz
    diff --git a/telegramer/include/tzlocal/windows_tz.py b/telegramer/include/tzlocal/windows_tz.py
    deleted file mode 100644
    index 0d28503..0000000
    --- a/telegramer/include/tzlocal/windows_tz.py
    +++ /dev/null
    @@ -1,699 +0,0 @@
    -# This file is autogenerated by the update_windows_mapping.py script
    -# Do not edit.
    -win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
    - 'AUS Eastern Standard Time': 'Australia/Sydney',
    - 'Afghanistan Standard Time': 'Asia/Kabul',
    - 'Alaskan Standard Time': 'America/Anchorage',
    - 'Aleutian Standard Time': 'America/Adak',
    - 'Altai Standard Time': 'Asia/Barnaul',
    - 'Arab Standard Time': 'Asia/Riyadh',
    - 'Arabian Standard Time': 'Asia/Dubai',
    - 'Arabic Standard Time': 'Asia/Baghdad',
    - 'Argentina Standard Time': 'America/Buenos_Aires',
    - 'Astrakhan Standard Time': 'Europe/Astrakhan',
    - 'Atlantic Standard Time': 'America/Halifax',
    - 'Aus Central W. Standard Time': 'Australia/Eucla',
    - 'Azerbaijan Standard Time': 'Asia/Baku',
    - 'Azores Standard Time': 'Atlantic/Azores',
    - 'Bahia Standard Time': 'America/Bahia',
    - 'Bangladesh Standard Time': 'Asia/Dhaka',
    - 'Belarus Standard Time': 'Europe/Minsk',
    - 'Bougainville Standard Time': 'Pacific/Bougainville',
    - 'Canada Central Standard Time': 'America/Regina',
    - 'Cape Verde Standard Time': 'Atlantic/Cape_Verde',
    - 'Caucasus Standard Time': 'Asia/Yerevan',
    - 'Cen. Australia Standard Time': 'Australia/Adelaide',
    - 'Central America Standard Time': 'America/Guatemala',
    - 'Central Asia Standard Time': 'Asia/Almaty',
    - 'Central Brazilian Standard Time': 'America/Cuiaba',
    - 'Central Europe Standard Time': 'Europe/Budapest',
    - 'Central European Standard Time': 'Europe/Warsaw',
    - 'Central Pacific Standard Time': 'Pacific/Guadalcanal',
    - 'Central Standard Time': 'America/Chicago',
    - 'Central Standard Time (Mexico)': 'America/Mexico_City',
    - 'Chatham Islands Standard Time': 'Pacific/Chatham',
    - 'China Standard Time': 'Asia/Shanghai',
    - 'Cuba Standard Time': 'America/Havana',
    - 'Dateline Standard Time': 'Etc/GMT+12',
    - 'E. Africa Standard Time': 'Africa/Nairobi',
    - 'E. Australia Standard Time': 'Australia/Brisbane',
    - 'E. Europe Standard Time': 'Europe/Chisinau',
    - 'E. South America Standard Time': 'America/Sao_Paulo',
    - 'Easter Island Standard Time': 'Pacific/Easter',
    - 'Eastern Standard Time': 'America/New_York',
    - 'Eastern Standard Time (Mexico)': 'America/Cancun',
    - 'Egypt Standard Time': 'Africa/Cairo',
    - 'Ekaterinburg Standard Time': 'Asia/Yekaterinburg',
    - 'FLE Standard Time': 'Europe/Kiev',
    - 'Fiji Standard Time': 'Pacific/Fiji',
    - 'GMT Standard Time': 'Europe/London',
    - 'GTB Standard Time': 'Europe/Bucharest',
    - 'Georgian Standard Time': 'Asia/Tbilisi',
    - 'Greenland Standard Time': 'America/Godthab',
    - 'Greenwich Standard Time': 'Atlantic/Reykjavik',
    - 'Haiti Standard Time': 'America/Port-au-Prince',
    - 'Hawaiian Standard Time': 'Pacific/Honolulu',
    - 'India Standard Time': 'Asia/Calcutta',
    - 'Iran Standard Time': 'Asia/Tehran',
    - 'Israel Standard Time': 'Asia/Jerusalem',
    - 'Jordan Standard Time': 'Asia/Amman',
    - 'Kaliningrad Standard Time': 'Europe/Kaliningrad',
    - 'Korea Standard Time': 'Asia/Seoul',
    - 'Libya Standard Time': 'Africa/Tripoli',
    - 'Line Islands Standard Time': 'Pacific/Kiritimati',
    - 'Lord Howe Standard Time': 'Australia/Lord_Howe',
    - 'Magadan Standard Time': 'Asia/Magadan',
    - 'Magallanes Standard Time': 'America/Punta_Arenas',
    - 'Marquesas Standard Time': 'Pacific/Marquesas',
    - 'Mauritius Standard Time': 'Indian/Mauritius',
    - 'Middle East Standard Time': 'Asia/Beirut',
    - 'Montevideo Standard Time': 'America/Montevideo',
    - 'Morocco Standard Time': 'Africa/Casablanca',
    - 'Mountain Standard Time': 'America/Denver',
    - 'Mountain Standard Time (Mexico)': 'America/Chihuahua',
    - 'Myanmar Standard Time': 'Asia/Rangoon',
    - 'N. Central Asia Standard Time': 'Asia/Novosibirsk',
    - 'Namibia Standard Time': 'Africa/Windhoek',
    - 'Nepal Standard Time': 'Asia/Katmandu',
    - 'New Zealand Standard Time': 'Pacific/Auckland',
    - 'Newfoundland Standard Time': 'America/St_Johns',
    - 'Norfolk Standard Time': 'Pacific/Norfolk',
    - 'North Asia East Standard Time': 'Asia/Irkutsk',
    - 'North Asia Standard Time': 'Asia/Krasnoyarsk',
    - 'North Korea Standard Time': 'Asia/Pyongyang',
    - 'Omsk Standard Time': 'Asia/Omsk',
    - 'Pacific SA Standard Time': 'America/Santiago',
    - 'Pacific Standard Time': 'America/Los_Angeles',
    - 'Pacific Standard Time (Mexico)': 'America/Tijuana',
    - 'Pakistan Standard Time': 'Asia/Karachi',
    - 'Paraguay Standard Time': 'America/Asuncion',
    - 'Qyzylorda Standard Time': 'Asia/Qyzylorda',
    - 'Romance Standard Time': 'Europe/Paris',
    - 'Russia Time Zone 10': 'Asia/Srednekolymsk',
    - 'Russia Time Zone 11': 'Asia/Kamchatka',
    - 'Russia Time Zone 3': 'Europe/Samara',
    - 'Russian Standard Time': 'Europe/Moscow',
    - 'SA Eastern Standard Time': 'America/Cayenne',
    - 'SA Pacific Standard Time': 'America/Bogota',
    - 'SA Western Standard Time': 'America/La_Paz',
    - 'SE Asia Standard Time': 'Asia/Bangkok',
    - 'Saint Pierre Standard Time': 'America/Miquelon',
    - 'Sakhalin Standard Time': 'Asia/Sakhalin',
    - 'Samoa Standard Time': 'Pacific/Apia',
    - 'Sao Tome Standard Time': 'Africa/Sao_Tome',
    - 'Saratov Standard Time': 'Europe/Saratov',
    - 'Singapore Standard Time': 'Asia/Singapore',
    - 'South Africa Standard Time': 'Africa/Johannesburg',
    - 'South Sudan Standard Time': 'Africa/Juba',
    - 'Sri Lanka Standard Time': 'Asia/Colombo',
    - 'Sudan Standard Time': 'Africa/Khartoum',
    - 'Syria Standard Time': 'Asia/Damascus',
    - 'Taipei Standard Time': 'Asia/Taipei',
    - 'Tasmania Standard Time': 'Australia/Hobart',
    - 'Tocantins Standard Time': 'America/Araguaina',
    - 'Tokyo Standard Time': 'Asia/Tokyo',
    - 'Tomsk Standard Time': 'Asia/Tomsk',
    - 'Tonga Standard Time': 'Pacific/Tongatapu',
    - 'Transbaikal Standard Time': 'Asia/Chita',
    - 'Turkey Standard Time': 'Europe/Istanbul',
    - 'Turks And Caicos Standard Time': 'America/Grand_Turk',
    - 'US Eastern Standard Time': 'America/Indianapolis',
    - 'US Mountain Standard Time': 'America/Phoenix',
    - 'UTC': 'Etc/UTC',
    - 'UTC+12': 'Etc/GMT-12',
    - 'UTC+13': 'Etc/GMT-13',
    - 'UTC-02': 'Etc/GMT+2',
    - 'UTC-08': 'Etc/GMT+8',
    - 'UTC-09': 'Etc/GMT+9',
    - 'UTC-11': 'Etc/GMT+11',
    - 'Ulaanbaatar Standard Time': 'Asia/Ulaanbaatar',
    - 'Venezuela Standard Time': 'America/Caracas',
    - 'Vladivostok Standard Time': 'Asia/Vladivostok',
    - 'Volgograd Standard Time': 'Europe/Volgograd',
    - 'W. Australia Standard Time': 'Australia/Perth',
    - 'W. Central Africa Standard Time': 'Africa/Lagos',
    - 'W. Europe Standard Time': 'Europe/Berlin',
    - 'W. Mongolia Standard Time': 'Asia/Hovd',
    - 'West Asia Standard Time': 'Asia/Tashkent',
    - 'West Bank Standard Time': 'Asia/Hebron',
    - 'West Pacific Standard Time': 'Pacific/Port_Moresby',
    - 'Yakutsk Standard Time': 'Asia/Yakutsk',
    - 'Yukon Standard Time': 'America/Whitehorse'}
    -
    -# Old name for the win_tz variable:
    -tz_names = win_tz
    -
    -tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
    - 'Africa/Accra': 'Greenwich Standard Time',
    - 'Africa/Addis_Ababa': 'E. Africa Standard Time',
    - 'Africa/Algiers': 'W. Central Africa Standard Time',
    - 'Africa/Asmera': 'E. Africa Standard Time',
    - 'Africa/Bamako': 'Greenwich Standard Time',
    - 'Africa/Bangui': 'W. Central Africa Standard Time',
    - 'Africa/Banjul': 'Greenwich Standard Time',
    - 'Africa/Bissau': 'Greenwich Standard Time',
    - 'Africa/Blantyre': 'South Africa Standard Time',
    - 'Africa/Brazzaville': 'W. Central Africa Standard Time',
    - 'Africa/Bujumbura': 'South Africa Standard Time',
    - 'Africa/Cairo': 'Egypt Standard Time',
    - 'Africa/Casablanca': 'Morocco Standard Time',
    - 'Africa/Ceuta': 'Romance Standard Time',
    - 'Africa/Conakry': 'Greenwich Standard Time',
    - 'Africa/Dakar': 'Greenwich Standard Time',
    - 'Africa/Dar_es_Salaam': 'E. Africa Standard Time',
    - 'Africa/Djibouti': 'E. Africa Standard Time',
    - 'Africa/Douala': 'W. Central Africa Standard Time',
    - 'Africa/El_Aaiun': 'Morocco Standard Time',
    - 'Africa/Freetown': 'Greenwich Standard Time',
    - 'Africa/Gaborone': 'South Africa Standard Time',
    - 'Africa/Harare': 'South Africa Standard Time',
    - 'Africa/Johannesburg': 'South Africa Standard Time',
    - 'Africa/Juba': 'South Sudan Standard Time',
    - 'Africa/Kampala': 'E. Africa Standard Time',
    - 'Africa/Khartoum': 'Sudan Standard Time',
    - 'Africa/Kigali': 'South Africa Standard Time',
    - 'Africa/Kinshasa': 'W. Central Africa Standard Time',
    - 'Africa/Lagos': 'W. Central Africa Standard Time',
    - 'Africa/Libreville': 'W. Central Africa Standard Time',
    - 'Africa/Lome': 'Greenwich Standard Time',
    - 'Africa/Luanda': 'W. Central Africa Standard Time',
    - 'Africa/Lubumbashi': 'South Africa Standard Time',
    - 'Africa/Lusaka': 'South Africa Standard Time',
    - 'Africa/Malabo': 'W. Central Africa Standard Time',
    - 'Africa/Maputo': 'South Africa Standard Time',
    - 'Africa/Maseru': 'South Africa Standard Time',
    - 'Africa/Mbabane': 'South Africa Standard Time',
    - 'Africa/Mogadishu': 'E. Africa Standard Time',
    - 'Africa/Monrovia': 'Greenwich Standard Time',
    - 'Africa/Nairobi': 'E. Africa Standard Time',
    - 'Africa/Ndjamena': 'W. Central Africa Standard Time',
    - 'Africa/Niamey': 'W. Central Africa Standard Time',
    - 'Africa/Nouakchott': 'Greenwich Standard Time',
    - 'Africa/Ouagadougou': 'Greenwich Standard Time',
    - 'Africa/Porto-Novo': 'W. Central Africa Standard Time',
    - 'Africa/Sao_Tome': 'Sao Tome Standard Time',
    - 'Africa/Timbuktu': 'Greenwich Standard Time',
    - 'Africa/Tripoli': 'Libya Standard Time',
    - 'Africa/Tunis': 'W. Central Africa Standard Time',
    - 'Africa/Windhoek': 'Namibia Standard Time',
    - 'America/Adak': 'Aleutian Standard Time',
    - 'America/Anchorage': 'Alaskan Standard Time',
    - 'America/Anguilla': 'SA Western Standard Time',
    - 'America/Antigua': 'SA Western Standard Time',
    - 'America/Araguaina': 'Tocantins Standard Time',
    - 'America/Argentina/La_Rioja': 'Argentina Standard Time',
    - 'America/Argentina/Rio_Gallegos': 'Argentina Standard Time',
    - 'America/Argentina/Salta': 'Argentina Standard Time',
    - 'America/Argentina/San_Juan': 'Argentina Standard Time',
    - 'America/Argentina/San_Luis': 'Argentina Standard Time',
    - 'America/Argentina/Tucuman': 'Argentina Standard Time',
    - 'America/Argentina/Ushuaia': 'Argentina Standard Time',
    - 'America/Aruba': 'SA Western Standard Time',
    - 'America/Asuncion': 'Paraguay Standard Time',
    - 'America/Atka': 'Aleutian Standard Time',
    - 'America/Bahia': 'Bahia Standard Time',
    - 'America/Bahia_Banderas': 'Central Standard Time (Mexico)',
    - 'America/Barbados': 'SA Western Standard Time',
    - 'America/Belem': 'SA Eastern Standard Time',
    - 'America/Belize': 'Central America Standard Time',
    - 'America/Blanc-Sablon': 'SA Western Standard Time',
    - 'America/Boa_Vista': 'SA Western Standard Time',
    - 'America/Bogota': 'SA Pacific Standard Time',
    - 'America/Boise': 'Mountain Standard Time',
    - 'America/Buenos_Aires': 'Argentina Standard Time',
    - 'America/Cambridge_Bay': 'Mountain Standard Time',
    - 'America/Campo_Grande': 'Central Brazilian Standard Time',
    - 'America/Cancun': 'Eastern Standard Time (Mexico)',
    - 'America/Caracas': 'Venezuela Standard Time',
    - 'America/Catamarca': 'Argentina Standard Time',
    - 'America/Cayenne': 'SA Eastern Standard Time',
    - 'America/Cayman': 'SA Pacific Standard Time',
    - 'America/Chicago': 'Central Standard Time',
    - 'America/Chihuahua': 'Mountain Standard Time (Mexico)',
    - 'America/Coral_Harbour': 'SA Pacific Standard Time',
    - 'America/Cordoba': 'Argentina Standard Time',
    - 'America/Costa_Rica': 'Central America Standard Time',
    - 'America/Creston': 'US Mountain Standard Time',
    - 'America/Cuiaba': 'Central Brazilian Standard Time',
    - 'America/Curacao': 'SA Western Standard Time',
    - 'America/Danmarkshavn': 'Greenwich Standard Time',
    - 'America/Dawson': 'Yukon Standard Time',
    - 'America/Dawson_Creek': 'US Mountain Standard Time',
    - 'America/Denver': 'Mountain Standard Time',
    - 'America/Detroit': 'Eastern Standard Time',
    - 'America/Dominica': 'SA Western Standard Time',
    - 'America/Edmonton': 'Mountain Standard Time',
    - 'America/Eirunepe': 'SA Pacific Standard Time',
    - 'America/El_Salvador': 'Central America Standard Time',
    - 'America/Ensenada': 'Pacific Standard Time (Mexico)',
    - 'America/Fort_Nelson': 'US Mountain Standard Time',
    - 'America/Fortaleza': 'SA Eastern Standard Time',
    - 'America/Glace_Bay': 'Atlantic Standard Time',
    - 'America/Godthab': 'Greenland Standard Time',
    - 'America/Goose_Bay': 'Atlantic Standard Time',
    - 'America/Grand_Turk': 'Turks And Caicos Standard Time',
    - 'America/Grenada': 'SA Western Standard Time',
    - 'America/Guadeloupe': 'SA Western Standard Time',
    - 'America/Guatemala': 'Central America Standard Time',
    - 'America/Guayaquil': 'SA Pacific Standard Time',
    - 'America/Guyana': 'SA Western Standard Time',
    - 'America/Halifax': 'Atlantic Standard Time',
    - 'America/Havana': 'Cuba Standard Time',
    - 'America/Hermosillo': 'US Mountain Standard Time',
    - 'America/Indiana/Knox': 'Central Standard Time',
    - 'America/Indiana/Marengo': 'US Eastern Standard Time',
    - 'America/Indiana/Petersburg': 'Eastern Standard Time',
    - 'America/Indiana/Tell_City': 'Central Standard Time',
    - 'America/Indiana/Vevay': 'US Eastern Standard Time',
    - 'America/Indiana/Vincennes': 'Eastern Standard Time',
    - 'America/Indiana/Winamac': 'Eastern Standard Time',
    - 'America/Indianapolis': 'US Eastern Standard Time',
    - 'America/Inuvik': 'Mountain Standard Time',
    - 'America/Iqaluit': 'Eastern Standard Time',
    - 'America/Jamaica': 'SA Pacific Standard Time',
    - 'America/Jujuy': 'Argentina Standard Time',
    - 'America/Juneau': 'Alaskan Standard Time',
    - 'America/Kentucky/Monticello': 'Eastern Standard Time',
    - 'America/Knox_IN': 'Central Standard Time',
    - 'America/Kralendijk': 'SA Western Standard Time',
    - 'America/La_Paz': 'SA Western Standard Time',
    - 'America/Lima': 'SA Pacific Standard Time',
    - 'America/Los_Angeles': 'Pacific Standard Time',
    - 'America/Louisville': 'Eastern Standard Time',
    - 'America/Lower_Princes': 'SA Western Standard Time',
    - 'America/Maceio': 'SA Eastern Standard Time',
    - 'America/Managua': 'Central America Standard Time',
    - 'America/Manaus': 'SA Western Standard Time',
    - 'America/Marigot': 'SA Western Standard Time',
    - 'America/Martinique': 'SA Western Standard Time',
    - 'America/Matamoros': 'Central Standard Time',
    - 'America/Mazatlan': 'Mountain Standard Time (Mexico)',
    - 'America/Mendoza': 'Argentina Standard Time',
    - 'America/Menominee': 'Central Standard Time',
    - 'America/Merida': 'Central Standard Time (Mexico)',
    - 'America/Metlakatla': 'Alaskan Standard Time',
    - 'America/Mexico_City': 'Central Standard Time (Mexico)',
    - 'America/Miquelon': 'Saint Pierre Standard Time',
    - 'America/Moncton': 'Atlantic Standard Time',
    - 'America/Monterrey': 'Central Standard Time (Mexico)',
    - 'America/Montevideo': 'Montevideo Standard Time',
    - 'America/Montreal': 'Eastern Standard Time',
    - 'America/Montserrat': 'SA Western Standard Time',
    - 'America/Nassau': 'Eastern Standard Time',
    - 'America/New_York': 'Eastern Standard Time',
    - 'America/Nipigon': 'Eastern Standard Time',
    - 'America/Nome': 'Alaskan Standard Time',
    - 'America/Noronha': 'UTC-02',
    - 'America/North_Dakota/Beulah': 'Central Standard Time',
    - 'America/North_Dakota/Center': 'Central Standard Time',
    - 'America/North_Dakota/New_Salem': 'Central Standard Time',
    - 'America/Ojinaga': 'Mountain Standard Time',
    - 'America/Panama': 'SA Pacific Standard Time',
    - 'America/Pangnirtung': 'Eastern Standard Time',
    - 'America/Paramaribo': 'SA Eastern Standard Time',
    - 'America/Phoenix': 'US Mountain Standard Time',
    - 'America/Port-au-Prince': 'Haiti Standard Time',
    - 'America/Port_of_Spain': 'SA Western Standard Time',
    - 'America/Porto_Acre': 'SA Pacific Standard Time',
    - 'America/Porto_Velho': 'SA Western Standard Time',
    - 'America/Puerto_Rico': 'SA Western Standard Time',
    - 'America/Punta_Arenas': 'Magallanes Standard Time',
    - 'America/Rainy_River': 'Central Standard Time',
    - 'America/Rankin_Inlet': 'Central Standard Time',
    - 'America/Recife': 'SA Eastern Standard Time',
    - 'America/Regina': 'Canada Central Standard Time',
    - 'America/Resolute': 'Central Standard Time',
    - 'America/Rio_Branco': 'SA Pacific Standard Time',
    - 'America/Santa_Isabel': 'Pacific Standard Time (Mexico)',
    - 'America/Santarem': 'SA Eastern Standard Time',
    - 'America/Santiago': 'Pacific SA Standard Time',
    - 'America/Santo_Domingo': 'SA Western Standard Time',
    - 'America/Sao_Paulo': 'E. South America Standard Time',
    - 'America/Scoresbysund': 'Azores Standard Time',
    - 'America/Shiprock': 'Mountain Standard Time',
    - 'America/Sitka': 'Alaskan Standard Time',
    - 'America/St_Barthelemy': 'SA Western Standard Time',
    - 'America/St_Johns': 'Newfoundland Standard Time',
    - 'America/St_Kitts': 'SA Western Standard Time',
    - 'America/St_Lucia': 'SA Western Standard Time',
    - 'America/St_Thomas': 'SA Western Standard Time',
    - 'America/St_Vincent': 'SA Western Standard Time',
    - 'America/Swift_Current': 'Canada Central Standard Time',
    - 'America/Tegucigalpa': 'Central America Standard Time',
    - 'America/Thule': 'Atlantic Standard Time',
    - 'America/Thunder_Bay': 'Eastern Standard Time',
    - 'America/Tijuana': 'Pacific Standard Time (Mexico)',
    - 'America/Toronto': 'Eastern Standard Time',
    - 'America/Tortola': 'SA Western Standard Time',
    - 'America/Vancouver': 'Pacific Standard Time',
    - 'America/Virgin': 'SA Western Standard Time',
    - 'America/Whitehorse': 'Yukon Standard Time',
    - 'America/Winnipeg': 'Central Standard Time',
    - 'America/Yakutat': 'Alaskan Standard Time',
    - 'America/Yellowknife': 'Mountain Standard Time',
    - 'Antarctica/Casey': 'Central Pacific Standard Time',
    - 'Antarctica/Davis': 'SE Asia Standard Time',
    - 'Antarctica/DumontDUrville': 'West Pacific Standard Time',
    - 'Antarctica/Macquarie': 'Tasmania Standard Time',
    - 'Antarctica/Mawson': 'West Asia Standard Time',
    - 'Antarctica/McMurdo': 'New Zealand Standard Time',
    - 'Antarctica/Palmer': 'SA Eastern Standard Time',
    - 'Antarctica/Rothera': 'SA Eastern Standard Time',
    - 'Antarctica/South_Pole': 'New Zealand Standard Time',
    - 'Antarctica/Syowa': 'E. Africa Standard Time',
    - 'Antarctica/Vostok': 'Central Asia Standard Time',
    - 'Arctic/Longyearbyen': 'W. Europe Standard Time',
    - 'Asia/Aden': 'Arab Standard Time',
    - 'Asia/Almaty': 'Central Asia Standard Time',
    - 'Asia/Amman': 'Jordan Standard Time',
    - 'Asia/Anadyr': 'Russia Time Zone 11',
    - 'Asia/Aqtau': 'West Asia Standard Time',
    - 'Asia/Aqtobe': 'West Asia Standard Time',
    - 'Asia/Ashgabat': 'West Asia Standard Time',
    - 'Asia/Ashkhabad': 'West Asia Standard Time',
    - 'Asia/Atyrau': 'West Asia Standard Time',
    - 'Asia/Baghdad': 'Arabic Standard Time',
    - 'Asia/Bahrain': 'Arab Standard Time',
    - 'Asia/Baku': 'Azerbaijan Standard Time',
    - 'Asia/Bangkok': 'SE Asia Standard Time',
    - 'Asia/Barnaul': 'Altai Standard Time',
    - 'Asia/Beirut': 'Middle East Standard Time',
    - 'Asia/Bishkek': 'Central Asia Standard Time',
    - 'Asia/Brunei': 'Singapore Standard Time',
    - 'Asia/Calcutta': 'India Standard Time',
    - 'Asia/Chita': 'Transbaikal Standard Time',
    - 'Asia/Choibalsan': 'Ulaanbaatar Standard Time',
    - 'Asia/Chongqing': 'China Standard Time',
    - 'Asia/Chungking': 'China Standard Time',
    - 'Asia/Colombo': 'Sri Lanka Standard Time',
    - 'Asia/Dacca': 'Bangladesh Standard Time',
    - 'Asia/Damascus': 'Syria Standard Time',
    - 'Asia/Dhaka': 'Bangladesh Standard Time',
    - 'Asia/Dili': 'Tokyo Standard Time',
    - 'Asia/Dubai': 'Arabian Standard Time',
    - 'Asia/Dushanbe': 'West Asia Standard Time',
    - 'Asia/Famagusta': 'GTB Standard Time',
    - 'Asia/Gaza': 'West Bank Standard Time',
    - 'Asia/Harbin': 'China Standard Time',
    - 'Asia/Hebron': 'West Bank Standard Time',
    - 'Asia/Hong_Kong': 'China Standard Time',
    - 'Asia/Hovd': 'W. Mongolia Standard Time',
    - 'Asia/Irkutsk': 'North Asia East Standard Time',
    - 'Asia/Jakarta': 'SE Asia Standard Time',
    - 'Asia/Jayapura': 'Tokyo Standard Time',
    - 'Asia/Jerusalem': 'Israel Standard Time',
    - 'Asia/Kabul': 'Afghanistan Standard Time',
    - 'Asia/Kamchatka': 'Russia Time Zone 11',
    - 'Asia/Karachi': 'Pakistan Standard Time',
    - 'Asia/Kashgar': 'Central Asia Standard Time',
    - 'Asia/Katmandu': 'Nepal Standard Time',
    - 'Asia/Khandyga': 'Yakutsk Standard Time',
    - 'Asia/Krasnoyarsk': 'North Asia Standard Time',
    - 'Asia/Kuala_Lumpur': 'Singapore Standard Time',
    - 'Asia/Kuching': 'Singapore Standard Time',
    - 'Asia/Kuwait': 'Arab Standard Time',
    - 'Asia/Macao': 'China Standard Time',
    - 'Asia/Macau': 'China Standard Time',
    - 'Asia/Magadan': 'Magadan Standard Time',
    - 'Asia/Makassar': 'Singapore Standard Time',
    - 'Asia/Manila': 'Singapore Standard Time',
    - 'Asia/Muscat': 'Arabian Standard Time',
    - 'Asia/Nicosia': 'GTB Standard Time',
    - 'Asia/Novokuznetsk': 'North Asia Standard Time',
    - 'Asia/Novosibirsk': 'N. Central Asia Standard Time',
    - 'Asia/Omsk': 'Omsk Standard Time',
    - 'Asia/Oral': 'West Asia Standard Time',
    - 'Asia/Phnom_Penh': 'SE Asia Standard Time',
    - 'Asia/Pontianak': 'SE Asia Standard Time',
    - 'Asia/Pyongyang': 'North Korea Standard Time',
    - 'Asia/Qatar': 'Arab Standard Time',
    - 'Asia/Qostanay': 'Central Asia Standard Time',
    - 'Asia/Qyzylorda': 'Qyzylorda Standard Time',
    - 'Asia/Rangoon': 'Myanmar Standard Time',
    - 'Asia/Riyadh': 'Arab Standard Time',
    - 'Asia/Saigon': 'SE Asia Standard Time',
    - 'Asia/Sakhalin': 'Sakhalin Standard Time',
    - 'Asia/Samarkand': 'West Asia Standard Time',
    - 'Asia/Seoul': 'Korea Standard Time',
    - 'Asia/Shanghai': 'China Standard Time',
    - 'Asia/Singapore': 'Singapore Standard Time',
    - 'Asia/Srednekolymsk': 'Russia Time Zone 10',
    - 'Asia/Taipei': 'Taipei Standard Time',
    - 'Asia/Tashkent': 'West Asia Standard Time',
    - 'Asia/Tbilisi': 'Georgian Standard Time',
    - 'Asia/Tehran': 'Iran Standard Time',
    - 'Asia/Tel_Aviv': 'Israel Standard Time',
    - 'Asia/Thimbu': 'Bangladesh Standard Time',
    - 'Asia/Thimphu': 'Bangladesh Standard Time',
    - 'Asia/Tokyo': 'Tokyo Standard Time',
    - 'Asia/Tomsk': 'Tomsk Standard Time',
    - 'Asia/Ujung_Pandang': 'Singapore Standard Time',
    - 'Asia/Ulaanbaatar': 'Ulaanbaatar Standard Time',
    - 'Asia/Ulan_Bator': 'Ulaanbaatar Standard Time',
    - 'Asia/Urumqi': 'Central Asia Standard Time',
    - 'Asia/Ust-Nera': 'Vladivostok Standard Time',
    - 'Asia/Vientiane': 'SE Asia Standard Time',
    - 'Asia/Vladivostok': 'Vladivostok Standard Time',
    - 'Asia/Yakutsk': 'Yakutsk Standard Time',
    - 'Asia/Yekaterinburg': 'Ekaterinburg Standard Time',
    - 'Asia/Yerevan': 'Caucasus Standard Time',
    - 'Atlantic/Azores': 'Azores Standard Time',
    - 'Atlantic/Bermuda': 'Atlantic Standard Time',
    - 'Atlantic/Canary': 'GMT Standard Time',
    - 'Atlantic/Cape_Verde': 'Cape Verde Standard Time',
    - 'Atlantic/Faeroe': 'GMT Standard Time',
    - 'Atlantic/Jan_Mayen': 'W. Europe Standard Time',
    - 'Atlantic/Madeira': 'GMT Standard Time',
    - 'Atlantic/Reykjavik': 'Greenwich Standard Time',
    - 'Atlantic/South_Georgia': 'UTC-02',
    - 'Atlantic/St_Helena': 'Greenwich Standard Time',
    - 'Atlantic/Stanley': 'SA Eastern Standard Time',
    - 'Australia/ACT': 'AUS Eastern Standard Time',
    - 'Australia/Adelaide': 'Cen. Australia Standard Time',
    - 'Australia/Brisbane': 'E. Australia Standard Time',
    - 'Australia/Broken_Hill': 'Cen. Australia Standard Time',
    - 'Australia/Canberra': 'AUS Eastern Standard Time',
    - 'Australia/Currie': 'Tasmania Standard Time',
    - 'Australia/Darwin': 'AUS Central Standard Time',
    - 'Australia/Eucla': 'Aus Central W. Standard Time',
    - 'Australia/Hobart': 'Tasmania Standard Time',
    - 'Australia/LHI': 'Lord Howe Standard Time',
    - 'Australia/Lindeman': 'E. Australia Standard Time',
    - 'Australia/Lord_Howe': 'Lord Howe Standard Time',
    - 'Australia/Melbourne': 'AUS Eastern Standard Time',
    - 'Australia/NSW': 'AUS Eastern Standard Time',
    - 'Australia/North': 'AUS Central Standard Time',
    - 'Australia/Perth': 'W. Australia Standard Time',
    - 'Australia/Queensland': 'E. Australia Standard Time',
    - 'Australia/South': 'Cen. Australia Standard Time',
    - 'Australia/Sydney': 'AUS Eastern Standard Time',
    - 'Australia/Tasmania': 'Tasmania Standard Time',
    - 'Australia/Victoria': 'AUS Eastern Standard Time',
    - 'Australia/West': 'W. Australia Standard Time',
    - 'Australia/Yancowinna': 'Cen. Australia Standard Time',
    - 'Brazil/Acre': 'SA Pacific Standard Time',
    - 'Brazil/DeNoronha': 'UTC-02',
    - 'Brazil/East': 'E. South America Standard Time',
    - 'Brazil/West': 'SA Western Standard Time',
    - 'CST6CDT': 'Central Standard Time',
    - 'Canada/Atlantic': 'Atlantic Standard Time',
    - 'Canada/Central': 'Central Standard Time',
    - 'Canada/Eastern': 'Eastern Standard Time',
    - 'Canada/Mountain': 'Mountain Standard Time',
    - 'Canada/Newfoundland': 'Newfoundland Standard Time',
    - 'Canada/Pacific': 'Pacific Standard Time',
    - 'Canada/Saskatchewan': 'Canada Central Standard Time',
    - 'Canada/Yukon': 'Yukon Standard Time',
    - 'Chile/Continental': 'Pacific SA Standard Time',
    - 'Chile/EasterIsland': 'Easter Island Standard Time',
    - 'Cuba': 'Cuba Standard Time',
    - 'EST5EDT': 'Eastern Standard Time',
    - 'Egypt': 'Egypt Standard Time',
    - 'Eire': 'GMT Standard Time',
    - 'Etc/GMT': 'UTC',
    - 'Etc/GMT+1': 'Cape Verde Standard Time',
    - 'Etc/GMT+10': 'Hawaiian Standard Time',
    - 'Etc/GMT+11': 'UTC-11',
    - 'Etc/GMT+12': 'Dateline Standard Time',
    - 'Etc/GMT+2': 'UTC-02',
    - 'Etc/GMT+3': 'SA Eastern Standard Time',
    - 'Etc/GMT+4': 'SA Western Standard Time',
    - 'Etc/GMT+5': 'SA Pacific Standard Time',
    - 'Etc/GMT+6': 'Central America Standard Time',
    - 'Etc/GMT+7': 'US Mountain Standard Time',
    - 'Etc/GMT+8': 'UTC-08',
    - 'Etc/GMT+9': 'UTC-09',
    - 'Etc/GMT-1': 'W. Central Africa Standard Time',
    - 'Etc/GMT-10': 'West Pacific Standard Time',
    - 'Etc/GMT-11': 'Central Pacific Standard Time',
    - 'Etc/GMT-12': 'UTC+12',
    - 'Etc/GMT-13': 'UTC+13',
    - 'Etc/GMT-14': 'Line Islands Standard Time',
    - 'Etc/GMT-2': 'South Africa Standard Time',
    - 'Etc/GMT-3': 'E. Africa Standard Time',
    - 'Etc/GMT-4': 'Arabian Standard Time',
    - 'Etc/GMT-5': 'West Asia Standard Time',
    - 'Etc/GMT-6': 'Central Asia Standard Time',
    - 'Etc/GMT-7': 'SE Asia Standard Time',
    - 'Etc/GMT-8': 'Singapore Standard Time',
    - 'Etc/GMT-9': 'Tokyo Standard Time',
    - 'Etc/UCT': 'UTC',
    - 'Etc/UTC': 'UTC',
    - 'Europe/Amsterdam': 'W. Europe Standard Time',
    - 'Europe/Andorra': 'W. Europe Standard Time',
    - 'Europe/Astrakhan': 'Astrakhan Standard Time',
    - 'Europe/Athens': 'GTB Standard Time',
    - 'Europe/Belfast': 'GMT Standard Time',
    - 'Europe/Belgrade': 'Central Europe Standard Time',
    - 'Europe/Berlin': 'W. Europe Standard Time',
    - 'Europe/Bratislava': 'Central Europe Standard Time',
    - 'Europe/Brussels': 'Romance Standard Time',
    - 'Europe/Bucharest': 'GTB Standard Time',
    - 'Europe/Budapest': 'Central Europe Standard Time',
    - 'Europe/Busingen': 'W. Europe Standard Time',
    - 'Europe/Chisinau': 'E. Europe Standard Time',
    - 'Europe/Copenhagen': 'Romance Standard Time',
    - 'Europe/Dublin': 'GMT Standard Time',
    - 'Europe/Gibraltar': 'W. Europe Standard Time',
    - 'Europe/Guernsey': 'GMT Standard Time',
    - 'Europe/Helsinki': 'FLE Standard Time',
    - 'Europe/Isle_of_Man': 'GMT Standard Time',
    - 'Europe/Istanbul': 'Turkey Standard Time',
    - 'Europe/Jersey': 'GMT Standard Time',
    - 'Europe/Kaliningrad': 'Kaliningrad Standard Time',
    - 'Europe/Kiev': 'FLE Standard Time',
    - 'Europe/Kirov': 'Russian Standard Time',
    - 'Europe/Lisbon': 'GMT Standard Time',
    - 'Europe/Ljubljana': 'Central Europe Standard Time',
    - 'Europe/London': 'GMT Standard Time',
    - 'Europe/Luxembourg': 'W. Europe Standard Time',
    - 'Europe/Madrid': 'Romance Standard Time',
    - 'Europe/Malta': 'W. Europe Standard Time',
    - 'Europe/Mariehamn': 'FLE Standard Time',
    - 'Europe/Minsk': 'Belarus Standard Time',
    - 'Europe/Monaco': 'W. Europe Standard Time',
    - 'Europe/Moscow': 'Russian Standard Time',
    - 'Europe/Oslo': 'W. Europe Standard Time',
    - 'Europe/Paris': 'Romance Standard Time',
    - 'Europe/Podgorica': 'Central Europe Standard Time',
    - 'Europe/Prague': 'Central Europe Standard Time',
    - 'Europe/Riga': 'FLE Standard Time',
    - 'Europe/Rome': 'W. Europe Standard Time',
    - 'Europe/Samara': 'Russia Time Zone 3',
    - 'Europe/San_Marino': 'W. Europe Standard Time',
    - 'Europe/Sarajevo': 'Central European Standard Time',
    - 'Europe/Saratov': 'Saratov Standard Time',
    - 'Europe/Simferopol': 'Russian Standard Time',
    - 'Europe/Skopje': 'Central European Standard Time',
    - 'Europe/Sofia': 'FLE Standard Time',
    - 'Europe/Stockholm': 'W. Europe Standard Time',
    - 'Europe/Tallinn': 'FLE Standard Time',
    - 'Europe/Tirane': 'Central Europe Standard Time',
    - 'Europe/Tiraspol': 'E. Europe Standard Time',
    - 'Europe/Ulyanovsk': 'Astrakhan Standard Time',
    - 'Europe/Uzhgorod': 'FLE Standard Time',
    - 'Europe/Vaduz': 'W. Europe Standard Time',
    - 'Europe/Vatican': 'W. Europe Standard Time',
    - 'Europe/Vienna': 'W. Europe Standard Time',
    - 'Europe/Vilnius': 'FLE Standard Time',
    - 'Europe/Volgograd': 'Volgograd Standard Time',
    - 'Europe/Warsaw': 'Central European Standard Time',
    - 'Europe/Zagreb': 'Central European Standard Time',
    - 'Europe/Zaporozhye': 'FLE Standard Time',
    - 'Europe/Zurich': 'W. Europe Standard Time',
    - 'GB': 'GMT Standard Time',
    - 'GB-Eire': 'GMT Standard Time',
    - 'GMT+0': 'UTC',
    - 'GMT-0': 'UTC',
    - 'GMT0': 'UTC',
    - 'Greenwich': 'UTC',
    - 'Hongkong': 'China Standard Time',
    - 'Iceland': 'Greenwich Standard Time',
    - 'Indian/Antananarivo': 'E. Africa Standard Time',
    - 'Indian/Chagos': 'Central Asia Standard Time',
    - 'Indian/Christmas': 'SE Asia Standard Time',
    - 'Indian/Cocos': 'Myanmar Standard Time',
    - 'Indian/Comoro': 'E. Africa Standard Time',
    - 'Indian/Kerguelen': 'West Asia Standard Time',
    - 'Indian/Mahe': 'Mauritius Standard Time',
    - 'Indian/Maldives': 'West Asia Standard Time',
    - 'Indian/Mauritius': 'Mauritius Standard Time',
    - 'Indian/Mayotte': 'E. Africa Standard Time',
    - 'Indian/Reunion': 'Mauritius Standard Time',
    - 'Iran': 'Iran Standard Time',
    - 'Israel': 'Israel Standard Time',
    - 'Jamaica': 'SA Pacific Standard Time',
    - 'Japan': 'Tokyo Standard Time',
    - 'Kwajalein': 'UTC+12',
    - 'Libya': 'Libya Standard Time',
    - 'MST7MDT': 'Mountain Standard Time',
    - 'Mexico/BajaNorte': 'Pacific Standard Time (Mexico)',
    - 'Mexico/BajaSur': 'Mountain Standard Time (Mexico)',
    - 'Mexico/General': 'Central Standard Time (Mexico)',
    - 'NZ': 'New Zealand Standard Time',
    - 'NZ-CHAT': 'Chatham Islands Standard Time',
    - 'Navajo': 'Mountain Standard Time',
    - 'PRC': 'China Standard Time',
    - 'PST8PDT': 'Pacific Standard Time',
    - 'Pacific/Apia': 'Samoa Standard Time',
    - 'Pacific/Auckland': 'New Zealand Standard Time',
    - 'Pacific/Bougainville': 'Bougainville Standard Time',
    - 'Pacific/Chatham': 'Chatham Islands Standard Time',
    - 'Pacific/Easter': 'Easter Island Standard Time',
    - 'Pacific/Efate': 'Central Pacific Standard Time',
    - 'Pacific/Enderbury': 'UTC+13',
    - 'Pacific/Fakaofo': 'UTC+13',
    - 'Pacific/Fiji': 'Fiji Standard Time',
    - 'Pacific/Funafuti': 'UTC+12',
    - 'Pacific/Galapagos': 'Central America Standard Time',
    - 'Pacific/Gambier': 'UTC-09',
    - 'Pacific/Guadalcanal': 'Central Pacific Standard Time',
    - 'Pacific/Guam': 'West Pacific Standard Time',
    - 'Pacific/Honolulu': 'Hawaiian Standard Time',
    - 'Pacific/Johnston': 'Hawaiian Standard Time',
    - 'Pacific/Kiritimati': 'Line Islands Standard Time',
    - 'Pacific/Kosrae': 'Central Pacific Standard Time',
    - 'Pacific/Kwajalein': 'UTC+12',
    - 'Pacific/Majuro': 'UTC+12',
    - 'Pacific/Marquesas': 'Marquesas Standard Time',
    - 'Pacific/Midway': 'UTC-11',
    - 'Pacific/Nauru': 'UTC+12',
    - 'Pacific/Niue': 'UTC-11',
    - 'Pacific/Norfolk': 'Norfolk Standard Time',
    - 'Pacific/Noumea': 'Central Pacific Standard Time',
    - 'Pacific/Pago_Pago': 'UTC-11',
    - 'Pacific/Palau': 'Tokyo Standard Time',
    - 'Pacific/Pitcairn': 'UTC-08',
    - 'Pacific/Ponape': 'Central Pacific Standard Time',
    - 'Pacific/Port_Moresby': 'West Pacific Standard Time',
    - 'Pacific/Rarotonga': 'Hawaiian Standard Time',
    - 'Pacific/Saipan': 'West Pacific Standard Time',
    - 'Pacific/Samoa': 'UTC-11',
    - 'Pacific/Tahiti': 'Hawaiian Standard Time',
    - 'Pacific/Tarawa': 'UTC+12',
    - 'Pacific/Tongatapu': 'Tonga Standard Time',
    - 'Pacific/Truk': 'West Pacific Standard Time',
    - 'Pacific/Wake': 'UTC+12',
    - 'Pacific/Wallis': 'UTC+12',
    - 'Poland': 'Central European Standard Time',
    - 'Portugal': 'GMT Standard Time',
    - 'ROC': 'Taipei Standard Time',
    - 'ROK': 'Korea Standard Time',
    - 'Singapore': 'Singapore Standard Time',
    - 'Turkey': 'Turkey Standard Time',
    - 'UCT': 'UTC',
    - 'US/Alaska': 'Alaskan Standard Time',
    - 'US/Aleutian': 'Aleutian Standard Time',
    - 'US/Arizona': 'US Mountain Standard Time',
    - 'US/Central': 'Central Standard Time',
    - 'US/Eastern': 'Eastern Standard Time',
    - 'US/Hawaii': 'Hawaiian Standard Time',
    - 'US/Indiana-Starke': 'Central Standard Time',
    - 'US/Michigan': 'Eastern Standard Time',
    - 'US/Mountain': 'Mountain Standard Time',
    - 'US/Pacific': 'Pacific Standard Time',
    - 'US/Samoa': 'UTC-11',
    - 'UTC': 'UTC',
    - 'Universal': 'UTC',
    - 'W-SU': 'Russian Standard Time',
    - 'Zulu': 'UTC'}