U
    hu                     @   s  d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlZd dl	Z	d dl
mZ d dlZddlmZmZmZ ddlmZmZ ddlmZmZ ddlmZmZ ddlmZ dd	lmZ ejrd
dlm Z  ej!ej"gej"f Z#G dd dZ$G dd dZ%dS )    N)deque   )eventsutilserrors)EventBuilderEventCommon)types	functions)GapErrorPrematureEndReason)get_running_loop)__version__   )TelegramClientc                   @   s   e Zd ZddddZddddZddddZded	d
dZd%deedddZ	d&deee
dddZdddddZddddZddddZdd ZddddZdddd Zddd!d"Zddd#d$ZdS )'UpdateMethodsr   selfc                    sj   zTz:| tj I d H  | jI d H }| jd k	r4| j|W W S  tk
rP   Y nX W 5 |   I d H  X d S N)
disconnectr
   updatesGetStateRequestdisconnected_updates_errorKeyboardInterrupt)r   result r   ;/tmp/pip-unpacked-wheel-c81u5j2r/telethon/client/updates.py_run_until_disconnected    s    


z%UpdateMethods._run_until_disconnectedc                    s$   | | _ |r | tj I dH  dS )z
        Change the value of `receive_updates`.

        This is an `async` method, because in order for Telegram to start
        sending updates again, a request must be made.
        N)Z_no_updatesr
   r   r   )r   Zreceive_updatesr   r   r   set_receive_updates-   s    z!UpdateMethods.set_receive_updatesc                 C   sR   | j  r|  S z0z| j |  W W S  tk
r>   Y nX W 5 |   X dS )a  
        Runs the event loop until the library is disconnected.

        It also notifies Telegram that we want to receive updates
        as described in https://core.telegram.org/api/updates.
        If an unexpected error occurs during update handling,
        the client will disconnect and said error will be raised.

        Manual disconnections can be made by calling `disconnect()
        <telethon.client.telegrambaseclient.TelegramBaseClient.disconnect>`
        or sending a ``KeyboardInterrupt`` (e.g. by pressing ``Ctrl+C`` on
        the console window running the script).

        If a disconnection error occurs (i.e. the library fails to reconnect
        automatically), said error will be raised through here, so you have a
        chance to ``except`` it on your own code.

        If the loop is already running, this method returns a coroutine
        that you should await on your own code.

        .. note::

            If you want to handle ``KeyboardInterrupt`` in your code,
            simply run the event loop in your code too in any way, such as
            ``loop.run_forever()`` or ``await client.disconnected`` (e.g.
            ``loop.run_until_complete(client.disconnected)``).

        Example
            .. code-block:: python

                # Blocks the current task here until a disconnection occurs.
                #
                # You will still receive updates, since this prevents the
                # script from exiting.
                await client.run_until_disconnected()
        N)loopZ
is_runningr   r   Zrun_until_completer   r   r   r   r   run_until_disconnected8   s    %

z$UpdateMethods.run_until_disconnected)r   eventc                    s    fdd}|S )a7  
        Decorator used to `add_event_handler` more conveniently.


        Arguments
            event (`_EventBuilder` | `type`):
                The event builder class or instance to be used,
                for instance ``events.NewMessage``.

        Example
            .. code-block:: python

                from telethon import TelegramClient, events
                client = TelegramClient(...)

                # Here we use client.on
                @client.on(events.NewMessage)
                async def handler(event):
                    ...
        c                    s    |   | S r   )add_event_handler)fr"   r   r   r   	decorator|   s    z#UpdateMethods.on.<locals>.decoratorr   )r   r"   r&   r   r%   r   ong   s    zUpdateMethods.onN)r   callbackr"   c                 C   sb   t |}|dk	r0|D ]}| j||f qdS t|trB| }n|sNt  }| j||f dS )aS  
        Registers a new event handler callback.

        The callback will be called when the specified event occurs.

        Arguments
            callback (`callable`):
                The callable function accepting one parameter to be used.

                Note that if you have used `telethon.events.register` in
                the callback, ``event`` will be ignored, and instead the
                events you previously registered will be used.

            event (`_EventBuilder` | `type`, optional):
                The event builder class or instance to be used,
                for instance ``events.NewMessage``.

                If left unspecified, `telethon.events.raw.Raw` (the
                :tl:`Update` objects with no further processing) will
                be passed instead.

        Example
            .. code-block:: python

                from telethon import TelegramClient, events
                client = TelegramClient(...)

                async def handler(event):
                    ...

                client.add_event_handler(handler, events.NewMessage)
        N)r   Z_get_handlers_event_buildersappend
isinstancetypeRaw)r   r(   r"   Zbuildersr   r   r   r#      s    $

zUpdateMethods.add_event_handler)r   r(   r"   returnc                 C   sj   d}|rt |tst|}t| j}|rf|d8 }| j| \}}||kr$|rTt ||r$| j|= |d7 }q$|S )a  
        Inverse operation of `add_event_handler()`.

        If no event is given, all events for this callback are removed.
        Returns how many callbacks were removed.

        Example
            .. code-block:: python

                @client.on(events.Raw)
                @client.on(events.NewMessage)
                async def handler(event):
                    ...

                # Removes only the "Raw" handling
                # "handler" will still receive "events.NewMessage"
                client.remove_event_handler(handler, events.Raw)

                # "handler" will stop receiving anything
                client.remove_event_handler(handler)
        r   r   )r+   r,   lenr)   )r   r(   r"   foundievcbr   r   r   remove_event_handler   s    

z"UpdateMethods.remove_event_handlerz5typing.Sequence[typing.Tuple[Callback, EventBuilder]])r   r.   c                 C   s   dd | j D S )a  
        Lists all registered event handlers.

        Returns
            A list of pairs consisting of ``(callback, event)``.

        Example
            .. code-block:: python

                @client.on(events.NewMessage(pattern='hello'))
                async def on_greeting(event):
                    '''Greets someone'''
                    await event.reply('Hi')

                for callback, event in client.list_event_handlers():
                    print(id(callback), type(event))
        c                 S   s   g | ]\}}||fqS r   r   ).0r"   r(   r   r   r   
<listcomp>   s     z5UpdateMethods.list_event_handlers.<locals>.<listcomp>)r)   r   r   r   r   list_event_handlers   s    z!UpdateMethods.list_event_handlersc                    s   | j t I dH  dS )a  
        "Catches up" on the missed updates while the client was offline.
        You should call this method after registering the event handlers
        so that the updates it loads can by processed by your script.

        This can also be used to forcibly fetch new updates if there are any.

        Example
            .. code-block:: python

                await client.catch_up()
        N)_updates_queueputr	   ZUpdatesTooLongr   r   r   r   catch_up   s    zUpdateMethods.catch_upc                    s   j dkp j  }d  _zh jr4  I d H  t }  r|r jrd 	|
 I d H  q:|r: j 	|
 } j| | jj qdq:t j jkr jt dt j j     j fdd t j jkrtd  jt dt j j  j }|r$ jt d z |I d H }W n tjtjtj t!fk
r } z. jt dt"|j  j#  W Y q:W 5 d }~X Y n2 tj$tj%fk
r( } zR jt d	t"|j  j#  |r| _ & I d H  W Y qW Y q:W 5 d }~X Y n tj't(j)fk
r } z> jt *d
|  j#  | _ & I d H  W Y qW 5 d }~X Y nV t+k
r } z6 jt dt"|j| t,-dI d H  W Y q:W 5 d }~X Y nX  j.| j\}}}	|r jt d |/ 0|||	 q: j1 j}|rƈ jt d|j2j3 z |I d H }W n tj$tj%fk
r } zb jt *d|j2j3t"|j  j4|t5j6 j |r| _ & I d H  W Y qW Y q:W 5 d }~X Y n tj't(j)fk
rd } zN jt *d|j2j3|  j4|t5j6 j | _ & I d H  W Y qW 5 d }~X Y n tj7tj8tjtjtj t!fk
r } z> jt *d|j2j3t"|j  j4|t5j6 j W Y q:W 5 d }~X Y n tj9tj:fk
r     jt d|j2j3  j4|t5j; j Y q:Y n\ t+k
rz } z< jt d|j2j3t"|j| t,-dI d H  W Y q:W 5 d }~X Y nX  j<|| j\}}}	|r jt d|j2j3 |/ 0|||	 q: j= }
|
t> ?  }|dkr:zt,@ jAB |I d H }W n, t,jCk
r.    jt d Y q:Y nX nq:g }z jD| j|\}}	W n tEk
rl   Y q:Y nX |/ 0|||	 q:W nf t,jFk
r   Y nP tGk
r } z0 jt HdtI d | _ & I d H  W 5 d }~X Y nX d S )NTzAIn-memory entity cache limit reached (%s/%s), flushing to sessionc                    s   |  j jkp|  jjkS r   )_mb_entity_cacheself_id_message_boxmap)idr   r   r   <lambda>%      z,UpdateMethods._update_loop.<locals>.<lambda>z\in-memory entities exceed entity_cache_limit after flushing; consider setting a larger limitz9In-memory entity cache at %s/%s after flushing to sessionz&Getting difference for account updatesz9Cannot get difference since Telegram is having issues: %sz<Cannot get difference since the account is not logged in: %szJCannot get difference since the account is likely misusing the session: %sz7Cannot get difference since the network is down: %s: %s   z"Got difference for account updatesz)Getting difference for channel %s updateszKCannot get difference for channel %s since the account is not logged in: %szYCannot get difference for channel %s since the account is likely misusing the session: %sz{Getting difference for channel updates %s caused %s; ending getting difference prematurely until server issues are resolvedzEAccount is now banned in %d so we can no longer fetch updates from itzFCannot get difference for channel %d since the network is down: %s: %sz%Got difference for channel %d updatesr   z#Timeout waiting for updates expiredz9Fatal error handling updates (this is a bug in Telethon vz, please report it))JZ_authorizedr=   Zis_emptyr   Z	_catch_upr:   r   is_connectedZ_sequential_updates_dispatch_updatepopleftr    Zcreate_taskZ_event_handler_tasksaddZadd_done_callbackdiscardr/   r;   Z_entity_cache_limit_log__name__info_save_states_and_entitiesZretainwarningswarnZget_differencedebugr   ZServerErrorZTimedOutErrorZFloodWaitError
ValueErrorr,   Zend_differenceZUnauthorizedErrorZAuthKeyErrorr   ZTypeNotFoundErrorsqlite3ZOperationalErrorwarningOSErrorasynciosleepZapply_differenceextend_preprocess_updatesZget_channel_differenceZchannelZ
channel_idZend_channel_differencer   ZTEMPORARY_SERVER_ISSUESZ PersistentTimestampOutdatedErrorZPersistentTimestampInvalidErrorZChannelPrivateErrorZChannelInvalidErrorZBANNEDZapply_channel_differenceZcheck_deadlinesr   timewait_forr8   getTimeoutErrorZprocess_updatesr   CancelledError	Exception	exceptionr   )r   Zwas_once_logged_inZupdates_to_dispatchZtaskZget_diffZdiffer   userschatsdeadlineZdeadline_delay	processedr   r   r   _update_loop  sN   









 

 
 

  

zUpdateMethods._update_loopc                 C   s8   | j || dd t||D }|D ]
}||_q(|S )Nc                 S   s   i | ]}t ||qS r   )r   Zget_peer_id)r5   xr   r   r   
<dictcomp>  s    z5UpdateMethods._preprocess_updates.<locals>.<dictcomp>)r;   rU   	itertoolschain	_entities)r   r   r_   r`   entitiesur   r   r   rV     s    
z!UpdateMethods._preprocess_updatesc              	      s   dd }|   rztj| jddI d H  W qW nB tjk
rD   Y n. tjk
rZ   Y d S  tk
rp   Y qY nX |  I d H  | j	 sqz| j
|  W n ttjfk
r   Y d S X |   | j  qd S )Nc                   S   s   t ddS )Nl         l            )random	randranger   r   r   r   r@     rA   z/UpdateMethods._keepalive_loop.<locals>.<lambda><   )timeout)rC   rS   rX   r   rZ   r[   r\   Z_clean_exported_sendersZ_senderZ_transport_connectedZ_keepalive_pingConnectionErrorrK   sessionsave)r   Zrndr   r   r   _keepalive_loop  s.     

zUpdateMethods._keepalive_loopc                    s  d }| j js8z| jddI d H  W n tk
r6   Y nX t| ||}| j D ]l}|D ]b}|tj }|rr|	| |tj
 }|r|| |tj }|r|| |jrV||I d H  qVqN| jD ]J\}}|t| }	|	sq|js|| I d H  ||	}
t|
r|
I d H }
|
sqz||	I d H  W q tjk
rh   t|dt|}| jt d| Y q tjk
r   t|dt|}| jt d|t|	j Y  qY q tk
r } z>t |t!j"r| # rt|dt|}| jt $d| W 5 d }~X Y qX qd S )NT)Z
input_peerrI   EEvent handler "%s" already has an open conversation, ignoring new one=Event handler "%s" stopped chain of propagation for event %s.Unhandled exception on %s)%r;   r<   get_merR   EventBuilderDictZ_conversationsvaluesr   Z
NewMessageZ_on_new_messageZMessageEditedZ_on_editZMessageReadZ_on_readZ_customZ_check_customr)   r,   resolvedresolvefilterinspectisawaitabler   AlreadyInConversationErrorgetattrreprrH   rI   rN   StopPropagationr\   r+   rS   r[   rC   r]   )r   updateothersZbuiltZconv_setconvr2   builderr(   r"   r{   namer^   r   r   r   rD   	  sf    









 zUpdateMethods._dispatch_updatec                    sZ  | j D ]L\}}t|tjrqt||js,q|jsB|| I dH  ||}t	|r`|I dH }|sfqz||I dH  W q t
jk
r   t|dt|}| jt d| Y q tjk
r   t|dt|}| jt d|t|j Y  qVY q tk
rR } z>t|tjr |  rBt|dt|}| jt d| W 5 d}~X Y qX qdS )zO
        Dispatches a single, out-of-order event. Used by `AlbumHack`.
        NrI   rs   rt   ru   )r)   r+   r   r-   Eventry   rz   r{   r|   r}   r   r~   r   r   rH   rI   rN   r   r,   r\   rS   r[   rC   r]   )r   r"   r   r(   r{   r   r^   r   r   r   _dispatch_eventL  s@    




 zUpdateMethods._dispatch_eventc              
      s   z|   I d H  W n: tk
rL } z| jt dt|| W 5 d }~X Y nX d S z2| jt d |  I d H  | jt d W nX tj	k
r } z| jt d| W 5 d }~X Y n$ tk
r   | jt 
d Y nX d S )Nz:Error executing high-level request after reconnect: %s: %sz/Asking for the current state after reconnect...z#Successfully fetched missed updatesz0Failed to get missed updates after reconnect: %rzCUnhandled exception while getting update difference after reconnect)rv   r\   rH   rI   rQ   r,   rJ   r:   r   ZRPCErrorr]   )r   r^   r   r   r   _handle_auto_reconnectu  s,     

z$UpdateMethods._handle_auto_reconnect)N)N)rI   
__module____qualname__r   r   r!   r   r'   Callbackr#   intr4   r7   r:   rc   rV   rr   rD   r   r   r   r   r   r   r      s8   / 4  ' X(C)r   c                   @   s&   e Zd ZdZddddZdd ZdS )	rw   zI
    Helper "dictionary" to return events from types and cache them.
    r   )clientc                 C   s   || _ || _|| _d S r   )r   r   r   )r   r   r   r   r   r   r   __init__  s    zEventBuilderDict.__init__c                 C   s   z| j | W S  tk
rz   || j| j| jj }| j |< t|trf| j|_	| jj
|_
|| j n|rr| j|_| Y S X d S r   )__dict__KeyErrorbuildr   r   r   Z_self_idr+   r   Zoriginal_updaterh   Z_set_clientZ_client)r   r   r"   r   r   r   __getitem__  s      

zEventBuilderDict.__getitem__N)rI   r   r   __doc__r   r   r   r   r   r   rw     s   rw   )&rS   r|   rf   rk   sysrW   	tracebacktypingloggingrL   collectionsr   rP    r   r   r   Zevents.commonr   r   tlr	   r
   Z_updatesr   r   Zhelpersr   versionr   TYPE_CHECKINGZtelegramclientr   CallableAnyr   r   rw   r   r   r   r   <module>   s6        