
    `gj                    *   d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	 ddl
mZ ddlmZmZmZmZmZmZ dd	lmZmZ dd
lmZ  ej0                  e      Z G d de      Z	 ddlZddZ efddZ!ef	 	 	 	 	 ddZ"ddZ#dZ$dddZ%y# eef$ r dZY ,w xY w)u\  
QQBot scan-to-configure (QR code onboard) module.

Mirrors the Feishu onboarding pattern: synchronous HTTP + a single public
entry-point ``qr_register()`` that handles the full flow (create task →
display QR code → poll → decrypt credentials).

Calls the ``q.qq.com`` ``create_bind_task`` / ``poll_bind_result`` APIs to
generate a QR-code URL and poll for scan completion.  On success the caller
receives the bot's *app_id*, *client_secret* (decrypted locally), and the
scanner's *user_openid* — enough to fully configure the QQBot gateway.

Reference: https://bot.q.qq.com/wiki/develop/api-v2/
    )annotationsN)IntEnum)OptionalTuple)quote   )ONBOARD_API_TIMEOUTONBOARD_CREATE_PATHONBOARD_POLL_INTERVALONBOARD_POLL_PATHPORTAL_HOSTQR_URL_TEMPLATE)decrypt_secretgenerate_bind_key)get_api_headersc                       e Zd ZdZdZdZdZdZy)
BindStatusz/Status codes returned by ``_poll_bind_result``.r   r         N)__name__
__module____qualname____doc__NONEPENDING	COMPLETEDEXPIRED     R/root/.hermes/venv/lib/python3.12/site-packages/gateway/platforms/qqbot/onboard.pyr   r   +   s    9DGIGr   r   c                    t         y	 t        j                  t         j                  j                  d      }|j	                  |        |j                  d       |j                  d       y# t        $ r Y yw xY w)zDTry to render a QR code in the terminal. Returns True if successful.Fr   )error_correctionborderT)fit)invert)_qrcode_modQRCode	constantsERROR_CORRECT_Madd_datamakeprint_ascii	Exception)urlqrs     r    
_render_qrr0   >   sq    
(22BB
 	C
D
d# s   A$A. .	A:9A:c                   ddl }dt         t         }t               }|j	                  | d      5 }|j                  |d|it                     }|j                          |j                         }ddd       j                  d      dk7  rt        |j                  d	d
            |j                  di       j                  d      }|st        d      t        j                  d|       ||fS # 1 sw Y   xY w)zCreate a bind task and return *(task_id, aes_key_base64)*.

    Raises:
        RuntimeError: If the API returns a non-zero ``retcode``.
    r   Nhttps://Ttimeoutfollow_redirectskeyjsonheadersretcodemsgzcreate_bind_task faileddatatask_idz-create_bind_task: missing task_id in responsezcreate_bind_task ok: task_id=%s)httpxr   r
   r   Clientpostr   raise_for_statusr8   getRuntimeErrorloggerdebug)r4   r>   r.   r6   clientrespr<   r=   s           r    _create_bind_taskrH   T   s     [M"5!6
7C

C	g	= {{3eS\?;L{Myy{
 xx	a488E+DEFFhhvr"&&y1GJKK
LL2G<C< s   ?C//C8c                &   ddl }dt         t         }|j                  |d      5 }|j	                  |d| it                     }|j                          |j                         }ddd       j                  d      dk7  rt        |j                  d	d
            |j                  di       }t        |j                  dd            t        |j                  dd            |j                  dd      |j                  dd      fS # 1 sw Y   xY w)zPoll the bind result for *task_id*.

    Returns:
        A 4-tuple of ``(status, bot_appid, bot_encrypt_secret, user_openid)``.

    Raises:
        RuntimeError: If the API returns a non-zero ``retcode``.
    r   Nr2   Tr3   r=   r7   r:   r;   zpoll_bind_result failedr<   status	bot_appid bot_encrypt_secretuser_openid)r>   r   r   r?   r@   r   rA   r8   rB   rC   r   str)r=   r4   r>   r.   rF   rG   r<   ds           r    _poll_bind_resultrQ   o   s     [M"3!4
5C	g	= {{3i%9?CT{Uyy{
 xx	a488E+DEFFA1551%&AEE+r"#	"B'	mR 	  s   ?DDc                @    t        j                  t        |             S )z3Build the QR-code target URL for a given *task_id*.r=   )r   formatr   rS   s    r    build_connect_urlrU      s    !!%.99r   r   c                   t        j                         | z   }t        t        dz         D ]u  }	 t	               \  }}t        |      }t                t        |      rt        d|        nt        d|        t        d       t                t        j                         |k  r	 t        |      \  }}}	}
|t        j                  k(  r=t!        |	|      }t                t        d| d       |
rt        d	|
        |||
d
c S |t        j"                  k(  r@|t        k\  rt        j                  dt                yt        d|dz    dt         d       /t        j                  t               t        j                         |k  rt        j                  d|         y y# t
        $ r!}t        j                  d|       Y d}~ yd}~ww xY w# t
        $ r t        j                  t               Y hw xY w)ug  Run the QQBot scan-to-configure QR registration flow.

    Mirrors ``feishu.qr_register()``: handles create → display → poll →
    decrypt in one call.  Unexpected errors propagate to the caller.

    :returns:
        ``{"app_id": ..., "client_secret": ..., "user_openid": ...}`` on
        success, or ``None`` on failure / expiry / cancellation.
    r   z.[QQBot onboard] Failed to create bind task: %sNz7  Scan the QR code above, or open this URL directly:
  z'  Open this URL in QQ on your phone:
  z>  Tip: pip install qrcode  to display a scannable QR code herez  QR scan complete! (App ID: )z  Scanner's OpenID: )app_idclient_secretrN   u6   [QQBot onboard] QR code expired %d times — giving upz#
  QR code expired, refreshing... (/z([QQBot onboard] Poll timed out after %ds)time	monotonicrange_MAX_REFRESHESrH   r-   rD   warningrU   printr0   rQ   sleepr   r   r   r   r   )timeout_secondsdeadlinerefresh_countr=   aes_keyexcr.   rJ   rX   encrypted_secretrN   rY   s               r    qr_registerrh      s    ~~/1H~12 2	02GW
  ( 	c?LSERS<SEBCRS nn)@QRY@Z= 0+
 --- ./? I5fXQ?@0>?$%2#.  +++ N2NN#[]kl<]Q=N<OqQ_P``abcJJ,-5 nn): NNEWe2h a  	NNKSQ	&  

01s)   F$&G$	G-G		G"G76G7)r.   rO   returnbool)r4   floatri   zTuple[str, str])r=   rO   r4   rk   ri   z Tuple[BindStatus, str, str, str])r=   rO   ri   rO   )iX  )rb   intri   zOptional[dict])&r   
__future__r   loggingr[   enumr   typingr   r   urllib.parser   r(   r	   r
   r   r   r   r   cryptor   r   utilsr   	getLoggerr   rD   r   qrcoder&   ImportError	TypeErrorr0   rH   rQ   rU   r^   rh   r   r   r    <module>rx      s    #    "   6 "			8	$  
, (; : ) &B: @E 	Y Ks   !B 	BB