
    `gjb=                    t   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d*dZd+dZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 d,dZ ddd-dZ!d.dZ"e G d d              Z#d/d!Z$d/d"Z%d/d#Z&ed$e
ee'e	f      f   Z(	  G d% d&      Z)e G d' d(             Z*d0d)Z+y)1u=  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y)KeyboardButtonPermissionzAButton permission metadata. ``type=2`` means all users can click.   inttypec                    d| j                   iS )Nr   )r   selfs    T/root/.hermes/venv/lib/python3.12/site-packages/gateway/platforms/qqbot/keyboards.pyto_dictz KeyboardButtonPermission.to_dict>   s    		""    NreturnDict[str, Any])__name__
__module____qualname____doc__r   __annotations__r    r   r   r   r   9   s    KD#M#r   r   c                  X    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	y)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_limitc                |    | j                   | j                  | j                  j                         | j                  dS )N)r   r#   r&   r(   )r   r#   r&   r   r(   r   s    r   r   zKeyboardButtonAction.to_dictT   s3    IIII//113++	
 	
r   Nr   )
r   r   r   r   r   r   r   r&   r(   r   r   r   r   r!   r!   B   s9     I
I+0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y	)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   stylec                J    | j                   | j                  | j                  dS )Nr,   r-   r.   r0   r   s    r   r   z KeyboardButtonRenderData.to_dicti   s#    ZZ!//ZZ
 	
r   Nr   )r   r   r   r   r   r.   r   r   r   r   r+   r+   ]   s"     JE3N
r   r+   c                  F    e Zd ZU dZded<   ded<   ded<   dZded	<   dd
Zy)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_idc                    | j                   | j                  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kk))+	
 	
r   Nr   )r   r   r   r   r   r7   r   r   r   r   r2   r2   q   s)    
 	G))  Hc
r   r2   c                  2    e Zd ZU  ee      Zded<   ddZy)KeyboardRowr$   zList[KeyboardButton]buttonsc                `    d| j                   D cg c]  }|j                          c}iS c c}w )Nr<   )r<   r   )r   bs     r   r   zKeyboardRow.to_dict   s%    >AAIIK>??>   +Nr   )r   r   r   r   listr<   r   r   r   r   r   r;   r;      s    $)$$?G!?@r   r;   c                  2    e Zd ZU  ee      Zded<   ddZy)KeyboardContentr$   zList[KeyboardRow]rowsc                `    d| j                   D cg c]  }|j                          c}iS c c}w )NrC   )rC   r   )r   rs     r   r   zKeyboardContent.to_dict   s%    dii88998r?   Nr   )r   r   r   r   r@   rC   r   r   r   r   r   rB   rB      s    #D9D
9:r   rB   c                  6    e Zd ZU dZ ee      Zded<   ddZy)InlineKeyboarduF   Top-level keyboard payload — goes into ``MessageToCreate.keyboard``.r$   rB   contentc                :    d| j                   j                         iS )NrH   )rH   r   r   s    r   r   zInlineKeyboard.to_dict   s    4<<//122r   Nr   )	r   r   r   r   r   rB   rH   r   r   r   r   r   rG   rG      s    P$_EG_E3r   rG   c                ~    t         j                  | xs d      }|sy|j                  d      |j                  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button_datams     r   parse_approval_button_datarR      s:     	 1r2A771:qwwqz!!r   c                \    t         j                  | xs d      }|sy|j                  d      S )z<Parse update-prompt ``button_data`` into ``'y'`` or ``'n'``.rK   Nr'   )_UPDATE_PROMPT_RErM   rN   rO   s     r   parse_update_prompt_button_datarU      s+     1r2A771:r   c                N    t        | t        |||      t        d|      |      S )Nr0   r'   )r   r#   r9   )r2   r+   r!   btn_idr,   r-   r#   r.   r7   s         r   _make_callback_buttonrY      s4     ,'

 $6	 	r   T)allow_permanentc          
     "   t        dddt         |  ddd      g}|r)|j                  t        dd	d
t         |  ddd             |j                  t        dddt         |  ddd             t        t	        t        |      g            S )u]  Build the approval keyboard, hiding persistent scope when unavailable.

    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'   approvalrW   alwaysu   ⭐ 始终允许u   已始终允许z:allow-alwaysdenyu
   ❌ 拒绝u	   已拒绝z:denyr   r<   rC   rH   )rY   APPROVAL_BUTTON_PREFIXappendrG   rB   r;   )session_keyrZ   r<   s      r   build_approval_keyboardrf      s     	"4K*+K=Dj	
G ,#5EV*+K=Fj
 	
 NN(\&'}E:* 
 /G8T7U"VWWr   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_promptrW   nou
   ✗ 取消u	   已取消nr   r`   ra   rb   )rG   rB   r;   rY   UPDATE_PROMPT_PREFIXr   r   r   build_update_prompt_keyboardrn      so    )$*&1 45Q7!0 *#*&1 45Q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Zded<   y)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"   re   titlerK   descriptioncommand_previewcwd	tool_nameseverityx   r   timeout_secTboolrZ   N)r   r   r   r   r   rr   rs   rt   ru   rv   rx   rZ   r   r   r   rp   rp     sY    
 JKOSCMIsHcK OT r   rp   c                ^    | j                   s| j                  rt        |       S t        |       S )zDRender an :class:`ApprovalRequest` into the message body (markdown).)rs   rt   _build_exec_text_build_plugin_text)reqs    r   build_approval_textr~      s'    
cgg$$c""r   c                   ddg}| j                   r$| j                   d d }|j                  d| d       | j                  r|j                  d| j                          | j                  r7| j                  | j                   k7  r|j                  d| j                          | j                  r|j                  d| j                          |j                  d       |j                  d	| j
                   d
       dj                  |      S )Nu   🔐 **命令执行审批**rK   i,  z```
z
```u   📁 目录:    📋    📝    ⏱️ 超时:     秒
)rs   rd   rt   rq   rr   rx   join)r}   linespreviews      r   r{   r{   '  s    5r:E
%%ds+uWIU+,
ww}SWWI./
yySYY#"5"55uSYYK()
uS__-./	LL	LL?3??"348999Ur   c                   | j                   dk(  rdn| j                   dk(  rdnd}| ddg}|j                  d| j                          | j                  r|j                  d	| j                          | j                  r|j                  d
| j                          |j                  d       |j                  d| j
                   d       dj                  |      S )Ncriticalu   🔴infou   🔵u   🟡u    **审批请求**rK   r   r   u   🔧 工具: r   r   r   )rv   rd   rq   rr   ru   rx   r   )r}   iconr   s      r   r|   r|   7  s    ,,*,||v-V 	
  & 12B7E	LL5$%
uS__-./
}}}S]]O45	LL	LL?3??"348999Ur   .c                  H    e Zd ZdZ	 d	 	 	 	 	 	 	 ddZ	 d	 	 	 	 	 	 	 	 	 ddZy)	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``.
    c                .    || _         || _        || _        y N)	_post_c2c_post_group_log_tag)r   post_c2c
post_grouplog_tags       r   __init__zApprovalSender.__init__Y  s     "%r   Nc                2  K   t        |      }t        |j                        }t        j	                  d| j
                  |||j                         	 |dk(  r| j                  ||||       d{    nD|dk(  r| j                  ||||       d{    n"t        j                  d| j
                  |       yt        j	                  d| j
                  ||       y7 l7 L# t        $ r-}t        j                  d	| j
                  |||       Y d}~yd}~ww xY w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…)c2cNrN   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~   rf   re   loggerr   r   r   r   warning	Exceptionerror)r   	chat_typechat_idr}   msg_idtextkeyboardexcs           r   sendzApprovalSender.sendc  s      #3'*3??;GMM9gs	

	E!nnWdFHEEEg%&&wfhGGG=MM9 KK5y'  FG  	LLCy'3 	s`   ADC -C.!C C&C 6D7"C DC C 	D'#D
DDD)QQBot)r   PostMessageFnr   r   r   r"   r   Noner   )
r   r"   r   r"   r}   rp   r   Optional[str]r   ry   )r   r   r   r   r   r   r   r   r   r   r   Q  sq     	   "  	 
 
  !%,, , 	,
 , 
,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y)InteractionEventzParsed ``INTERACTION_CREATE`` event payload.

    See https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/event-emit.html
    rK   r"   r3   r   r   r   r   scenegroup_openidgroup_member_openiduser_openid
channel_idguild_idrP   	button_idresolver_user_idc                R    | j                   xs | j                  xs | 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     BLSD#M4Is4E3OIL#!!KJHcKIsc
 
r   r   c                   | j                  d      xs i }|j                  d      xs i }t        | j                  dd      xs d      }ddddj                  |d	      }t        t        | j                  d
d	            t        |j                  dd      xs d      ||t        | j                  dd	            t        | j                  dd	            t        | j                  dd	            t        | j                  dd	            t        | j                  dd	            t        |j                  dd	            t        |j                  dd	            t        |j                  dd	                  S )z<Parse a raw ``INTERACTION_CREATE`` dispatch payload (``d``).r#   resolvedr   r   guildrN   r   )r   r'   r   rK   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"   )rawdata_rawr   
scene_coder   s        r   parse_interaction_eventr     s1   wwv$"H||J'-2HSWW[!,12JG.22:rBEswwtR !fa(-A.45(=r BCr23sww|R01SWWZ,-]B78hll;34X\\)R89 r   )rP   r"   r   zOptional[tuple[str, str]])rP   r"   r   r   )rX   r"   r,   r"   r-   r"   r#   r"   r.   r   r7   r"   r   r2   )re   r"   rZ   ry   r   rG   )r   rG   )r}   rp   r   r"   )r   r   r   r   ),r   
__future__r   loggingredataclassesr   r   typingr   r   r   r	   r
   r   	getLoggerr   r   rc   rm   compilerL   rT   r   r!   r+   r2   r;   rB   rG   rR   rU   rY   rf   rn   rp   r~   r{   r|   r"   r   r   r   r   r   r   r   <module>r      s  : #  	 ( A A			8	$ $ ' 
 BJJ4 
 BJJ78 
 # # # 
 
 
4 
 
 
& 
 
 
( @ @ @ : : : 3 3 3
"  	
   ( JN X<< ! ! !.# & iS#X778> >F "
 "
 "
Jr   