U
    hK                     @   sh   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mZm	Z	m
Z
 dZdd ZG d	d
 d
eZdS )    N   )
ChatGetter   )helpersutilserrorsgMbP?c                    s   t   fdd}|S )Nc                    s    | j rtd | f||S )Nz%The conversation was cancelled before)
_cancelledasyncioZCancelledError)selfargskwargsf C/tmp/pip-unpacked-wheel-c81u5j2r/telethon/tl/custom/conversation.pywrapper   s    
z"_checks_cancelled.<locals>.wrapper)	functoolswraps)r   r   r   r   r   _checks_cancelled   s    r   c                   @   s  e Zd ZdZdZdZdd Zedd Zedd Z	ed/d
dZ
d0d	dddZd1d	dddZdd Zd2d	dddZd3d	dddZd	dddZdd Zdd Zdd Zdd  Zd!d" Zed#d$ Zd4d%d&Zd'd( Zd)d* Zd+d, Zd-d. ZejZejZ d	S )5ConversationaT  
    Represents a conversation inside an specific chat.

    A conversation keeps track of new messages since it was
    created until its exit and easily lets you query the
    current state.

    If you need a conversation across two or more chats,
    you should use two conversations and synchronize them
    as you better see fit.
    r   c                C   s   t j| |d tj| _t jd7  _|| _|| _|| _d | _t	 | _
d| _g | _d| _|| _d | _i | _i | _i | _i | _i | _|| _d| _i | _|r| j| _ni | _i | _d S )N)
input_chatr   r   F)r   __init__r   _id_counterZ_id_client_timeout_total_timeout
_total_dueset	_outgoing_last_outgoing	_incoming_last_incoming_max_incoming
_last_read_custom_pending_responses_pending_replies_pending_edits_pending_reads
_exclusiver   _response_indices_reply_indices_edit_dates)r
   clientr   timeoutZtotal_timeoutZmax_messagesZ	exclusiveZreplies_are_responsesr   r   r   r   )   s2    
zConversation.__init__c                    sV   | j j| jf||I dH }t|tr*|n|f}| jdd |D  |d j| _|S )z
        Sends a message in the context of this conversation. Shorthand
        for `telethon.client.messages.MessageMethods.send_message` with
        ``entity`` already set.
        Nc                 s   s   | ]}|j V  qd S Nid.0mr   r   r   	<genexpr>]   s     z,Conversation.send_message.<locals>.<genexpr>)	r   send_message_input_chat
isinstancelistr   updater1   r   r
   r   r   sentmsr   r   r   r7   Q   s    
zConversation.send_messagec                    sV   | j j| jf||I dH }t|tr*|n|f}| jdd |D  |d j| _|S )z
        Sends a file in the context of this conversation. Shorthand
        for `telethon.client.uploads.UploadMethods.send_file` with
        ``entity`` already set.
        Nc                 s   s   | ]}|j V  qd S r/   r0   r2   r   r   r   r5   m   s     z)Conversation.send_file.<locals>.<genexpr>r6   )	r   	send_filer8   r9   r:   r   r;   r1   r   r<   r   r   r   r?   a   s    
zConversation.send_fileNc                 C   sD   |dkr"| j r| j d j}q2d}nt|ts2|j}| jj| j|dS )a,  
        Marks as read the latest received message if ``message is None``.
        Otherwise, marks as read until the given message (or message ID).

        This is equivalent to calling `client.send_read_acknowledge
        <telethon.client.messages.MessageMethods.send_read_acknowledge>`.
        Nr6   r   )max_id)r    r1   r9   intr   Zsend_read_acknowledger8   r
   messager   r   r   	mark_readq   s    	
 zConversation.mark_readr.   c                C   s   |  || j| j|dd S )a  
        Gets the next message that responds to a previous one. This is
        the method you need most of the time, along with `get_edit`.

        Args:
            message (`Message <telethon.tl.custom.message.Message>` | `int`, optional):
                The message (or the message ID) for which a response
                is expected. By default this is the last sent message.

            timeout (`int` | `float`, optional):
                If present, this `timeout` (in seconds) will override the
                per-action timeout defined for the conversation.

        .. code-block:: python

            async with client.conversation(...) as conv:
                await conv.send_message('Hey, what is your name?')

                response = await conv.get_response()
                name = response.text

                await conv.send_message('Nice to meet you, {}!'.format(name))
        c                 S   s   dS NTr   xyr   r   r   <lambda>       z+Conversation.get_response.<locals>.<lambda>)_get_messager*   r%   r
   rC   r.   r   r   r   get_response   s       zConversation.get_responsec                C   s   |  || j| j|dd S )zR
        Gets the next message that explicitly replies to a previous one.
        c                 S   s   | j o| j j|kS r/   )reply_toreply_to_msg_idrG   r   r   r   rJ      rK   z(Conversation.get_reply.<locals>.<lambda>)rL   r+   r&   rM   r   r   r   	get_reply   s       zConversation.get_replyc                 C   s   t   }| |}||krRt| jD ]\}}	|	j|kr$|||<  qRq$t| j||< | jj }
|| }|t| jk r| j| }	||	|r||  d7  < |
	|	 |
S |
||< | 
|
||||S )a  
        Gets the next desired message under the desired condition.

        Args:
            target_message (`object`):
                The target message for which we want to find another
                response that applies based on `condition`.

            indices (`dict`):
                This dictionary remembers the last ID chosen for the
                input `target_message`.

            pending (`dict`):
                This dictionary remembers {msg_id: Future} to be set
                once `condition` is met.

            timeout (`int`):
                The timeout (in seconds) override to use for this operation.

            condition (`callable`):
                The condition callable that checks if an incoming
                message is a valid response.
        r   )time_get_message_id	enumerater    r1   lenr   loopcreate_future
set_result_get_result)r
   Ztarget_messageindicespendingr.   	condition
start_time	target_idiincomingfutureZlast_idxr   r   r   rL      s$    




zConversation._get_messagec                   s   t   }| || jd t fdd| jD dd dd}| jj }|r|j	
  kr|j	
 | j< || |S || j< | |||| jS )z
        Awaits for an edit after the last message to arrive.
        The arguments are the same as those for `get_response`.
        r   c                 3   s0   | ](}|j r|jkr|j   kr|V  qd S r/   )	edit_dater1   	timestamp)r3   rH   Ztarget_dater^   r   r   r5      s
    
z(Conversation.get_edit.<locals>.<genexpr>c                 S   s
   | j  S r/   )rb   rc   )rH   r   r   r   rJ      rK   z'Conversation.get_edit.<locals>.<lambda>N)keydefault)rR   rS   r,   getminr    r   rV   rW   rb   rc   rX   r'   rY   )r
   rC   r.   r]   Zearliest_editra   r   rd   r   get_edit   s    



zConversation.get_editc                C   s^   t   }| jj }| |}| jdkr2|d | _| j|kr@dS || j|< | |||| j|S )z
        Awaits for the sent message to be marked as read. Note that
        receiving a response doesn't imply the message was read, and
        this action will also trigger even without a response.
        Nr   )rR   r   rV   rW   rS   r#   r(   rY   )r
   rC   r.   r]   ra   r^   r   r   r   	wait_read  s    




zConversation.wait_readc             
      s   t   }t|tr| }|| jI dH  tj}t jd7  _| jj }||f| j	|< z| |||| j	|I dH W S | j	
|d X dS )a$  
        Waits for a custom event to occur. Timeouts still apply.

        .. note::

            **Only use this if there isn't another method available!**
            For example, don't use `wait_event` for new messages,
            since `get_response` already exists, etc.

        Unless you're certain that your code will run fast enough,
        generally you should get a "handle" of this special coroutine
        before acting. In this example you will see how to wait for a user
        to join a group with proper use of `wait_event`:

        .. code-block:: python

            from telethon import TelegramClient, events

            client = TelegramClient(...)
            group_id = ...

            async def main():
                # Could also get the user id from an event; this is just an example
                user_id = ...

                async with client.conversation(user_id) as conv:
                    # Get a handle to the future event we'll wait for
                    handle = conv.wait_event(events.ChatAction(
                        group_id,
                        func=lambda e: e.user_joined and e.user_id == user_id
                    ))

                    # Perform whatever action in between
                    await conv.send_message('Please join this group before speaking to me!')

                    # Wait for the event we registered above to fire
                    event = await handle

                    # Continue with the conversation
                    await conv.send_message('Thanks!')

        This way your event can be registered before acting,
        since the response may arrive before your event was
        registered. It depends on your use case since this
        also means the event can arrive before you send
        a previous action.
        Nr   )rR   r9   typeresolver   r   _custom_counterrV   rW   r$   poprY   )r
   eventr.   r]   counterra   r   r   r   
wait_event  s    0
zConversation.wait_eventc                    sh   t | j D ]T\}\}}t|}|| }|r||}t|rL|I d H }|r|| | j|= qd S r/   )r:   r$   itemsrk   filterinspectisawaitablerX   )r
   Zbuiltre   ZevfutZev_typeinstrs   r   r   r   _check_customZ  s    



zConversation._check_customc                 C   s   |j }|j| jks|jrd S t| j| jkr>| td d S | j| t	| j
 D ]*\}}t| j| j|< || | j
|= qXt	| j D ]<\}}|jr||jjkrt| j| j|< || | j|= qd S )NzToo many incoming messages)rC   chat_idoutrU   r    r"   _cancel_all
ValueErrorappendr:   r%   rr   r*   rX   r&   rO   rP   r+   )r
   responsemsg_idra   r   r   r   _on_new_messageh  s     


zConversation._on_new_messagec                 C   s   |j }|j| jks|jrd S t| jD ]"\}}|j|jkr&|| j|<  qJq&t| j D ]d\}}||jk rX|j	
 }|| j|dkr| j|  t7  < n|j	
 | j|< || | j|= qXd S )Nr   )rC   ry   rz   rT   r    r1   r:   r'   rr   rb   rc   r,   rg   _EDIT_COLLISION_DELTArX   )r
   rC   r_   r4   r   ra   Zedit_tsr   r   r   _on_edit  s    



zConversation._on_editc                 C   sV   |j | j ks|jrd S |j| _t| j D ]$\}}|| jkr,|d | j|= q,d S rF   )ry   Zinboxr@   r#   r:   r(   rr   rX   )r
   ro   r   r[   r   r   r   _on_read  s    

zConversation._on_readc                 C   s4   |d k	rt |tr|S |jS | jr(| jS tdd S )NzNo message was sent previously)r9   rA   r1   r   r|   rB   r   r   r   rS     s
    zConversation._get_message_idc                 C   sP   | j }|d kr| j}|d k	r*t||| }tj||tdkr@d n
|t  dS )NinfrE   )r   r   rh   r	   wait_forfloatrR   )r
   ra   r]   r.   r[   r^   Zduer   r   r   rY     s    zConversation._get_resultc                 C   st   d| _ t| j | j | j D ]}|r:|| q&|  q&| j	 D ] \}}|rf|| qN|  qNd S rF   )
r   	itertoolschainr%   valuesr&   r'   Zset_exceptioncancelr$   )r
   	exceptionr[   _rv   r   r   r   r{     s    
zConversation._cancel_allc              	      s   | j | jI d H | _t| j| _t| j}| j j| }| jrN|rNt	
 ||  d| _d| _d| _| j| j| j| j| j| j| j| j| jf	D ]}|  q| jrt | j | _n
td| _| S )NFr   r   )r   Zget_input_entityr8   r   Zget_peer
_chat_peerget_peer_id_conversationsr)   r   ZAlreadyInConversationErroraddr   r   r!   r   r    r%   r&   r'   r*   r+   r,   r$   clearr   rR   r   r   )r
   ry   conv_setdr   r   r   
__aenter__  s4    

     

zConversation.__aenter__c                 C   s   |    dS )z
        Cancels the current conversation. Pending responses and subsequent
        calls to get a response will raise ``asyncio.CancelledError``.

        This method is synchronous and should not be awaited.
        N)r{   )r
   r   r   r   r     s    zConversation.cancelc                    s2   | j | jI dH }| j j| D ]}|  q dS )z
        Calls `cancel` on *all* conversations in this chat.

        Note that you should ``await`` this method, since it's meant to be
        used outside of a context manager, and it needs to resolve the chat.
        N)r   r   r8   r   r   )r
   ry   convr   r   r   
cancel_all  s    zConversation.cancel_allc                    s<   t | j}| jj| }||  |s0| jj|= |   d S r/   )r   r   r   r   r   discardr{   )r
   exc_typeexc_valexc_tbry   r   r   r   r   	__aexit__  s    

zConversation.__aexit__)N)N)N)N)N)N)!__name__
__module____qualname____doc__r   rm   r   r   r7   r?   rD   rN   rQ   rL   ri   rj   rq   rx   r   r   r   rS   rY   r{   r   r   r   r   r   Z_sync_enter	__enter__Z
_sync_exit__exit__r   r   r   r   r      s<   (

	=B

		r   )r	   r   rt   r   rR   Z
chatgetterr    r   r   r   r   r   r   r   r   r   r   <module>   s   
