
    i>                       d Z ddlmZ ddlZddlZddlmZmZ ddlm	Z	m
Z
mZmZmZmZ  ej        e          ZdZdZ ej        d          Z ej        d	          Ze G d
 d                      Ze G d d                      Ze G d d                      Ze G d d                      Ze G d d                      Ze G d d                      Ze G d d                      Zd8dZd9dZd:d&Z d;d(Z!d<d)Z"e G d* d+                      Z#d=d-Z$d=d.Z%d=d/Z&ed0e
ee'e	f                  f         Z(	  G d1 d2          Z)e G d3 d4                      Z*d>d7Z+dS )?u=  QQ Bot inline keyboards + approval / update-prompt senders.

QQ Bot v2 supports attaching inline keyboards to outbound messages. When a
user clicks a button, the platform dispatches an ``INTERACTION_CREATE``
gateway event containing the button's ``data`` payload. The bot must ACK the
interaction promptly via ``PUT /interactions/{id}`` or the user sees an
error indicator on the button.

This module provides:

- :class:`InlineKeyboard` + button dataclasses — serialized into the
  ``keyboard`` field of the outbound message body.
- :func:`build_approval_keyboard` — 3-button ✅ once / ⭐ always / ❌ deny
  keyboard for tool-approval flows.
- :func:`build_update_prompt_keyboard` — Yes/No keyboard for update confirms.
- :func:`parse_approval_button_data` / :func:`parse_update_prompt_button_data`
  — decode the ``button_data`` payload from ``INTERACTION_CREATE``.
- :class:`ApprovalRequest` + :class:`ApprovalSender` — high-level helper that
  builds an approval message with keyboard and posts it to a c2c / group chat.

``button_data`` formats::

    approve:<session_key>:<decision>      # decision = allow-once|allow-always|deny
    update_prompt:<answer>                # answer = y|n

Ported from WideLee's qqbot-agent-sdk v1.2.2 (``approval.py`` + ``dto.py``
keyboard types). Authorship preserved via Co-authored-by.
    )annotationsN)	dataclassfield)Any	AwaitableCallableDictListOptionalzapprove:zupdate_prompt:z-^approve:(.+):(allow-once|allow-always|deny)$z^update_prompt:(y|n)$c                  *    e Zd ZU dZdZded<   d	dZdS )
KeyboardButtonPermissionzAButton permission metadata. ``type=2`` means all users can click.   inttypereturnDict[str, Any]c                    d| j         iS )Nr   )r   selfs    F/home/piyush/.hermes/hermes-agent/gateway/platforms/qqbot/keyboards.pyto_dictz KeyboardButtonPermission.to_dict>   s    	""    Nr   r   )__name__
__module____qualname____doc__r   __annotations__r    r   r   r   r   9   s=         KKDMMMM# # # # # #r   r   c                  `    e Zd ZU dZded<   ded<    ee          Zded<   d	Zded
<   ddZ	dS )KeyboardButtonActionuv  What happens when the button is clicked.

    :param type: ``1`` (Callback — triggers ``INTERACTION_CREATE``) or
        ``2`` (Link — opens a URL).
    :param data: Payload delivered in ``data.resolved.button_data`` when
        ``type=1``.
    :param permission: :class:`KeyboardButtonPermission`.
    :param click_limit: Max clicks per user (``1`` = single-use).
    r   r   strdatadefault_factoryr   
permission   click_limitr   r   c                \    | j         | j        | j                                        | j        dS )N)r   r#   r&   r(   )r   r#   r&   r   r(   r   s    r   r   zKeyboardButtonAction.to_dictT   s2    II/1133+	
 
 	
r   Nr   )
r   r   r   r   r   r   r   r&   r(   r   r   r   r   r!   r!   B   s           IIIIII+050, , ,J     K
 
 
 
 
 
r   r!   c                  >    e Zd ZU dZded<   ded<   dZded<   dd
ZdS )KeyboardButtonRenderDatazVisual rendering of a button.

    :param label: Pre-click label.
    :param visited_label: Post-click label (button stays greyed in place).
    :param style: ``0`` = grey, ``1`` = blue.
    r"   labelvisited_labelr'   r   styler   r   c                ,    | j         | j        | j        dS )Nr,   r-   r.   r0   r   s    r   r   z KeyboardButtonRenderData.to_dicti   s!    Z!/Z
 
 	
r   Nr   )r   r   r   r   r   r.   r   r   r   r   r+   r+   ]   sX           JJJENNNN
 
 
 
 
 
r   r+   c                  H    e Zd ZU dZded<   ded<   ded<   dZded	<   ddZdS )KeyboardButtonu   One button in a keyboard.

    :param group_id: Buttons sharing a ``group_id`` are mutually exclusive —
        clicking one greys the rest.
    r"   idr+   render_datar!   actiondefaultgroup_idr   r   c                    | j         | j                                        | j                                        | j        dS )Nr3   r4   r5   r7   )r3   r4   r   r5   r7   r   s    r   r   zKeyboardButton.to_dict}   s>    '+3355k))++	
 
 	
r   Nr   )r   r   r   r   r   r7   r   r   r   r   r2   r2   q   sh          
 GGG))))    H
 
 
 
 
 
r   r2   c                  :    e Zd ZU  ee          Zded<   ddZdS )	KeyboardRowr$   zList[KeyboardButton]buttonsr   r   c                (    dd | j         D             iS )Nr<   c                6    g | ]}|                                 S r   r   ).0bs     r   
<listcomp>z'KeyboardRow.to_dict.<locals>.<listcomp>   s     >>>AAIIKK>>>r   r<   r   s    r   r   zKeyboardRow.to_dict   s    >>>>>??r   Nr   )r   r   r   r   listr<   r   r   r   r   r   r;   r;      sN         $)E$$?$?$?G????@ @ @ @ @ @r   r;   c                  :    e Zd ZU  ee          Zded<   ddZdS )	KeyboardContentr$   zList[KeyboardRow]rowsr   r   c                (    dd | j         D             iS )NrG   c                6    g | ]}|                                 S r   r?   )r@   rs     r   rB   z+KeyboardContent.to_dict.<locals>.<listcomp>   s     888888r   rG   r   s    r   r   zKeyboardContent.to_dict   s    88di88899r   Nr   )r   r   r   r   rD   rG   r   r   r   r   r   rF   rF      sH         #eD999D9999: : : : : :r   rF   c                  >    e Zd ZU dZ ee          Zded<   d	dZdS )
InlineKeyboarduF   Top-level keyboard payload — goes into ``MessageToCreate.keyboard``.r$   rF   contentr   r   c                8    d| j                                         iS )NrN   )rN   r   r   s    r   r   zInlineKeyboard.to_dict   s    4<//1122r   Nr   )	r   r   r   r   r   rF   rN   r   r   r   r   r   rM   rM      sN         PP$u_EEEGEEEE3 3 3 3 3 3r   rM   button_datar"   r   Optional[tuple[str, str]]c                    t                               | pd          }|sdS |                    d          |                    d          fS )zParse approval ``button_data`` into ``(session_key, decision)``.

    :param button_data: Raw ``data.resolved.button_data`` from
        ``INTERACTION_CREATE``.
    :returns: ``(session_key, decision)`` or ``None`` if not an approval button.
     Nr'   r   )_APPROVAL_DATA_REmatchgrouprP   ms     r   parse_approval_button_datarY      sH     	 1r22A t771::qwwqzz!!r   Optional[str]c                l    t                               | pd          }|sdS |                    d          S )z<Parse update-prompt ``button_data`` into ``'y'`` or ``'n'``.rS   Nr'   )_UPDATE_PROMPT_RErU   rV   rW   s     r   parse_update_prompt_button_datar]      s7     1r22A t771::r   btn_idr,   r-   r#   r.   r   r7   c                f    t          | t          |||          t          d|          |          S )Nr0   r'   )r   r#   r9   )r2   r+   r!   r^   r,   r-   r#   r.   r7   s         r   _make_callback_buttonra      sN     ,'
 
 

 $666	 	 	 	r   session_keyc                   t          t          t          t          dddt           |  ddd          t          dd	d
t           |  ddd          t          dddt           |  ddd          g          g                    S )u<  Build the 3-button approval keyboard.

    Layout: ``[✅ 允许一次] [⭐ 始终允许] [❌ 拒绝]`` — all three share
    ``group_id='approval'`` so clicking one greys out the rest.

    :param session_key: Embedded into ``button_data`` so the decision
        routes back to the right pending approval.
    allowu   ✅ 允许一次u	   已允许z:allow-oncer'   approvalr`   alwaysu   ⭐ 始终允许u   已始终允许z:allow-alwaysdenyu
   ❌ 拒绝u	   已拒绝z:denyr   rC   rK   rN   )rM   rF   r;   ra   APPROVAL_BUTTON_PREFIX)rb   s    r   build_approval_keyboardrj      s     )&0&1 6PPPP!+   *'0&7 6RRRR!+   *%*&1 6JJJJ!+  #%   
 
 
   r   c                     t          t          t          t          dddt           ddd          t          dd	d
t           ddd          g          g                    S )z8Build a Yes/No keyboard for update confirmation prompts.yesu
   ✓ 确认u	   已确认yr'   update_promptr`   nou
   ✗ 取消u	   已取消nr   rC   rK   rh   )rM   rF   r;   ra   UPDATE_PROMPT_PREFIXr   r   r   build_update_prompt_keyboardrr      s    )$*&1 4777!0   *#*&1 4777!0  %   
 
 
   r   c                  |    e Zd ZU dZded<   ded<   dZded<   dZded<   dZded<   dZded	<   dZ	ded
<   dZ
ded<   dS )ApprovalRequesta  Structured approval-request display data.

    :param session_key: Routes the decision back to the waiting caller.
    :param title: Short title at the top.
    :param description: Optional longer description.
    :param command_preview: Command text (exec approvals).
    :param cwd: Working directory (exec approvals).
    :param tool_name: Tool name (plugin approvals).
    :param severity: ``'critical' | 'info' | ''``.
    :param timeout_sec: Seconds until the approval expires.
    r"   rb   titlerS   descriptioncommand_previewcwd	tool_nameseverityx   r   timeout_secN)r   r   r   r   r   rv   rw   rx   ry   rz   r|   r   r   r   rt   rt     s         
 
 JJJKOCMMMMIHKr   rt   reqc                Z    | j         s| j        rt          |           S t          |           S )zDRender an :class:`ApprovalRequest` into the message body (markdown).)rw   rx   _build_exec_text_build_plugin_text)r}   s    r   build_approval_textr   ,  s2    
 %cg %$$$c"""r   c                   ddg}| j         r(| j         d d         }|                    d| d           | j        r|                    d| j                    | j        r-| j        | j         k    r|                    d| j                    | j        r|                    d| j                    |                    d           |                    d	| j         d
           d                    |          S )Nu   🔐 **命令执行审批**rS   i,  z```
z
```u   📁 目录:    📋    📝    ⏱️ 超时:     秒
)rw   appendrx   ru   rv   r|   join)r}   linespreviews      r   r   r   3  s   5r:E
 -%dsd++W+++,,,
w 0.SW..///
y *SY#"555(SY(()))
 0.S_..///	LL	LL83?88899999Ur   c                   | j         dk    rdn| j         dk    rdnd}| ddg}|                    d| j                    | j        r|                    d	| j                    | j        r|                    d
| j                    |                    d           |                    d| j         d           d                    |          S )Ncriticalu   🔴infou   🔵u   🟡u    **审批请求**rS   r   r   u   🔧 工具: r   r   r   )rz   r   ru   rv   ry   r|   r   )r}   iconr   s      r   r   r   C  s    ,*,,|v--VV 	
  222B7E	LL$$$%%%
 0.S_..///
} 64S]44555	LL	LL83?88899999Ur   .c                  *    e Zd ZdZ	 ddd
Z	 dddZdS )ApprovalSenderzSend an approval-request message with an inline keyboard.

    Decoupled from the adapter via callables so it can be unit-tested in
    isolation. Pass the adapter's ``_send_message_with_keyboard`` helper
    (or any equivalent) as ``post_message``.
    QQBotpost_c2cPostMessageFn
post_grouplog_tagr"   r   Nonec                0    || _         || _        || _        d S N)	_post_c2c_post_group_log_tag)r   r   r   r   s       r   __init__zApprovalSender.__init__e  s     "%r   N	chat_typechat_idr}   rt   msg_idrZ   boolc                6  K   t          |          }t          |j                  }t                              d| j        |||j                   	 |dk    r|                     ||||           d{V  nH|dk    r|                     ||||           d{V  n#t                              d| j        |           dS t                              d| j        ||           dS # t          $ r.}t          
                    d	| j        |||           Y d}~dS d}~ww xY w)
aP  Send an approval message to *chat_id*.

        :param chat_type: ``'c2c'`` or ``'group'``.
        :param chat_id: User openid or group openid.
        :param req: :class:`ApprovalRequest`.
        :param msg_id: Reply-to message id (required for passive messages).
        :returns: ``True`` on success, ``False`` on failure.
        u9   [%s] Sending approval request to %s:%s (session=%.20s…)c2cNrV   z'[%s] Approval: unsupported chat_type %rFz#[%s] Approval message sent to %s:%sTz1[%s] Failed to send approval message to %s:%s: %s)r   rj   rb   loggerr   r   r   r   warning	Exceptionerror)r   r   r   r}   r   textkeyboardexcs           r   sendzApprovalSender.sendo  sc      #3''*3?;;GM9gs	
 	
 	

	E!!nnWdFHEEEEEEEEEEg%%&&wfhGGGGGGGGGG=M9   uKK5y'   4 	 	 	LLCy'3   55555	s   A+C  <"C   
D*#DD)r   )r   r   r   r   r   r"   r   r   r   )
r   r"   r   r"   r}   rt   r   rZ   r   r   )r   r   r   r   r   r   r   r   r   r   r   ]  sZ          	          !%, , , , , , ,r   r   c                      e Zd ZU dZdZded<   	 dZded<   	 dZded<   	 dZded	<   	 dZ	ded
<   dZ
ded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   edd            ZdS )InteractionEventzParsed ``INTERACTION_CREATE`` event payload.

    See https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/event-emit.html
    rS   r"   r3   r   r   r   r   scenegroup_openidgroup_member_openiduser_openid
channel_idguild_idrP   	button_idresolver_user_idr   c                ,    | j         p| j        p| j        S )u@   Best available operator openid (group → member; c2c → user).)r   r   r   r   s    r   operator_openidz InteractionEvent.operator_openid  s%     $ %%$	
r   N)r   r"   )r   r   r   r   r3   r   r   r   r   r   r   r   r   r   rP   r   r   propertyr   r   r   r   r   r     s          BLLLLSDMMMM4I4EOOOOIL!!!!!KJHKI
 
 
 X
 
 
r   r   rawr   c                   |                      d          pi }|                     d          pi }t          |                      dd          pd          }dddd                     |d	          }t          t          |                      d
d	                    t          |                     dd          pd          ||t          |                      dd	                    t          |                      dd	                    t          |                      dd	                    t          |                      dd	                    t          |                      dd	                    t          |                     dd	                    t          |                     dd	                    t          |                     dd	                              S )z<Parse a raw ``INTERACTION_CREATE`` dispatch payload (``d``).r#   resolvedr   r   guildrV   r   )r   r'   r   rS   r3   r   r   r   r   r   r   rP   r   user_id)r3   r   r   r   r   r   r   r   r   rP   r   r   )getr   r   r"   )r   data_rawr   
scene_coder   s        r   parse_interaction_eventr     s   wwv$"H||J''-2HSWW[!,,122JG..22:rBBEswwtR  !!fa((-A..4455(=r B BCCr2233sww|R0011SWWZ,,--]B7788hll;3344X\\)R8899   r   )rP   r"   r   rQ   )rP   r"   r   rZ   )r^   r"   r,   r"   r-   r"   r#   r"   r.   r   r7   r"   r   r2   )rb   r"   r   rM   )r   rM   )r}   rt   r   r"   )r   r   r   r   ),r   
__future__r   loggingredataclassesr   r   typingr   r   r   r	   r
   r   	getLoggerr   r   ri   rq   compilerT   r\   r   r!   r+   r2   r;   rF   rM   rY   r]   ra   rj   rr   rt   r   r   r   r"   r   r   r   r   r   r   r   <module>r      s   : # " " " " "  				 ( ( ( ( ( ( ( ( A A A A A A A A A A A A A A A A		8	$	$ $ ' 
 BJ4  
 BJ788 
 # # # # # # # # 
 
 
 
 
 
 
 
4 
 
 
 
 
 
 
 
& 
 
 
 
 
 
 
 
( @ @ @ @ @ @ @ @ : : : : : : : : 3 3 3 3 3 3 3 3
" 
" 
" 
"      (( ( ( (V   <        ,# # # #       & iS#X778> > > > > > > >F "
 "
 "
 "
 "
 "
 "
 "
J     r   