
    i'P              
         U d Z ddlm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mZ ddlZ ej        e          ZdZdZdZdZd	d	d
ddddddZded<   i ddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:Zd;ed<<   dyd@Zdzd{dDZd|dFZd}dIZd~dJZddLZddNZ ddOZ!ddPZ"ddQZ#efddUZ$	 	 ddd`Z%	 	 	 	 dddjZ&ddpZ'	 	 	 	 	 	 dddvZ(	 	 dddwZ)ddxZ*dS )u=  
yuanbao_media.py — 元宝平台媒体处理模块

提供 COS 上传、文件下载、TIM 媒体消息构建等功能。
移植自 TypeScript 版 media.ts（yuanbao-openclaw-plugin），
使用 httpx 替代 cos-nodejs-sdk-v5，避免引入额外 SDK 依赖。

COS 上传流程：
  1. 调用 genUploadInfo 获取临时凭证（tmpSecretId/tmpSecretKey/sessionToken）
  2. 用临时凭证通过 HMAC-SHA1 签名构建 Authorization 头
  3. HTTP PUT 上传到 COS

TIM 消息体构建：
  - buildImageMsgBody() → TIMImageElem
  - buildFileMsgBody()  → TIMFileElem
    )annotationsN)OptionalAnyz/api/resource/genUploadInfozyuanbao.tencent.com2   T               )
image/jpegz	image/jpg	image/gif	image/png	image/bmp
image/webp
image/heic
image/tiffzdict[str, int]_MIME_TO_IMAGE_FORMAT.jpgr   .jpeg.pngr   .gifr   .webpr   .bmpr   .heicr   .tiffr   .icozimage/x-iconz.pdfzapplication/pdfz.doczapplication/mswordz.docxzGapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentz.xlszapplication/vnd.ms-excelz.xlsxzAapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetz.pptzapplication/vnd.ms-powerpointz.pptxzIapplication/vnd.openxmlformats-officedocument.presentationml.presentationz.txtz
text/plainzapplication/zipzapplication/x-tarzapplication/gzipz
audio/mpegz	video/mp4z	audio/wavz	audio/oggz
video/webm)z.zipz.tarz.gzz.mp3z.mp4z.wavz.oggz.webmdict[str, str]_EXT_TO_MIMEfilenamestrreturnc                    t           j                            |           d                                         }t                              |d          S )u*   根据文件扩展名猜测 MIME 类型。application/octet-stream)ospathsplitextlowerr   get)r   exts     D/home/ubuntu/.hermes/hermes-agent/gateway/platforms/yuanbao_media.pyguess_mime_typer,   Y   s?    
'

8
$
$R
(
.
.
0
0CC!;<<<     	mime_typeboolc                    |                     d          rdS t          j                            |           d                                         }|dv S )u   判断是否为图片类型。image/Tr#   >	   r   r   r   r   r   r   r   r   r   )
startswithr%   r&   r'   r(   )r   r/   r*   s      r+   is_imager4   _   sO    H%% t
'

8
$
$R
(
.
.
0
0C^^^r-   intc                \    t                               |                                 d          S )u    获取 TIM 图片格式编号。r   )r   r)   r(   )r/   s    r+   get_image_formatr7   g   s"     $$Y__%6%6<<<r-   databytesc                N    t          j        |                                           S )u    计算 MD5 十六进制摘要。)hashlibmd5	hexdigestr8   s    r+   md5_hexr?   l   s    ;t&&(((r-   c                 *    t          j        d          S )u(   生成随机文件 ID（32 位 hex）。   )secrets	token_hex r-   r+   generate_file_idrE   q   s    R   r-   Optional[dict[str, int]]c                z    t          |           p,t          |           pt          |           pt          |           S )u   
    解析图片宽高（支持 JPEG/PNG/GIF/WebP），无需第三方依赖。
    返回 {"width": w, "height": h} 或 None（无法识别）。
    )_parse_png_size_parse_jpeg_size_parse_gif_size_parse_webp_sizer>   s    r+   parse_image_sizerL   y   sF     	 	"D!!	"4  	" D!!	r-   bufc                    t          |           dk     rd S | d d         dk    rd S t          j        d| dd                   d         }t          j        d| dd                   d         }||dS )	N   r
   s   PNGz>IrA      r   widthheightlenstructunpack)rM   whs      r+   rH   rH      sx    
3xx"}}t
2A2w*tdC2J''*AdC2J''*A!$$$r-   c           	     :   t          |           dk     s| d         dk    s| d         dk    rd S d}|t          |           dz
  k     r| |         dk    r|dz  }(| |dz            }|dv rWt          j        d	| |d
z   |dz                      d         }t          j        d	| |dz   |dz                      d         }||dS |dz   t          |           k     r0|dt          j        d	| |dz   |dz                      d         z   z  }nn|t          |           dz
  k     d S )Nr
   r   r   r      r   	   )      z>H      rQ   r	   rT   )rM   imarkerrY   rX   s        r+   rI   rI      s>   
3xx!||s1v~~Q4t	A
c#hhl

q6T>>FAQU\!!dCAq1u$566q9AdCAq1u$566q9A!,,,q53s88V]4QUAE\):;;A>>>AA c#hhl

 4r-   c                   t          |           dk     rd S | d d                             dd          }|dvrd S t          j        d| dd                   d	         }t          j        d| dd                   d	         }||d
S )N
      asciireplaceerrors)GIF87aGIF89a<H   r   rQ   rU   decoderV   rW   )rM   sigrX   rY   s       r+   rJ   rJ      s    
3xx"}}t
bqb'...
3
3C
&&&tdC!H%%a(AdC"I&&q)A!$$$r-   c                R   t          |           dk     rd S | d d         dk    s| dd         dk    rd S | dd                             dd	          }|d
k    rt          |           dk    ru| d         dk    ri| d         dk    r]| d         dk    rQt          j        d| dd                   d         dz  }t          j        d| dd                   d         dz  }||dS n|dk    r[t          |           dk    rG| d         dk    r;t          j        d| dd                   d         }|dz  dz   }|dz	  dz  dz   }||dS nd|dk    r^t          |           dk    rK| d         | d         dz  z  | d         dz  z  dz   }| d         | d         dz  z  | d          dz  z  dz   }||dS d S )!NrA   r
   s   RIFFrm      s   WEBPrf   rg   rh   zVP8          rO   r      *   rl         r   i?  rQ   VP8LrP   /   z<I      VP8X      rn   )rM   chunkrX   rY   bitss        r+   rK   rK      s   
3xx"}}t
2A2w'S2Y'11t2Jgi88Es88r>>c"goo#b'T//c"gQUoodC2J//2V;AdC2J//2V;A!,,,	&s88r>>c"goo=s2b5z2215D!#A"*&!+A!,,,	&s88r>>RCGqL)SW];q@ARCGqL)SW];q@A!,,,4r-   urlmax_size_mbtuple[bytes, str]c                  K   |dz  dz  }t          j        dd          4 d{V }	 |                    |            d{V }t          |j                            dd          pd          }|dk    r#||k    rt          d|dz  dz  d	d
| d          n# t           j        $ r Y nw xY w|                    d|           4 d{V }|	                                 |j                            dd          
                    d          d                                         }g }d}	|                    d          2 3 d{V }
|	t          |
          z  }	|	|k    rt          d| d          |                    |
           H6 	 ddd          d{V  n# 1 d{V swxY w Y   d                    |          }||fcddd          d{V  S # 1 d{V swxY w Y   dS )uR  
    下载 URL 内容，返回 (bytes, content_type)。

    Args:
        url:          HTTP(S) URL
        max_size_mb:  最大允许大小（MB），超过则抛出异常

    Returns:
        (data_bytes, content_type_string)

    Raises:
        ValueError:  内容超过大小限制
        httpx.HTTPError: 网络/HTTP 错误
    i   g      >@T)timeoutfollow_redirectsNzcontent-lengthr   u   文件过大: z.1fz MB > z MBGETcontent-typer.   ;i   u   文件过大: 已超过 u
    MB 限制r-   )httpxAsyncClientheadr5   headersr)   
ValueErrorHTTPStatusErrorstreamraise_for_statussplitstripaiter_bytesrU   appendjoin)r   r   	max_bytesclientr   content_lengthrespcontent_typechunks
downloadedr   r8   s               r+   download_urlr      sd     $ d"T)I EEE " " " " " " "	S))))))))D !1!12BA!F!F!K!LLN!!ny&@&@ ]^d%:T%A]]][]]]   $ 	 	 	D	 ==,, 	% 	% 	% 	% 	% 	% 	%!!###<++NB??EEcJJ1MSSUUL"$FJ#//66 % % % % % % %ec%jj(
	))$J;JJJ   e$$$$  76	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% xx\!;" " " " " " " " " " " " " " " " " " " " " " " " " " " " " "sg   GA.BGB*'G)B**G	A4F=FAFG
F#	#G&F#	'G
G"G  methodr&   paramsr   	secret_id
secret_key
start_timeOptional[int]expire_secondsc                   t          t          j                              }|p| d|p||z    }	t          j        |                    d          |	                    d          t
          j                                                  }
t          d |	                                D                       }t          d |	                                D                       }d
                    d |D                       }d
                    d |D                       }d
                    d |D                       }d
                    d	 |D                       }d

                    |                                 |||dg          }t          j        |                    d                                                    }d

                    d|	|dg          }t          j        |
                    d          |                    d          t
          j                                                  }d| d|	 d|	 d| d| d| S )u  
    构建 COS 请求签名（q-sign-algorithm=sha1 方案）。
    参考：https://cloud.tencent.com/document/product/436/7778

    Args:
        method:         HTTP 方法（小写，如 "put"）
        path:           URL 路径（URL encode 后的小写）
        params:         URL 查询参数 dict（用于签名）
        headers:        参与签名的请求头 dict（key 需小写）
        secret_id:      临时 SecretId（tmpSecretId）
        secret_key:     临时 SecretKey（tmpSecretKey）
        start_time:     签名起始 Unix 时间戳（默认 now）
        expire_seconds: 签名有效期（秒，默认 3600）

    Returns:
        Authorization header 值（完整字符串）
    r   zutf-8c              3     K   | ]I\  }}|                                 t          j                            t	          |          d           fV  JdS r.   safeNr(   urllibparsequoter    .0kvs      r+   	<genexpr>z_cos_sign.<locals>.<genexpr>&  sQ      hhQUQRTUAGGIIv|'9'9#a&&r'9'J'JLhhhhhhr-   c              3     K   | ]I\  }}|                                 t          j                            t	          |          d           fV  JdS r   r   r   s      r+   r   z_cos_sign.<locals>.<genexpr>'  sQ      jjRVRSUVQWWYY(:(:3q66(:(K(KMjjjjjjr-   c              3      K   | ]	\  }}|V  
d S NrD   r   r   _s      r+   r   z_cos_sign.<locals>.<genexpr>)  s&      ::DAqa::::::r-   &c              3  *   K   | ]\  }}| d | V  dS =NrD   r   s      r+   r   z_cos_sign.<locals>.<genexpr>*  s0      ??AQ****??????r-   c              3      K   | ]	\  }}|V  
d S r   rD   r   s      r+   r   z_cos_sign.<locals>.<genexpr>+  s&      88A1888888r-   c              3  *   K   | ]\  }}| d | V  dS r   rD   r   s      r+   r   z_cos_sign.<locals>.<genexpr>,  s0      @@AQ****@@@@@@r-   
r.   sha1zq-sign-algorithm=sha1&q-ak=z&q-sign-time=z&q-key-time=z&q-header-list=z&q-url-param-list=z&q-signature=)r5   timehmacnewencoder;   r   r=   sorteditemsr   r(   )r   r&   r   r   r   r   r   r   nowq_sign_timesign_keysorted_paramssorted_headersurl_param_list
url_paramsheader_list
header_strhttp_stringsha1_of_httpstring_to_sign	signatures                        r+   	_cos_signr      sy   6 dikk

C&3OO**;~)MOOK x'""7##  ikk	  hhY_YeYeYgYghhhhhMjjZaZgZgZiZijjjjjNXX::M:::::N???????J((8888888K@@@@@@@J))
  K < 2 27 ; ;<<FFHHLYY
	   N   g&&  ikk	 	$	$ 	$#	$ 	$ #	$ 	$ &		$ 	$
 ,	$ 	$ "	$ 	$r-   fileapp_key
api_domaintokenfile_idOptional[str]bot_id	route_envdictc           	       K   |t                      }|                    d           t           }d||p| dd}|r||d<   ||ddd	}	t          j        d
          4 d{V }
|
                    ||	|           d{V }|                                 |                                }ddd          d{V  n# 1 d{V swxY w Y   |                    d          }|dk    r+|)t          d| d|                    dd                     |                    d          p|ddg}fd|D             }|rt          d|           S )u  
    调用 genUploadInfo 接口获取 COS 临时密钥及上传配置。

    Args:
        app_key:        应用 Key（用于 X-ID 头）
        api_domain:     API 域名（如 https://bot.yuanbao.tencent.com）
        token:          当前有效的签票 token（X-Token 头）
        filename:       待上传的文件名（含扩展名）
        file_id:        客户端生成的唯一文件 ID（不传则自动生成）
        bot_id:         Bot 账号 ID（用于 X-ID 头）

    Returns:
        COS 上传配置 dict，包含以下字段：
            bucketName         (str)  — COS Bucket 名称
            region             (str)  — COS 地域
            location           (str)  — 上传 Key（对象路径）
            encryptTmpSecretId (str)  — 临时 SecretId
            encryptTmpSecretKey(str)  — 临时 SecretKey
            encryptToken       (str)  — SessionToken
            startTime          (int)  — 凭证起始时间戳（Unix）
            expiredTime        (int)  — 凭证过期时间戳（Unix）
            resourceUrl        (str)  — 上传后的公网访问 URL
            resourceID         (str)  — 资源 ID（可选）

    Raises:
        RuntimeError: 接口返回非 0 code 或字段缺失
    N/zapplication/jsonweb)Content-TypezX-TokenzX-IDzX-SourcezX-Route-EnvlocalDocr.   )fileNamefileIddocFrom	docOpenIdg      .@r   )jsonr   coder   u   genUploadInfo 失败: code=z, msg=msgr8   
bucketNamelocationc                >    g | ]}                     |          |S rD   )r)   )r   fr8   s     r+   
<listcomp>z'get_cos_credentials.<locals>.<listcomp>  s(    ===Q!=q===r-   u2   genUploadInfo 返回字段不完整: 缺少字段 )
rE   rstripUPLOAD_INFO_PATHr   r   postr   r   r)   RuntimeError)r   r   r   r   r   r   r   
upload_urlr   bodyr   r   resultr   required_fieldsmissingr8   s                   @r+   get_cos_credentialsr   S  sI     H "$$%%c**>,<>>J +!'	 G  +!*	 D  ... - - - - - - -&[[$[HHHHHHHH!%- - - - - - - - - - - - - - - - - - - - - - - - - - -
 ::fDqyyT%M$MMfjj6K6KMM
 
 	
 ::f'D#Z0O====/===G 
JJJ
 
 	
 Ks   $AB==
C
C
file_bytesr   credentialsbucketregionc           
       K   |                     dd          }|                     dd          }|                     dd          }|                     dd          }	|                     dd          }
|                     d          }|                     d          }|r|r|	s?t          d	t          |           d
t          |           dt          |	                     t          r| d}n| d| d}t          j                            |	d          }d| d|                    d           }|r|dk    r!t          |          rt          |          }nd}t          |           }t          |           }|||d}t          t          j                              }|r|n|}|r||k    r||z
  nd}t          dd|                    d           i |||||          }|||d}t                              d|||	||           t#          j        d          4 d{V }|                    || |           d{V }|                                 ddd          d{V  n# 1 d{V swxY w Y   |
p|||d}|                    d          r't-          |           }|r|d         |d<   |d          |d <   t                              d!|d"         |           |S )#u  
    通过 httpx PUT 请求将文件上传到 COS。
    使用临时凭证（tmpSecretId/tmpSecretKey/sessionToken）构建 HMAC-SHA1 签名。

    Args:
        file_bytes:   文件二进制内容
        filename:     文件名（用于辅助计算 MIME、UUID）
        content_type: MIME 类型（如 "image/jpeg"）
        credentials:  get_cos_credentials() 返回的 dict，包含：
                        encryptTmpSecretId  → tmpSecretId
                        encryptTmpSecretKey → tmpSecretKey
                        encryptToken        → sessionToken
                        location            → COS key（对象路径）
                        resourceUrl         → 上传后公网 URL
                        startTime           → 凭证起始时间（Unix）
                        expiredTime         → 凭证过期时间（Unix）
        bucket:       COS Bucket 名称（如 chatbot-1234567890）
        region:       COS 地域（如 ap-guangzhou）

    Returns:
        上传结果 dict，包含：
            url       (str)           — COS 公网访问 URL
            uuid      (str)           — 文件内容 MD5
            size      (int)           — 文件大小（字节）
            width     (int, optional) — 图片宽度（仅图片）
            height    (int, optional) — 图片高度（仅图片）

    Raises:
        httpx.HTTPStatusError: COS 返回非 2xx 状态
        RuntimeError:          credentials 字段缺失
    encryptTmpSecretIdr.   encryptTmpSecretKeyencryptTokenr   resourceUrl	startTimeexpiredTimeu$   COS credentials 不完整: secretId=z, secretKey=z, location=z.cos.accelerate.myqcloud.comz.cos.z.myqcloud.comr   r   zhttps://r$   )hostr   x-cos-security-tokenr   put)r   r&   r   r   r   r   r   r   )Authorizationr   r	  z3COS PUT: bucket=%s region=%s key=%s size=%d mime=%sg      ^@r   N)contentr   )r   uuidsizer2   rR   rS   u    COS 上传成功: url=%s size=%dr   )r)   r   r0   COS_USE_ACCELERATEr   r   r   lstripr4   r,   r?   rU   r5   r   r   loggerinfor   r   r
  r   r3   rL   )r   r   r   r   r   r   r   r   session_tokencos_keyresource_urlr   expired_timecos_hostencoded_keycos_url	file_uuid	file_sizesign_headersr   
sign_startsign_expireauthorizationput_headersr   r   r   	size_infos                               r+   upload_to_cosr"    s8     N !__%92>>I!oo&;R@@J$<<M??:r22G#r::L + < <J"-//-"@"@L 
J 
g 
F4	?? F Fj))F F6:7mmF F
 
 	
  9:::886888 ,$$W3$77K===K$6$6s$;$;==G  6<+EEEH 	6*844LL5L 
##IJI $ - L dikk

C)2sJ*6W<#;M;M<#%%SWK*##C((**"	 	 	M '$ - K KK=L  
  ///              6ZZ   
 
 
 
 
 
 
 

 	                                                      &w F x(( 3$Z00	 	3'0F7O(2F8
KK*uy   Ms    3I%%
I/2I/r  r  rR   rS   
list[dict]c           	     t    |p|pt          |           pd}|rt          |          nd}d||d|||| dgddgS )uO  
    构建腾讯 IM TIMImageElem 消息体。
    参考：https://cloud.tencent.com/document/product/269/2720

    Args:
        url:       图片公网访问 URL（COS resourceUrl）
        uuid:      文件 UUID（MD5 或其他唯一标识）
        filename:  文件名（uuid 为空时作为备用）
        size:      文件大小（字节）
        width:     图片宽度（像素）
        height:    图片高度（像素）
        mime_type: MIME 类型（用于确定 image_format）

    Returns:
        TIMImageElem 消息体列表（适合直接放入 msg_body）
    imager   TIMImageElemr   )typer  rR   rS   r   )r  image_formatimage_info_arraymsg_typemsg_content)_basename_from_urlr7   )	r   r  r   r  rR   rS   r/   _uuidr(  s	            r+   build_image_msg_bodyr/  +  s    2 BHB 23 7 7B7E2;D#I...L ' , !" $!&"(" % 	
 	
 r-   c                     |p|}d|||| ddgS )u  
    构建腾讯 IM TIMFileElem 消息体。
    参考：https://cloud.tencent.com/document/product/269/2720

    Args:
        url:      文件公网访问 URL（COS resourceUrl）
        filename: 文件名（含扩展名）
        uuid:     文件 UUID（MD5 或其他唯一标识，不传则使用 filename）
        size:     文件大小（字节）

    Returns:
        TIMFileElem 消息体列表（适合直接放入 msg_body）
    TIMFileElem)r  	file_namer  r   r*  rD   )r   r   r  r  r.  s        r+   build_file_msg_bodyr3  [  s=    & HE &%!	 	
 	

 
r-   c                    	 t           j                            |           }t          j                            |j                  S # t          $ r Y dS w xY w)u   从 URL 提取文件名。r.   )r   r   urlparser%   r&   basename	Exception)r   parseds     r+   r-  r-    sV    &&s++w,,,   rrs   AA 
AA)r   r    r!   r    )r.   )r   r    r/   r    r!   r0   )r/   r    r!   r5   )r8   r9   r!   r    )r!   r    )r8   r9   r!   rF   )rM   r9   r!   rF   )r   r    r   r5   r!   r   )Nr   )r   r    r&   r    r   r   r   r   r   r    r   r    r   r   r   r5   r!   r    )r   Nr.   r.   )r   r    r   r    r   r    r   r    r   r   r   r    r   r    r!   r   )r   r9   r   r    r   r    r   r   r   r    r   r    r!   r   )NNr   r   r   r.   )r   r    r  r   r   r   r  r5   rR   r5   rS   r5   r/   r    r!   r#  )Nr   )
r   r    r   r    r  r   r  r5   r!   r#  )r   r    r!   r    )+__doc__
__future__r   r;   r   loggingr%   rB   rV   r   urllib.parser   typingr   r   r   	getLogger__name__r  r   DEFAULT_API_DOMAINDEFAULT_MAX_SIZE_MBr  r   __annotations__r   r,   r4   r7   r?   rE   rL   rH   rI   rJ   rK   r   r   r   r"  r/  r3  r-  rD   r-   r+   <module>rC     s    " # " " " " "    				                        		8	$	$ 1 *     	) 	)  	 	 	 	 
L \  K  K	 
 \  K  \  \  N       V  &  P  +   X! " L# $ 3         >= = = =_ _ _ _ _= = = =
) ) ) )
! ! ! !
 
 
 
% % % %   (% % % %   : +0" 0" 0" 0" 0"x !%O O O O Op !K K K K K\E E E EX "- - - - -f 	    H     r-   