ó
    ª½Íjº&  ã                   ó&  • S r SSKrSSKrSSKrSSKrSSKr\R                  " \5      rSr	Sr
SrSrSrSrS	rS
rSrSrSrSrSr " S S\5      rS rS r\4S jrS rS rS rS rS"S jrS#S jr S#S jr!S r"S r#S$S jr$SS \RJ                  4S! jr&g)%a  Facade over the TikTok APIs (Login Kit, Display API, Content Posting API): plain `requests`
calls, no Flask, no DB.

Every function raises TikTokAPIError with a client-safe message when TikTok fails; the raw
error payload is logged server-side and never returned to a client.
é    Nz)https://www.tiktok.com/v2/auth/authorize/z+https://open.tiktokapis.com/v2/oauth/token/z)https://open.tiktokapis.com/v2/user/info/z*https://open.tiktokapis.com/v2/video/list/z7https://open.tiktokapis.com/v2/post/publish/video/init/z9https://open.tiktokapis.com/v2/post/publish/status/fetch/é   i,  i   z_open_id,union_id,avatar_url,display_name,follower_count,following_count,likes_count,video_countzcid,title,video_duration,cover_image_url,create_time,view_count,like_count,comment_count,share_count)ÚPUBLIC_TO_EVERYONEÚMUTUAL_FOLLOW_FRIENDSÚ	SELF_ONLY)ÚdirectÚdraftc                   ó0   ^ • \ rS rSrSrSU 4S jjrSrU =r$ )ÚTikTokAPIErroré(   zWTikTok API failure; `message` is safe to show to a client, the details live in the log.c                 óH   >• [         TU ]  U5        Xl        X l        X0l        g ©N)ÚsuperÚ__init__ÚstatusÚmessageÚcode)Úselfr   r   r   Ú	__class__s       €Ú@F:\Solutions\Thinkera\Publishers\TikTokManager\app\tiktok_api.pyr   ÚTikTokAPIError.__init__+   s   ø€ Ü‰Ñ˜Ô!ØŒØŒØ�	ó    )r   r   r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r   Ú__static_attributes__Ú__classcell__)r   s   @r   r
   r
   (   s   ø† Ùa÷õ r   r
   c                 ód   • US;   a  gUS:X  a  gU S:X  a  gU S:X  a  gU S:X  a  g	U S
:X  d  US;   a  gg)N)Úaccess_token_invalidÚaccess_token_expiredÚinvalid_access_tokenz8TikTok rejected the access token. Reconnect the account.Úscope_not_authorizedzwTikTok refused the request: a permission (scope) is missing. Reconnect the account and grant all requested permissions.i‘  i“  znTikTok refused the request: the app is not allowed to do this (unaudited apps can only post SELF_ONLY videos).i”  z+The requested TikTok object does not exist.i­  )Úrate_limit_exceededÚtoo_many_requestszDTikTok API rate limit or daily posting cap reached; try again later.úTikTok API request failed.© )r   r   s     r   Ú_safe_messager(   2   sP   € ØÐWÓWØIØÐ%Ó%ð IØ�ƒ}ØIØ�ƒ}ØØ�ƒ}Ø<Ø�ƒ}˜Ð LÓLØUØ'r   c                 óî  • [        U [        5      (       d"  [        R                  SU5        [	        US5      eU R                  S5      =(       d    0 n[        U[        5      (       a  UR                  S5      OSnU(       aB  US:w  a<  [        R                  SXUR                  S5      5        [	        U[        X5      U5      eUS	:¼  a,  [        R                  S
X5        [	        U[        X5      U5      eU $ )zOTikTok returns HTTP 200 with an error body for many failures; both are checked.z&tiktok.api non-dict response status=%sr&   Úerrorr   NÚokz-tiktok.api error status=%s code=%s message=%sr   é�  z'tiktok.api http error status=%s body=%s)Ú
isinstanceÚdictÚlogr*   r
   Úgetr(   )Úpayloadr   r*   r   s       r   Ú_checkr2   B   sÅ   € ä�gœt×$Ñ$Ü�	‰	Ð:¸FÔCÜ˜VÐ%AÓBÐBØ�K‰K˜Ó ×& B€EÜ *¨5´$× 7Ñ 7ˆ5�9‰9�VÔ¸T€DÞ�˜“Ü�	‰	ÐAÀ6ÐQV×QZÑQZÐ[dÓQeÔfÜ˜V¤]°6Ó%@À$ÓGÐGØ�ƒ}Ü�	‰	Ð;¸VÔMÜ˜V¤]°6Ó%@À$ÓGÐGØ€Nr   c                 ó
  • [         R                  " X4SU0UD6n UR                  5       n[        XTR                  5      $ ! [         a8    [        R                  SUR                  5        [        UR                  S5      S ef = f)NÚtimeoutz&tiktok.api non-json response status=%sr&   )	ÚrequestsÚrequestÚjsonÚ
ValueErrorr/   r*   Ústatus_coder
   r2   )ÚmethodÚurlr4   ÚkwargsÚresponser1   s         r   Ú_requestr>   R   s   € Ü×Ò ÑG°WÐGÀÑG€Hð[Ø—-‘-“/ˆô �'×/Ñ/Ó0Ð0øô ó [Ü�	‰	Ð:¸H×<PÑ<PÔQÜ˜X×1Ñ1Ð3OÓPÐVZÐZð[ús   ›A  Á ABc                 ó   • SSU  30$ )NÚAuthorizationzBearer r'   )Útokens    r   Ú_bearerrB   \   s   € Ø˜w u gÐ.Ð/Ð/r   c           
      ó,   • [        S[        UUU SUS.S9$ )zPExchange the OAuth code for access + refresh tokens, open_id and granted scopes.ÚPOSTÚauthorization_code)Ú
client_keyÚclient_secretr   Ú
grant_typeÚredirect_uri©Údata©r>   Ú	TOKEN_URL)r   rF   rG   rI   s       r   Úexchange_coderN   `   s'   € ä�FœIØ Ø&ØØ*Ø$ñ-ñ ð r   c           	      ó*   • [        S[        UUSU S.S9$ )zWExchange a refresh token for new tokens; TikTok rotates the refresh token on every use.rD   Úrefresh_token)rF   rG   rH   rP   rJ   rL   )Úrefresh_token_valuerF   rG   s      r   rP   rP   k   s$   € ä�FœIØ Ø&Ø%Ø,ñ	-ñ ð r   c                 ó–   • [        S[        [        U 5      S[        0S9nUR	                  S0 5      R	                  S0 5      =(       d    0 $ )z*The connected account's profile and stats.ÚGETÚfields)ÚheadersÚparamsrK   Úuser)r>   ÚUSER_INFO_URLrB   ÚUSER_INFO_FIELDSr0   )rA   r1   s     r   Úget_user_inforZ   u   s?   € ä�uœm´W¸U³^ÈXÔWgÐLhÑi€GØ�;‰;�v˜rÓ"×&Ñ& v¨rÓ2×8°bÐ8r   c           	      ó2  • [        S[        0 [        U 5      ESS0E[        R	                  S5      UUS.S9nUR                  S0 5      =(       d    0 nUR                  S/ 5      =(       d    / UR                  S	U5      [        UR                  S
5      5      S.$ )zFPublished videos with their stats; returns {videos, cursor, has_more}.rD   úContent-Typeúapplication/jsonÚ,)rT   Ú	max_countÚcursor©rU   r7   rK   Úvideosr`   Úhas_more)rb   r`   rc   )r>   ÚVIDEO_LIST_URLrB   ÚVIDEO_FIELDSÚsplitr0   Úbool)rA   r_   r`   r1   rK   s        r   Úlist_videosrh   {   s‘   € ä�vœ~Ð7m¼'À%».Ð7mÈ.ÐZlÑ7mÜ×$Ñ$ SÓ)ØØñuñ €Gð
 �;‰;�v˜rÓ"×( b€Dà—(‘(˜8 RÓ(×.¨BØ—(‘(˜8 VÓ,Ü˜Ÿ™ *Ó-Ó.ñð r   c                 ód  • SSXUS.0n0 nU(       a  XHS'   US:X  a  U=(       d    SUS'   U(       a  X‡S'   [        S	[        0 [        U 5      ES
S0EUS9n	U	R                  S0 5      =(       d    0 n
U
R                  S5      (       a  U
R                  S5      (       d  [	        SS5      eU
S   U
S   S.$ )zCInitialize a FILE_UPLOAD publish; returns {publish_id, upload_url}.Úsource_infoÚFILE_UPLOAD)ÚsourceÚ
video_sizeÚ
chunk_sizeÚtotal_chunk_countÚtitler   r   Úprivacy_levelÚ	post_inforD   r\   r]   ra   rK   Ú
publish_idÚ
upload_urléÈ   r&   )rs   rt   ©r>   ÚPUBLISH_INIT_URLrB   r0   r
   )rA   rm   rn   ro   rp   rq   ÚmodeÚbodyrr   r1   rK   s              r   Úinit_video_uploadrz   Š   sÉ   € à mÀ:ð  Pñ  Qð  R€DØ€IÞØ"�'ÑØˆxÓØ%2×%A°kˆ	�/Ñ"ÞØ%ˆ[ÑÜ�vÔ/Ð9o¼GÀE»NÐ9oÈNÐ\nÑ9oÐvzÑ{€GØ�;‰;�v˜rÓ"×( b€DØ�8‰8�L×!Ñ!¨¯©°,×)?Ñ)?Ü˜SÐ">Ó?Ð?Ø˜|Ñ,¸DÀÑ<NÑOÐOr   c                 ó.  • SSUS.0n0 nU(       a  X&S'   US:X  a  U=(       d    SUS'   U(       a  XeS'   [        S	[        0 [        U 5      ES
S0EUS9nUR                  S0 5      =(       d    0 nUR                  S5      (       d  [	        SS5      eSUS   0$ )z9Initialize a PULL_FROM_URL publish; returns {publish_id}.rj   ÚPULL_FROM_URL)rl   Ú	video_urlrp   r   r   rq   rr   rD   r\   r]   ra   rK   rs   ru   r&   rv   )	rA   r}   rp   rq   rx   ry   rr   r1   rK   s	            r   Úinit_video_pullr~   ›   s©   € à oÀIÑNÐO€DØ€IÞØ"�'ÑØˆxÓØ%2×%A°kˆ	�/Ñ"ÞØ%ˆ[ÑÜ�vÔ/Ð9o¼GÀE»NÐ9oÈNÐ\nÑ9oÐvzÑ{€GØ�;‰;�v˜rÓ"×( b€DØ�8‰8�L×!Ñ!Ü˜SÐ">Ó?Ð?Ø˜$˜|Ñ,Ð-Ð-r   c           
      óà   • [         R                  " U USU SU SU 3SS.[        S9nUR                  S:¼  a6  [        R                  SUR                  5        [        UR                  S	5      eU$ )
z7PUT one chunk to the upload URL with its Content-Range.zbytes Ú-Ú/z	video/mp4)zContent-Ranger\   )rK   rU   r4   r,   z$tiktok.upload chunk failed status=%sz!TikTok rejected the video upload.)r5   ÚputÚUPLOAD_TIMEOUT_SECONDSr9   r/   r*   r
   )rt   ÚchunkÚstartÚendÚtotalr=   s         r   Úupload_chunkrˆ   ¬   sw   € ä�|Š|ØØà% e W¨A¨c¨U°!°E°7Ð;Ø'ñ
ô 'ñ€Hð ×Ñ˜sÓ"Ü�	‰	Ð8¸(×:NÑ:NÔOÜ˜X×1Ñ1Ð3VÓWÐWØ€Or   c                 ób  • [        S[        0 [        U 5      ESS0ESU0S9nUR                  S0 5      =(       d    0 nUR                  SS5      UR                  S	5      [	        UR                  S
5      [
        5      (       a+  UR                  S
5      =(       d    0 R                  S5      S.$ SS.$ )zMThe processing status of a publish: PROCESSING_*, PUBLISH_COMPLETE or FAILED.rD   r\   r]   rs   ra   rK   Úpublish_statusÚUNKNOWNÚpublicly_available_post_idÚfail_reasonr   N©rŠ   Úvideo_idr�   )r>   ÚPUBLISH_STATUS_URLrB   r0   r-   r.   )rA   rs   r1   rK   s       r   Úfetch_publish_statusr‘   ½   s´   € ä�vÔ1Ð;q¼gÀe»nÐ;qÈnÐ^pÑ;qØ�jðyñ €Gð �;‰;�v˜rÓ"×( b€DàŸ(™(Ð#3°YÓ?Ø—H‘HÐ9Ó:ÜFPÐQU×QYÑQYÐZgÓQhÔjn×FoÑFo˜Ÿ™ Ó/×5°2×:Ñ:¸6ÓBñð ð vzñð r   c                 ó  • [         R                  R                  U5      nUS::  a  [        SS5      e[	        [
        U5      n[        S[        R                  " Xg-  5      5      n[        XXxX#US9n	[        US5       n
[        U5       Hb  nU
R                  U5      nX·-  n[        U	S   XÍU[        U5      -   S-
  U5        U(       d  MA  U" [	        SU[        U5      -   U-  5      5        Md     S	S	S	5        S
U	S
   0$ ! , (       d  f       N= f)u®   Upload a local video file to TikTok (init + chunked PUT); returns {publish_id}.
The video then processes asynchronously â€” poll with wait_for_publish / fetch_publish_status.r   r,   zThe video file is empty.é   )rm   rn   ro   rp   rq   rx   Úrbrt   g      ð?Nrs   )ÚosÚpathÚgetsizer
   ÚminÚ
CHUNK_SIZEÚmaxÚmathÚceilrz   ÚopenÚrangeÚreadrˆ   Úlen)rA   Ú	file_pathrp   rq   rx   Úon_progressÚsizern   Útotal_chunksÚinitÚfÚindexr„   r…   s                 r   Úpublish_video_filer¨   Ê   sû   € ô �7‰7�?‰?˜9Ó%€DØˆqƒyÜ˜SÐ"<Ó=Ð=Ü”Z Ó&€JÜ�qœ$Ÿ)š) DÑ$5Ó6Ó7€LÜØ¨:Ø°tñ€Dô 
ˆi˜Ô	 !Ü˜<Ö(ˆEØ—F‘F˜:Ó&ˆEØÑ&ˆEÜ˜˜lÑ+¨U¸5Ä3ÀuÃ:Ñ;MÐPQÑ;QÐSWÔXßˆ{ÙœC  e¬c°%«jÑ&8¸DÑ%@ÓAÖBñ )÷ 
ð ˜$˜|Ñ,Ð-Ð-÷ 
Õ	ús   Á<A	C=Ã	%C=Ã=
DéZ   é   c                 ó¶   • [         R                  " 5       U-   nSSSS.n [        X5      nUS   S;   a  U$ [         R                  " 5       U:¼  a  U$ U" U5        M;  )zbPoll the publish status until it completes, fails or the timeout elapses; returns the last status.Ú
PROCESSINGNrŽ   rŠ   )ÚPUBLISH_COMPLETEÚFAILED)ÚtimeÚ	monotonicr‘   )rA   rs   Útimeout_secondsÚpoll_secondsÚsleepÚdeadliner   s          r   Úwait_for_publishrµ   à   sa   € ä�~Š~Ó /Ñ1€HØ ,¸$ÈtÑT€FØ
Ü% eÓ8ˆØÐ"Ñ#Ð'EÓEØˆMÜ�>Š>Ó˜xÓ'ØˆMÙˆlÔñ r   )é   r   )NNr   )NNr   N)'r   Úloggingr›   r•   r¯   r5   Ú	getLoggerr   r/   ÚAUTHORIZE_URLrM   rX   rd   rw   r�   ÚDEFAULT_TIMEOUT_SECONDSrƒ   r™   rY   re   ÚPRIVACY_LEVELSÚ
POST_MODESÚ	Exceptionr
   r(   r2   r>   rB   rN   rP   rZ   rh   rz   r~   rˆ   r‘   r¨   r³   rµ   r'   r   r   Ú<module>r¾      så   ðñó Û Û 	Û ã à×Ò˜Ó!€à;€Ø9€	Ø;€Ø=€ØLÐ ØPÐ àÐ ØÐ Ø€
ð.ð ð
6ð ð
 N€Ø €
ô�Yô ò(ò ð  #:ô 1ò0òòò9ôôPô".ò"ò"
ô.ð, 9;ÈÐRV×R\ÑR\õ 
r   