ó
    °ÁÍjÀ  ã                   óÒ   • S r SSKrSSKJrJr  SSKJr  SSKJr  SSKJ	r	  SSK
Jr  SSKJr  SS	KJrJrJr  SS
KJrJrJrJr  \R.                  " \5      rSS jrS rS rS rSS jrg)z–The scheduled-post worker: publishes due posts and records the outcome (ADR-005).

Run by the dedicated scheduler container:  python -m app.scheduler
é    N)ÚdatetimeÚtimezone)Údb)Úensure_fresh_token)ÚScheduledPost)ÚValidationError)Úvalidate_post_fields)ÚVIDEO_EXTENSIONSÚremove_quietlyÚstore_upload)ÚTikTokAPIErrorÚinit_video_pullÚpublish_video_fileÚwait_for_publishc                 ó�  • SSK Jn  [        U5      nU" UR                  S5      5      nU(       d  [	        S5      eSnUS   (       a  [        US   U[        US9nUR                  n[        U R                  US   US	   S
:X  a  US   OSUS	   UUS   UR                  [        R                  5      R                  SS9[        R                  S9n	[        R                   R#                  U	5        [        R                   R%                  5         [&        R)                  SU	R                  UR+                  5       U	R,                  5        U	$ )z²Create a pending scheduled post from form/JSON fields plus an optional video file; the
file is stored under MEDIA_FOLDER until the scheduler publishes it. Raises ValidationError.r   ©Úparse_scheduled_timeÚscheduled_atz-scheduled_at is required for a scheduled postNÚ
video_file)Ú	max_bytesÚtitleÚmodeÚdirectÚprivacy_levelÚ	video_url©Útzinfo)Ú
account_idr   r   r   Ú
video_pathr   r   Ústatusz'scheduled.create ok id=%s at=%s mode=%s)Úapp.services.postsr   r	   Úgetr   r   r
   Úpathr   ÚidÚ
astimezoner   ÚutcÚreplaceÚSTATUS_PENDINGr   ÚsessionÚaddÚcommitÚlogÚinfoÚ	isoformatr   )
ÚaccountÚdataÚmedia_folderÚupload_max_bytesr   Úfieldsr   r   ÚstoredÚposts
             ÚHF:\Solutions\Thinkera\Publishers\TikTokManager\app\services\scheduled.pyÚcreate_scheduled_postr7      s  € õ 8ä! $Ó'€FÙ'¨¯©°Ó(@ÓA€LÞÜÐMÓNÐNà€JØˆl×Ü˜f \Ñ2°LÔBRÐ^nÑoˆØ—[‘[ˆ
äØ—:‘:Ø�W‰oØ17¸±À8Ó1K�f˜_Ò-ÐQUØ�F‰^ØØ˜Ñ%Ø!×,Ñ,¬X¯\©\Ó:×BÑBÈ$ÐBÐOÜ×+Ñ+ñ	€Dô ‡J�J‡N�N�4ÔÜ‡J�J×ÑÔÜ‡H�HÐ6¸¿¹À×AWÑAWÓAYÐ[_×[dÑ[dÔeØ€Kó    c                 ó  • SSK Jn  U" U5      nU(       d  [        S5      eUR                  [        R
                  5      R                  SS9U l        [        R                  U l
        SU l        [        R                  R                  5         U $ )zIMove a scheduled post to a new time and put it back in the pending queue.r   r   zscheduled_at is requiredNr   )r!   r   r   r%   r   r&   r'   r   r   r(   r    Úerrorr   r)   r+   )r5   Úscheduled_at_valuer   r   s       r6   Úreschedule_postr<   2   sm   € å7á'Ð(:Ó;€LÞÜÐ8Ó9Ð9Ø$×/Ñ/´·±Ó=×EÑEÈTÐEÐR€DÔÜ×.Ñ.€D„KØ€D„JÜ‡J�J×ÑÔØ€Kr8   c                 óÊ   • U R                   (       a  [        U R                   5        [        R                  R	                  U 5        [        R                  R                  5         g)zEDelete a scheduled post row and its stored media file (when present).N)r   r   r   r)   Údeleter+   ©r5   s    r6   Údelete_scheduled_postr@   @   s7   € à‡‡Ü�t—‘Ô'Ü‡J�J×Ñ�dÔÜ‡J�J×ÑÕr8   c                 óz   • [         R                  U l        SU l        [        R
                  R                  5         U $ )z‰Put a failed post back in the pending queue with its original time (already in the past,
so the scheduler publishes it on the next tick).N)r   r(   r    r:   r   r)   r+   r?   s    r6   Ú
retry_postrB   H   s-   € ô  ×.Ñ.€D„KØ€D„JÜ‡J�J×ÑÔØ€Kr8   c           	      ó”  • U =(       d$    [         R                  " [        R                  5      n U R                  (       a  U R                  SS9OU n[        R                  R                  [        R                  S9R                  [        R                  U:*  5      R                  [        R                  5      R                  5       nU GHÄ  n[        R                  Ul        [         R"                  R%                  5          ['        UR(                  5      nUR*                  (       a5  [-        XCR*                  UR.                  UR0                  UR2                  S9nO4[5        XCR6                  UR.                  UR0                  UR2                  S9n[9        XES   5      nUS   S:X  a  [        R:                  Ul        SUl        O[        R>                  Ul        SUl        US   Ul         URC                  S	5      Ul"        UR                  [        R>                  :X  a&  UR*                  (       a  [G        UR*                  5        [H        RK                  S
URL                  UR                  5        [         R"                  R%                  5         GMÇ     [W        U5      $ ! [N         a[  n[        R:                  Ul        URP                  Ul        [H        R=                  SURL                  URR                  5         SnANŽSnAf[T         a>    [        R:                  Ul        SUl        [H        R=                  SURL                  SS9   NØf = f)zåPublish every pending post whose time has come; marks each row published or failed with a
safe error message. Returns the number of due posts processed. `scheduled_at` is stored as
naive UTC, so the comparison uses naive UTC too.Nr   )r    )r   r   r   Ú
publish_idÚpublish_statusÚFAILEDz.TikTok rejected the video while processing it.Úvideo_idz!scheduled.publish id=%s status=%sz&scheduled.publish failed id=%s code=%sz(Publishing failed; check the server log.zscheduled.publish failed id=%sT)Úexc_info),r   Únowr   r&   r   r'   r   ÚqueryÚ	filter_byr(   Úfilterr   Úorder_byÚallÚSTATUS_PUBLISHINGr    r   r)   r+   r   r/   r   r   r   r   r   r   r   r   ÚSTATUS_FAILEDr:   ÚSTATUS_PUBLISHEDrD   r"   rG   r   r,   r-   r$   r   ÚmessageÚcodeÚ	ExceptionÚlen)rI   Ú	now_naiveÚduer5   ÚtokenÚresultr    Úes           r6   Úrun_due_postsr[   Q   sQ  € ð ×
+”—’œhŸl™lÓ+€CØ,/¯J¯J�—‘ 4�Ñ(¸C€IÜ
×
Ñ
×
'Ñ
'¬}×/KÑ/KÐ
'Ð
L×
SÑ
SÜ×"Ñ" iÑ/óç�hŒ}×)Ñ)Ó*¯3©3«5ð ô ˆÜ#×5Ñ5ˆŒÜ
�
‰
×ÑÔð	PÜ& t§|¡|Ó4ˆEØ��Ü+ØŸ?™?ØŸ*™*°D×4FÑ4FÈTÏYÉYñ‘ô
 )ØŸ>™>ØŸ*™*°D×4FÑ4FÈTÏYÉYñ�ô & e°LÑ-AÓBˆFØÐ&Ñ'¨8Ó3Ü+×9Ñ9�”ØM�•
ä+×<Ñ<�”Ø!�”
Ø$ \Ñ2ˆDŒOØ"ŸJ™J zÓ2ˆDŒMØ�{‰{œm×<Ñ<Ó<ÀÇÇÜ˜tŸ™Ô/Ü�H‰HÐ8¸$¿'¹'À4Ç;Á;ÔOô 	�
‰
×Ñ×ñG ôH ˆs‹8€Oøô ó 	QÜ'×5Ñ5ˆDŒKØŸ™ˆDŒJÜ�I‰IÐ>ÀÇÁÈÏÉ×PÑPûÜó 	PÜ'×5Ñ5ˆDŒKØCˆDŒJÜ�I‰IÐ6¸¿¹È$ˆIÓOð	Pús!   Ä E.JÊ
MÊ&AK<Ë<AMÍM)N)Ú__doc__Úloggingr   r   Úappr   Úapp.authr   Ú
app.modelsr   Úapp.services.errorsr   r!   r	   Úapp.services.uploadsr
   r   r   Úapp.tiktok_apir   r   r   r   Ú	getLoggerÚ__name__r,   r7   r<   r@   rB   r[   © r8   r6   Ú<module>rg      sV   ðñó ß 'å Ý 'Ý $Ý /Ý 3ß OÑ Oß `Ó `à×Ò˜Ó!€ôò>òòõ.r8   