U
    @tiC                     @   s  d dl Z d dlZd dlZd dlmZmZmZmZ d dl	Z	d dl
mZ d dlmZ d dlmZmZmZmZmZ dZdZdZd	Zd
ZdZedddgZedddZe	jdddZe ZdMe e e e dddZ!ee"ef ee"ef dddZ#ee"ef ddddZ$e"e"e"ee"ef ddd Z%ee"ef ee"ef d!d"d#Z&d$d%ee"ef e ee"ef d&d'd(Z'e"e"d)d*d+Z(e"ee"ef d,d-d.Z)e"ee"e"f d/d0d1Z*ee"ef e"dd2d3d4Z+e"ee"ef d/d5d6Z,e"e"ee"ef d7d8d9Z-e"e"ee"ef d:d;d<Z.d=dd>e"e"e"e"e/e/e/e/e/e/ee  ee"ef d?d@dAZ0d=dd>e"e"e"e"e/e/e/e/e/e/ee  ee"ef dBdCdDZ1e"e"ee"ef d:dEdFZ2edG Z3d=ddHdddIe"e"e"e/e/e/e/e/e/ee  e3ee" ee" ee"ef dJdKdLZ4dS )N    N)AnyDictOptionalLiteral)HTTPAdapter)Retry)TIKTOK_CLIENT_KEYTIKTOK_CLIENT_SECRETTIKTOK_REDIRECT_URITIKTOK_SCOPESTIKTOK_PRODUCTIONz)https://www.tiktok.com/v2/auth/authorize/z+https://open.tiktokapis.com/v2/oauth/token/z=https://open.tiktokapis.com/v2/post/publish/inbox/video/init/z7https://open.tiktokapis.com/v2/post/publish/video/init/z?https://open.tiktokapis.com/v2/post/publish/creator_info/query/z9https://open.tiktokapis.com/v2/post/publish/status/fetch/GETPOSTPUT)returnc                  C   s|   t dddddd} ztf | tddW S  tk
r<   Y nX ztf | tddW S  tk
rh   Y nX tf | dtiS )	u   
    Compatibilidad urllib3:
      - urllib3 nuevo: allowed_methods=
      - urllib3 viejo: method_whitelist=
    Además, algunas versiones no aceptan raise_on_status.
       g?)i  i  i  i  i  )totalconnectreadZbackoff_factorZstatus_forcelistF)allowed_methodsraise_on_status)method_whitelistr   r   )dictr   _ALLOWED_RETRY_METHODS	TypeError)Zbase_kwargs r   +/var/www/html/luxverbi-app/tiktok_client.py_make_retry)   s8    	r   c                  C   s8   t  } t }t|ddd}| d| | d| | S )N   )Zmax_retriesZpool_connectionsZpool_maxsizezhttps://zhttp://)requestsSessionr   r   Zmount)sretryadapterr   r   r   _build_sessionS   s    r$   x   )now_ts
expires_inskew_secondsr   c                 C   s   | t dt|t|  S )u[   
    Guarda expiración absoluta con margen (skew) para evitar usar tokens al límite.
    r   )maxint)r&   r'   r(   r   r   r   compute_expires_ata   s    r+   )bundler   c                 C   sp   t t }t | dpd}t | dp,d}|rBt||ddnd| d< |r\t||ddnd| d< || d< | S )	zq
    Normaliza el bundle OAuth a expiraciones absolutas:
      - expires_at_ts
      - refresh_expires_at_ts
    r'   r   refresh_expires_inr%   )r(   expires_at_tsrefresh_expires_at_tsZupdated_at_ts)r*   timegetr+   )r,   nowZaccess_expires_inr-   r   r   r   _normalize_token_bundleh   s    r3   )payloadr   c              	   C   sD   t | tr@| dr@td| d d| d d| d dS )zr
    OAuth v2 devuelve errores en top-level:
      {"error":"...", "error_description":"...", "log_id":"..."}
    errorzoauth_error=z desc=Zerror_description log_id=log_idN)
isinstancer   r1   RuntimeError)r4   r   r   r   _raise_oauth_error_if_anyx   s    &r:   )
client_keyclient_secretrefresh_tokenr   c                 C   sb   | |d|d}t jt|ddidd}|jr2| ni }|jdkrVtd|j d	| t| |S )
zd
    Refresca access_token usando refresh_token.
    Devuelve el bundle OAuth (sin normalizar).
    r=   )r;   r<   
grant_typer=   Content-Type!application/x-www-form-urlencoded   dataheaderstimeout   zrefresh_failed http=	 payload=)_SESSIONpostTIKTOK_OAUTH_TOKEN_URLcontentjsonstatus_coder9   r:   )r;   r<   r=   rC   rr4   r   r   r   refresh_user_access_token   s     
rO   )currentr   c                 C   s   | pi  d}|stdttt|}| dr<|d | d< | drR|d | d< dD ]$}||krV|| dk	rV|| | |< qVt| S )u^   
    Refresca el bundle actual y aplica rotación de refresh_token si TikTok lo devuelve.
    r=   Zmissing_refresh_tokenaccess_token)r'   r-   Zopen_idscope
token_typeN)r1   r9   rO   r   r	   r3   )rP   rtr,   kr   r   r   refresh_token_bundle   s    

rV   ,  )refresh_if_lt_seconds)rP   rX   r   c                C   sp   | st dtt }t| dp&d}|r>||kr>t dt| dpLd}|rd||t| krlt| S | S )u   
    Garantiza que access_token sea válido.
    - Si expira en <refresh_if_lt_seconds, refresca.
    - Si refresh_token expiró, lanza error (requiere relogin).
    Zmissing_token_bundler/   r   Z&refresh_token_expired_requires_reloginr.   )r9   r*   r0   r1   rV   )rP   rX   r2   Zrefresh_expires_atZ
expires_atr   r   r   ensure_fresh_token_bundle   s    rY   )stater   c                 C   s$   t dtt| d}td tj| S )Ncode)r;   Zresponse_typerR   redirect_urirZ   ?)r   r   r
   TIKTOK_OAUTH_AUTHORIZE_URLurllibparse	urlencode)rZ   paramsr   r   r   build_authorize_url   s    rc   )r[   r   c                 C   sh   t td| td}tjt|ddidd}|jr4| ni }|jdkrXt	d|j d	| t
| t|S )
zV
    Intercambia code por token bundle y lo normaliza con expiraciones absolutas.
    Zauthorization_code)r;   r<   r>   r[   r\   r?   r@   rA   rB   rF   zoauth_exchange_failed http=rG   )r   r	   r
   rH   rI   rJ   rK   rL   rM   r9   r:   r3   )r[   r4   resprC   r   r   r   exchange_code_for_token   s"    
re   )rQ   r   c                 C   s   d|  ddS )NzBearer zapplication/json; charset=UTF-8)Authorizationr?   r   )rQ   r   r   r   _api_headers   s    rg   )r4   contextr   c             
   C   sV   | pi  dpi }| d}|rR|dkrRt| d| d| d d| d d	S )
zW
    TikTok API v2 suele devolver:
      {"error":{"code":"ok",...}, "data":{...}}
    r5   r[   okz error_code=z	 message=messager6   r7   N)r1   r9   )r4   rh   errr[   r   r   r   _raise_api_error_if_any   s    
rl   c                 C   sh   t | }tjt|i dd}|jr(| ni }|jdkrLtd|j d| t|dd |	di pfi S )	NrA   rD   rL   rE   rF   zcreator_info_http_failed http=rG   creator_inforh   rC   )
rg   rH   rI   CREATOR_INFO_URLrK   rL   rM   r9   rl   r1   )rQ   rD   rd   r4   r   r   r   query_creator_info  s    
rq   )rQ   
publish_idr   c                 C   sl   t | }tjt|d|idd}|jr,| ni }|jdkrPtd|j d| t|dd |	d	i pji S )
Nrr   rA   rm   rF   zstatus_fetch_http_failed http=rG   Zstatus_fetchro   rC   )
rg   rH   rI   STATUS_FETCH_URLrK   rL   rM   r9   rl   r1   )rQ   rr   rD   rd   r4   r   r   r   fetch_post_status  s    
rt   )	file_pathrQ   r   c              	   C   s  t j| }t|}dd||ddi}tjt||dd}|jrF| ni }|j	dkrjt
d|j	 d	| t|d
d |di pi }|d}|st
d| |d }	dd|	 d| d}
t| d}tj||
|dd}W 5 Q R X |j	dkrt
d|j	 d|jd d  |S )Nsource_infoFILE_UPLOAD   sourceZ
video_size
chunk_sizeZtotal_chunk_count<   rm   rF   zinbox_init_http_failed http=rG   Z
inbox_initro   rC   
upload_urlz&inbox_init_missing_upload_url payload=	video/mp4bytes 0-/r?   zContent-RangerbrW   rD   rC   rE     zinbox_put_failed http= body=  )ospathgetsizerg   rH   rI   INBOX_INIT_URLrK   rL   rM   r9   rl   r1   openputtext)ru   rQ   	file_sizerD   body	init_resp	init_datarC   r}   end_byteput_headersfput_respr   r   r   _upload_video_as_draft"  s6    	

 r   T)is_aigcvideo_cover_timestamp_ms)rQ   	video_urlcaptionprivacy_leveldisable_commentdisable_duetdisable_stitchbrand_content_togglebrand_organic_toggler   r   r   c              	   C   s   |rt |tstdt| }||p&dt|t|t|t|t|t|	d}|
d k	rdt|
|d< d|d|d}tjt||dd	}|j	r|
 ni }|jd
krtd|j d| t|dd |S )Nz'video_url is required for PULL_FROM_URL r   titler   r   r   r   r   r   r   PULL_FROM_URL)rz   r   rv   	post_infor|   rm   rF   direct_init_http_failed http=rG   Zdirect_init_pull_from_urlro   )r8   str
ValueErrorrg   boolr*   rH   rI   DIRECT_POST_INIT_URLrK   rL   rM   r9   rl   )rQ   r   r   r   r   r   r   r   r   r   r   rD   r   r   r   r   r   r   r   _publish_video_from_urlN  s2    

r   )ru   rQ   r   r   r   r   r   r   r   r   r   r   c              	   C   s`  t j| }t|}||pdt|t|t|t|t|t|	d}|
d k	rZt|
|d< d||dd|d}tjt||dd	}|j	r|
 ni }|jd
krtd|j d| t|dd |di pi }|d}|std| |d }dd| d| d}t| d}tj|||dd}W 5 Q R X |jdkr\td|j d|jd d  |S )Nr   r   r   rw   rx   ry   r   r|   rm   rF   r   rG   Zdirect_init_file_uploadro   rC   r}   z'direct_init_missing_upload_url payload=r~   r   r   r   r   rW   r   r   zdirect_put_failed http=r   r   )r   r   r   rg   r   r*   rH   rI   r   rK   rL   rM   r9   rl   r1   r   r   r   )ru   rQ   r   r   r   r   r   r   r   r   r   r   rD   r   r   r   r   rC   r}   r   r   r   r   r   r   r   %_upload_and_publish_video_file_upload  sL    



 r   c                 C   s   t | |dS )Nru   rQ   )r   r   r   r   r   upload_video_draft  s    r   )r   rw   r   )r   r   moder   ru   )rQ   r   r   r   r   r   r   r   r   r   r   r   ru   r   c                 C   sv   |
dkr2|st dt| ||||||||||	dS |
dkrd|sFt dt|| |||||||||	dS t d|
 dS )	z
    Unified entrypoint:
      - mode="PULL_FROM_URL": requiere video_url (recomendado si el MP4 ya existe en tu servidor)
      - mode="FILE_UPLOAD": requiere file_path (fallback/tests)
    r   z/video_url is required when mode='PULL_FROM_URL')rQ   r   r   r   r   r   r   r   r   r   r   rw   z-file_path is required when mode='FILE_UPLOAD')ru   rQ   r   r   r   r   r   r   r   r   r   zUnsupported mode: N)r   r   r   )rQ   r   r   r   r   r   r   r   r   r   r   r   ru   r   r   r   upload_video_direct_post  sB    r   )r%   )5r   r0   urllib.parser_   typingr   r   r   r   r   Zrequests.adaptersr   Zurllib3.util.retryr   configr   r	   r
   r   r   r^   rJ   r   r   rp   rs   	frozensetr   r   r    r$   rH   r*   r+   r   r3   r:   rO   rV   rY   rc   re   rg   rl   rq   rt   r   r   r   r   r   ZPublishModer   r   r   r   r   <module>   s   *	  (
7
?
G
