Ë
    uŸÒiïe  ã                   óF  — d dl Z 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	m
Z
 d dlmZmZ d dlmZmZ d dlmZmZmZ d dlmZmZmZ d dlmZmZmZ  ed	e«      Zh d
£Zh d£Z de!fd„Z"de#fd„Z$de!de%fd„Z&de!de'de'fd„Z(de!fd„Z)de!de!fd„Z*d„ Z+de!de!de!fd„Z,ej[                  ddgd¬ «      d!„ «       Z.ej[                  d"dgd#¬ «      d$„ «       Z/ej[                  d%dgd&¬ «      d'„ «       Z0ej[                  d(dgd)¬ «      d*„ «       Z1ej[                  d+dgd,¬ «      d-„ «       Z2y).é    N)Ú	BlueprintÚrequestÚjsonifyÚcurrent_app)Úbulk_db)Úrequire_bulk_api_keyÚget_access_token_for_alias)Úparse_publish_at_iso_to_tsÚcreator_can_post_now)Úsafe_filenameÚvideo_duration_seconds_ffprobeÚbuild_public_media_url)Úprocess_one_scheduled_jobÚresolve_access_token_for_jobÚrefresh_submitted_job_status)Úquery_creator_infoÚupload_video_direct_postÚfetch_post_statusÚbulk_api>   ÚfailedÚcompleteÚ	cancelled>   Ú	scheduledÚ	submittedÚ
submittingÚ
table_namec                 óÒ   — | j                  d|› d�«       | j                  «       }g }|D ]  }	 |j                  |d   «       Œ |S # t        $ r |j                  |d   «       Y Œ;w xY w)z€
    Devuelve lista de columnas reales del schema para inserts robustos.
    Funciona con row_factory sqlite3.Row o tuplas.
    zPRAGMA table_info(ú)Únameé   )ÚexecuteÚfetchallÚappendÚ	Exception)Úcurr   ÚrowsÚcolsÚrs        ú5/var/www/html/luxverbi-app/app/blueprints/bulk_api.pyÚ_table_columnsr*      sv   € ð
 ‡K�KÐ$ Z L°Ð2Ô3Ø�<‰<‹>€DØ€DØò ˆð	Ø�K‰K˜˜&™	Õ"ðð €Køô ò 	Ø�K‰K˜˜!™Öð	ús   ®AÁA&Á%A&Újobc                 óŽ  — | j                  «       }t        |d«      }t        |«      }|j                  «       D ��ci c]  \  }}||v sŒ||“Œ }}}dj	                  |j                  «       «      }dj	                  dgt        |«      z  «      }	d|› d|	› d�}
|j                  |
t        |j                  «       «      «       |S c c}}w )z^
    Inserta en bulk_jobs filtrando por columnas reales (evita 'N values for M columns').
    Ú	bulk_jobsz, ú?zINSERT INTO bulk_jobs (z
) VALUES (r   )
Úcursorr*   ÚsetÚitemsÚjoinÚkeysÚlenr!   ÚtupleÚvalues)Úconnr+   r%   r'   ÚcolsetÚkÚvÚfilteredÚcolnamesÚplaceholdersÚsqls              r)   Ú_insert_bulk_jobr?   +   s°   € ð �+‰+‹-€CÜ˜#˜{Ó+€DÜ�‹Y€Fà!$§¡£×<™˜˜A°°V²��1‘Ð<€HÑ<à�y‰y˜Ÿ™›Ó)€HØ—9‘9˜c˜U¤S¨£]Ñ2Ó3€LØ# H :¨Z¸°~ÀQÐ
G€CØ‡K�K�”U˜8Ÿ?™?Ó,Ó-Ô.Ø€Jùó =s   »CÁCÚerrÚreturnc                 ó2  ‡ ‡— ‰ syt        ‰ «      j                  «       Š‰ j                  d«      ry‰ j                  d«      ryg d¢}t        ˆfd„|D «       «      ry‰ j                  d«      s‰ j                  d«      ryg d¢}t        ˆ fd	„|D «       «      ry
y
)u‹  
    Decide si el fallo merece reintento (requeue) en lugar de marcar el job como failed.

    FilosofÃ­a:
      - Reintentar todo lo que sea probablemente transitorio: 5xx, 429, internal_error,
        timeouts, errores de red, "cannot_post_now", etc.
      - Marcar failed solo para errores claramente permanentes (fichero inexistente, privacy
        invÃ¡lida, duraciÃ³n excedida, etc.)
    TÚcannot_post_nowÚmissing_publish_id)zhttp=429ztoo many requestsÚ
rate_limitÚthrottlezhttp=500zhttp=502zhttp=503zhttp=504Úinternal_errorzservice unavailablezbad gatewayzgateway timeoutÚtimeoutz	timed outÚreadtimeoutÚconnecttimeoutÚ
connectionzconnection resetÚtemporarilyz	try againc              3   ó&   •K  — | ]  }|‰v –— Œ
 y ­w©N© )Ú.0ÚmÚes     €r)   ú	<genexpr>z&_is_transient_error.<locals>.<genexpr>[   s   øè ø€ Ò
-�aˆ1�Œ6Ñ
-ùs   ƒÚdirect_post_failedÚcreator_info_failed)Úfile_not_found_on_serverÚduration_exceeds_maxÚinvalid_privacy_levelÚbranded_cannot_be_self_onlyÚmissing_privacy_levelÚmissing_videoÚmissing_filenamec              3   ó@   •K  — | ]  }‰j                  |«      –— Œ y ­wrN   )Ú
startswith)rP   Úpr@   s     €r)   rS   z&_is_transient_error.<locals>.<genexpr>l   s   øè ø€ Ò
9 ˆ3�>‰>˜!×Ñ
9ùs   ƒF)ÚstrÚlowerr^   Úany)r@   Útransient_markersÚpermanent_prefixesrR   s   `  @r)   Ú_is_transient_errorre   <   s–   ù€ ñ ØäˆC‹�‰Ó€Að ‡~�~Ð'Ô(ØØ
‡~�~Ð*Ô+Øò	Ðô Ó
-Ð,Ô
-Ô-Øð ‡~�~Ð*Ô+¨s¯~©~Ð>SÔ/TØòÐô Ó
9Ð&8Ô
9Ô9Øàó    Úattempts_nextc           
      óÖ   — d}| xs dj                  d«      rdnd}t        ||dt        d|dz
  «      z  z  «      }t        |t	        j
                  d	d
«      z  «      }t        ||z   «      S )u¼   
    Backoff exponencial con jitter, con cap. Evita martillear la API.

    - cannot_post_now: arranca mÃ¡s alto.
    - cap: 6h (el sistema sigue intentando, pero sin bucle agresivo).
    i`T  Ú rC   é<   é   é   r   r    g        gš™™™™™É?)r^   ÚminÚmaxÚintÚrandomÚuniform)r@   rg   ÚcapÚbaseÚexpÚjitters         r)   Ú_backoff_secondsrv   r   sm   € ð €CØ’)˜×'Ñ'Ð(9Ô:‰2À€DÜ
ˆc�4˜1¤ A }°qÑ'8Ó 9Ñ9Ñ:Ó
;€CÜ�”v—~‘~ c¨3Ó/Ñ/Ó0€FÜˆs�V‰|ÓÐrf   c                 óÌ   — 	 | d   xs dj                  «       }|rd|› �S 	 | d   xs dj                  «       }|rd|dd › �S dS # t        $ r d}Y Œ8w xY w# t        $ r d}Y Œ*w xY w)	z—
    Clave para agrupar 'cooldown' por cuenta.
    Preferimos account_alias; si no existe (legacy), degradamos a un hash parcial del access_token.
    Úaccount_aliasri   zalias:Úaccess_tokenztoken:Né   Úunknown)Ústripr$   )ÚrowÚaliasÚtoks      r)   Ú_job_account_keyr€   €   s•   € ð
Ø�_Ñ%Ò+¨×2Ñ2Ó4ˆñ Ø˜�wÐÐðØ�>Ñ"Ò( b×/Ñ/Ó1ˆñ #&ˆV�C˜˜�H�:ÐÐ4¨9Ð4øô ò ØŠðûô ò ØŠðús"   ‚A ¡A ÁAÁAÁA#Á"A#Úvaluec                 óF   — | xs dj                  «       j                  «       S )Nri   )r|   ra   )r�   s    r)   Ú_normalize_job_statusrƒ   “   s   € ØŠK�R×ÑÓ ×&Ñ&Ó(Ð(rf   c                 óF   — | sy | d   | d   | d   | d   | d   | d   | d   dœS )	NÚidÚstatusÚpublish_at_tsÚupdated_at_tsÚ
publish_idÚlast_statusÚlast_fail_reason)Újob_idr†   r‡   rˆ   r‰   rŠ   r‹   rO   )r}   s    r)   Ú_serialize_job_matchr�   —   sI   € ÙØà�d‘)Ø�h‘-Ø˜_Ñ-Ø˜_Ñ-Ø˜,Ñ'Ø˜=Ñ)ØÐ 2Ñ3ñð rf   Údb_pathrx   Úoriginal_filenamec                óT  — t        | «      }|j                  «       }|j                  d||f«       |j                  «       }|j	                  «        d}d}|D ]+  }t        |d   «      }	|	t        v r|€|}Œ|	t        v sŒ'|�Œ*|}Œ- ||t        |«      |du t        |«      t        |«      dœS )zù
    Busca trabajos previos del mismo video para una cuenta concreta.

    Regla de negocio:
      - Un job activo (scheduled/submitting/submitted) bloquea nuevas altas.
      - Un job terminal (complete/failed/cancelled) NO bloquea re-subidas.
    a–  
        SELECT id, status, publish_at_ts, updated_at_ts, publish_id, last_status, last_fail_reason
        FROM bulk_jobs
        WHERE account_alias=? AND original_filename=?
        ORDER BY
          CASE
            WHEN LOWER(COALESCE(status, '')) IN ('scheduled', 'submitting', 'submitted') THEN 0
            ELSE 1
          END,
          publish_at_ts DESC,
          updated_at_ts DESC
        Nr†   )rx   r�   Úmatches_totalÚcan_scheduleÚactive_matchÚlatest_terminal_match)
r   r/   r!   r"   Úcloserƒ   ÚACTIVE_JOB_STATUSESÚTERMINAL_JOB_STATUSESr4   r�   )
rŽ   rx   r�   r7   r%   r&   r“   r”   r}   r†   s
             r)   Ú*_lookup_existing_jobs_by_original_filenamer˜   ¥   sÍ   € ô �7Ó€DØ
�+‰+‹-€CØ‡K�Kð	ð 
Ð)Ð*ôð �<‰<‹>€DØ‡J�J„Là€LØ ÐØò (ˆÜ& s¨8¡}Ó5ˆØÔ(Ñ(¨\Ð-AØ‰LØÔ,Ò,Ð1FÑ1NØ$'Ñ!ð(ð 'Ø.Ü˜T›Ø$¨Ð,Ü,¨\Ó:Ü!5Ð6KÓ!Lñð rf   z/api/bulk/publishÚPOSTÚapi_bulk_publish)ÚmethodsÚendpointc                  ó 
  — t        «        t        j                  j                  d«      xs dj	                  «       } t        j                  j                  d«      xs dj	                  «       }| r	 t        | «      }|st        dd	d
œ«      dfS dt        j                  vrt        ddd
œ«      dfS t        j                  d   }|j                  st        ddd
œ«      dfS t        j                  j                  d«      xs dj	                  «       }t        j                  j                  d«      xs dj	                  «       }|st        ddd
œ«      dfS t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }	t        j                  j                  dd«      dk(  }
t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }|r|dk(  rt        ddd
œ«      dfS 	 t        |«      }t        |«      \  }}|st        dd|dœ«      dfS |j                  d«      xs g }||vrt        dd |d!œ«      dfS |j                  d"«      d#u rd}|j                  d$«      d#u rd}|j                  d%«      d#u rd}| }| }| }t        j                  d&   }t        |j                  «      }t!        t#        j"                  «       «      › d'|› �}t$        j&                  j)                  ||«      }|j+                  |«       |j                  d(«      }t-        |«      }t/        |t         «      r7|d)kD  r2|d)kD  r-||kD  r(	 t%        j0                  |«       t        dd*||d+œ«      dfS t3        |t        j                  d,   t        j                  d-   t        j                  d.   ¬/«      }	 t5        ||||||||
|d0|¬1«      }|j                  d2«      xs i j                  d3«      }t        d#||||| xs dd4œ«      d5fS # t        $ r$}t        ddt        |«      dœ«      dfcY d}~S d}~ww xY w# t        $ r$}t        ddt        |«      dœ«      dfcY d}~S d}~ww xY w# t        $ r Y �Œw xY w# t        $ r$}t        dd6t        |«      dœ«      dfcY d}~S d}~ww xY w)7uà   
    Publish inmediato (server-to-server). AquÃ­ SÃ� es correcto generar video_url firmado,
    porque se publica en el momento (la ventana de TTL es suficiente).
    Soporta: account_alias (recomendado) o access_token.
    rx   ri   ry   FÚalias_token_failed©ÚokÚerrorÚdetailsé�  NÚ%missing_access_token_or_account_alias©r    r¡   Úvideor[   r\   ÚtitleÚprivacy_levelrZ   Úallow_commentÚ1Ú
allow_duetÚallow_stitchÚcommercial_toggleÚ0Úbrand_organic_toggleÚbrand_content_toggleÚis_aigcÚ	SELF_ONLYrY   rU   rC   )r    r¡   ÚreasonÚprivacy_level_optionsrX   )r    r¡   ÚoptionsÚcomment_disabledTÚduet_disabledÚstitch_disabledÚ
UPLOAD_DIRÚ_Úmax_video_post_duration_secr   rW   )r    r¡   Údurrn   ÚPUBLIC_BASE_URLÚMEDIA_SIGNING_SECRETÚMEDIA_TOKEN_TTL_SECONDS)Ústored_filenameÚpublic_base_urlÚsigning_secretÚttl_secondsÚPULL_FROM_URL)ry   Úcaptionr¨   Údisable_commentÚdisable_duetÚdisable_stitchr°   r¯   r±   ÚmodeÚ	video_urlÚdatar‰   )r    r‰   rÊ   Ú	init_resprÀ   rx   éÈ   rT   )r   r   ÚformÚgetr|   r	   r$   r   r`   ÚfilesÚfilenamer   r   r   Úconfigr   ro   ÚtimeÚosÚpathr2   Úsaver   Ú
isinstanceÚremover   r   )rx   ry   rR   Úfiler§   r¨   r©   r«   r¬   r­   r¯   r°   r±   Úcreator_infoÚcan_post_nowr³   rµ   rÆ   rÇ   rÈ   Ú
upload_dirÚoriginal_fnrÀ   Ú	save_pathÚmax_durr¼   rÊ   rÌ   r‰   s                                r)   rš   rš   Ô   s  € ô Ôä—\‘\×%Ñ% oÓ6Ò<¸"×CÑCÓE€MÜ—L‘L×$Ñ$ ^Ó4Ò:¸×AÑAÓC€Láð	aÜ5°mÓDˆLñ Ü˜eÐ.UÑVÓWÐY\Ð\Ð\à”g—m‘mÑ#Ü˜e¨oÑ>Ó?ÀÐDÐDä�=‰=˜Ñ!€DØ�=Š=Ü˜eÐ.@ÑAÓBÀCÐGÐGä�\‰\×Ñ˜gÓ&Ò,¨"×3Ñ3Ó5€EÜ—\‘\×%Ñ% oÓ6Ò<¸"×CÑCÓE€MÙÜ˜eÐ.EÑFÓGÈÐLÐLä—L‘L×$Ñ$ _°cÓ:¸cÑA€MÜ—‘×!Ñ! ,°Ó4¸Ñ;€JÜ—<‘<×#Ñ# N°CÓ8¸CÑ?€LäŸ™×(Ñ(Ð)<¸cÓBÀcÑIÐÜ"Ÿ<™<×+Ñ+Ð,BÀCÓHÈCÑOÐÜ"Ÿ<™<×+Ñ+Ð,BÀCÓHÈCÑOÐÜ�l‰l×Ñ˜y¨#Ó.°#Ñ5€Gá °Ò <Ü˜eÐ.KÑLÓMÈsÐRÐRð^Ü)¨,Ó7ˆô 0°Ó=Ñ€L�&ÙÜ˜eÐ.?È6ÑRÓSÐUXÐXÐXð ×ÑÐ6Ó7Ò=¸2€GØ˜GÑ#Ü˜eÐ.EÐRYÑZÓ[Ð]`Ð`Ð`ð ×ÑÐ*Ó+¨tÑ3ØˆØ×Ñ˜Ó(¨DÑ0Øˆ
Ø×ÑÐ)Ó*¨dÑ2Øˆà'Ð'€OØ!�>€LØ%Ð%€Nä×#Ñ# LÑ1€Jä §¡Ó.€KÜœTŸY™Y›[Ó)Ð*¨!¨K¨=Ð9€OÜ—‘—‘˜Z¨Ó9€IØ‡I�IˆiÔð ×ÑÐ<Ó=€GÜ
(¨Ó
3€CÜ�'œ3Ô G¨a¢K°C¸!²GÀÀgÂð	Ü�I‰I�iÔ ô ˜eÐ.DÈSÐY`ÑaÓbÐdgÐgÐgô 'Ø'Ü#×*Ñ*Ð+<Ñ=Ü"×)Ñ)Ð*@ÑAÜ×&Ñ&Ð'@ÑAô	€Ið]Ü,Ø%ØØ'Ø+Ø%Ø)Ø!5Ø!5ØØ Øô
ˆ	ð  —m‘m FÓ+Ò1¨r×6Ñ6°|ÓDˆ
ÜàØ(Ø&Ø&Ø#2Ø!.Ò!6°$ñó	
ð ð	ð 		øôC ò 	aÜ %Ð2FÔSVÐWXÓSYÑZÓ[Ð]`Ð`Õ`ûð	aûôB ò ^Ü˜eÐ.CÔPSÐTUÓPVÑWÓXÐZ]Ð]Õ]ûð^ûôJ ò 	Úð	ûôJ ò ]Ü˜eÐ.BÌsÐSTËvÑVÓWÐY\Ð\Õ\ûð]úsm   Á0Q0 ÉR  Î4S ÐAS  Ñ0	RÑ9RÒRÒRÒ 	SÒ)SÓSÓSÓ	SÓSÓ 	TÓ)TÔTÔTz/api/bulk/statusÚapi_bulk_statusc                  óx  — t        «        t        j                  j                  d«      xs dj	                  «       } t        j                  j                  d«      xs dj	                  «       }| r	 t        | «      }t        j                  j                  d	«      xs dj	                  «       }|r|st        dd
dœ«      dfS 	 t        ||«      }t        d|dœ«      dfS # t        $ r$}t        ddt        |«      dœ«      dfcY d}~S d}~ww xY w# t        $ r#}t        dt        |«      dœ«      dfcY d}~S d}~ww xY w)zJ
    Status (server-to-server). Soporta account_alias o access_token.
    rx   ri   ry   Frž   rŸ   r£   Nr‰   Ú"missing_access_token_or_publish_idr¥   T)r    rË   rÍ   )
r   r   rÎ   rÏ   r|   r	   r$   r   r`   r   )rx   ry   rR   r‰   rË   s        r)   rà   rà   R  s%  € ô
 Ôä—\‘\×%Ñ% oÓ6Ò<¸"×CÑCÓE€MÜ—L‘L×$Ñ$ ^Ó4Ò:¸×AÑAÓC€Láð	aÜ5°mÓDˆLô —,‘,×"Ñ" <Ó0Ò6°B×=Ñ=Ó?€JÙ™zÜ˜eÐ.RÑSÓTÐVYÐYÐYð<Ü  ¨zÓ:ˆÜ˜d¨DÑ1Ó2°CÐ7Ð7øô ò 	aÜ %Ð2FÔSVÐWXÓSYÑZÓ[Ð]`Ð`Õ`ûð	aûô ò <Ü˜e¬c°!«fÑ5Ó6¸Ð;Õ;ûð<ús<   Á0C ÃD Ã	D
Ã&DÃ?D
ÄD
Ä	D9ÄD4Ä.D9Ä4D9z/api/bulk/find_existingÚapi_bulk_find_existingc                  óÚ  — t        «        t        j                  j                  d«      xs dj	                  «       } t        j                  j                  d«      xs# t        j                  j                  d«      xs dj	                  «       }| st        dddœ«      dfS |st        dd	dœ«      dfS t        |«      }t        t        j                  d
   | |¬«      }t        ddi|¥«      dfS )u  
    Lookup server-to-server para deduplicaciÃƒÂ³n por alias + nombre de archivo original.

    El scheduler externo puede usar `can_schedule`:
      - True  => no hay job activo bloqueando.
      - False => ya existe uno en curso para ese video/cuenta.
    rx   ri   r�   rÑ   FÚaccount_alias_requiredr¥   r£   Úmissing_original_filenameÚBULK_DB_PATH©rx   r�   r    TrÍ   )
r   r   rÎ   rÏ   r|   r   r   r˜   r   rÒ   )rx   Úoriginal_filename_rawr�   Úlookups       r)   rã   rã   m  sÞ   € ô Ôä—\‘\×%Ñ% oÓ6Ò<¸"×CÑCÓE€MÜ$Ÿ\™\×-Ñ-Ð.AÓBÒhÄgÇlÁl×FVÑFVÐWaÓFbÒhÐfh×oÑoÓqÐáÜ˜eÐ.FÑGÓHÈ#ÐMÐMÙ Ü˜eÐ.IÑJÓKÈSÐPÐPä%Ð&;Ó<ÐÜ7Ü×Ñ˜>Ñ*Ø#Ø+ô€Fô
 �D˜$Ð) &Ð)Ó*¨CÐ/Ð/rf   z/api/bulk/scheduleÚapi_bulk_schedulec                  óì	  — t        «        t        j                  j                  d«      xs dj	                  «       } | st        dddœ«      dfS 	 t        | «      }t        j                  j                  d
«      xs dj	                  «       }|st        dddœ«      dfS 	 t        |«      }dt        j                  vrt        dddœ«      dfS t        j                  d   }|j                  st        dddœ«      dfS t        j                  j                  d«      xs dj	                  «       }t        j                  j                  d«      xs dj	                  «       }|st        dddœ«      dfS t        j                  j                  d«      xs dj	                  «       j                  «       }|dvrt        dddœ«      dfS t        j                  j                  dd«      dk(  }	t        j                  j                  dd«      dk(  }
t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }t        j                  j                  dd«      dk(  }|r|d k(  rt        dd!dœ«      dfS t        j                  d"   }t        |j                  «      }t!        t        j                  d#   | |¬$«      }d	}|dk(  r|d%   }n|d&k(  r|d%   xs |d'   }|r8t#        |d(   «      }t        d)d)|t$        v rd*nd+|d,   |d(   |d-   || |d.œ	«      d/fS t'        j(                  «       j*                  }|› d0|› �}t,        j.                  j1                  ||«      }|j3                  |«       t5        t7        j6                  «       «      }i d1|“d2|“d-|“d3|“d(d4“d5d6“d7d	“d8|“d| “d|“d|“d|	rd9nd6“d|
rd9nd6“d|rd9nd6“d|rd9nd6“d|rd9nd6“d|rd9nd6“|rd9nd6||dd	d	d	|d:œ¥}t9        t        j                  d#   «      }t;        ||«       |j=                  «        |j?                  «        t        d)|||| d;œ«      d/fS # t        $ r$}t        ddt        |«      dœ«      dfcY d	}~S d	}~ww xY w# t        $ r$}t        ddt        |«      dœ«      dfcY d	}~S d	}~ww xY w)<uM  
    Schedule (server-to-server). Para jobs programados, account_alias es requerido
    para que el cron/worker pueda refrescar tokens con refresh_token.

    Importante:
      - NO generamos video_url firmado aquÃ­, porque caduca (TTL) antes de publish.
      - El worker debe generar video_url justo en el momento de publicar.
    rx   ri   FÚ)account_alias_required_for_scheduled_jobsr¥   r£   rž   rŸ   NÚ
publish_atÚmissing_publish_atÚinvalid_publish_atr¦   r[   r\   r§   r¨   rZ   Údedupe_modeÚactive_only>   ÚallÚnonerò   Úinvalid_dedupe_moder©   rª   r«   r¬   r­   r®   r¯   r°   r±   r²   rY   r¹   rç   rè   r“   ró   r”   r†   TÚalready_scheduledÚalready_existsrŒ   r‡   )	r    Úskippedr³   rŒ   r†   r‡   r�   rx   rñ   rÍ   rº   r…   Úcreated_at_tsÚnext_attempt_tsr   Úattemptsr   Ú
last_errorry   r    )r±   rÀ   r�   rÊ   r‰   rŠ   r‹   rˆ   )r    rŒ   r‡   rÀ   rx   ) r   r   rÎ   rÏ   r|   r   r	   r$   r`   r
   rÐ   rÑ   ra   r   rÒ   r   r˜   rƒ   r–   ÚuuidÚuuid4ÚhexrÔ   rÕ   r2   rÖ   ro   rÓ   r   r?   Úcommitr•   )rx   ry   rR   rî   r‡   rÙ   r§   r¨   rñ   r©   r«   r¬   r­   r¯   r°   r±   rÜ   rÝ   Úexisting_lookupÚexisting_matchÚexisting_statusrŒ   rÀ   rÞ   Únow_tsr+   r7   s                              r)   rë   rë   ‰  ss  € ô Ôä—\‘\×%Ñ% oÓ6Ò<¸"×CÑCÓE€MÙÜ˜eÐ.YÑZÓ[Ð]`Ð`Ð`ð]Ü1°-Ó@ˆô —,‘,×"Ñ" <Ó0Ò6°B×=Ñ=Ó?€JÙÜ˜eÐ.BÑCÓDÀcÐIÐIð]Ü2°:Ó>ˆð ”g—m‘mÑ#Ü˜e¨oÑ>Ó?ÀÐDÐDä�=‰=˜Ñ!€DØ�=Š=Ü˜eÐ.@ÑAÓBÀCÐGÐGä�\‰\×Ñ˜gÓ&Ò,¨"×3Ñ3Ó5€EÜ—\‘\×%Ñ% oÓ6Ò<¸"×CÑCÓE€MÙÜ˜eÐ.EÑFÓGÈÐLÐLä—<‘<×#Ñ# MÓ2ÒC°m×JÑJÓL×RÑRÓT€KØÐ8Ñ8Ü˜eÐ.CÑDÓEÀsÐJÐJä—L‘L×$Ñ$ _°cÓ:¸cÑA€MÜ—‘×!Ñ! ,°Ó4¸Ñ;€JÜ—<‘<×#Ñ# N°CÓ8¸CÑ?€LäŸ™×(Ñ(Ð)<¸cÓBÀcÑIÐÜ"Ÿ<™<×+Ñ+Ð,BÀCÓHÈCÑOÐÜ"Ÿ<™<×+Ñ+Ð,BÀCÓHÈCÑOÐÜ�l‰l×Ñ˜y¨#Ó.°#Ñ5€Gá °Ò <Ü˜eÐ.KÑLÓMÈsÐRÐRä×#Ñ# LÑ1€JÜ §¡Ó.€Kä@Ü×Ñ˜>Ñ*Ø#Ø%ô€Oð
 €NØ�mÒ#Ø(¨Ñ8‰Ø	˜Ò	Ø(¨Ñ8Òd¸OÐLcÑ<dˆáÜ/°¸xÑ0HÓIˆÜàØØ1@ÔDWÑ1WÑ-Ð]mØ(¨Ñ2Ø(¨Ñ2Ø!/°Ñ!@Ø%0Ø!.Ø*ñ
ó
ð ðð 	ô �Z‰Z‹\×Ñ€FØ˜  + Ð/€OÜ—‘—‘˜Z¨Ó9€IØ‡I�IˆiÔä”—‘“Ó€FðØˆfðà˜ðð 	˜ðð 	˜=ð	ð
 	�+ðð 	�Aðð 	�dðð 	˜ðð 	˜ðð 	�ðð 	˜ðð 	™m™°ðð 	™:‘a¨1ðð 	™\™¨qðð 	Ñ"3™Q¸ðð  	Ñ%9¡¸qð!ð" 	Ñ%9¡¸qð#ñ$  ‘1 QØ*Ø(ØØØØ Øò3€Cô8 ”;×%Ñ% nÑ5Ó6€DÜ�T˜3ÔØ‡K�K„MØ‡J�J„LäàØØ*Ø.Ø*ñ	
óð ðð øôa ò ]Ü˜eÐ.BÌsÐSTËvÑVÓWÐY\Ð\Õ\ûð]ûô ò ]Ü˜eÐ.BÌsÐSTËvÑVÓWÐY\Ð\Õ\ûð]ús<   ÁR ÂS Ò	SÒR>Ò8SÒ>SÓ	S3ÓS.Ó(S3Ó.S3z/api/bulk/process_dueÚapi_bulk_process_duec            	      ól  — t        «        t        t        j                  j	                  dd«      xs d«      } t        t        j
                  «       «      }t        t        j                  j	                  dd«      xs d«      }t        t        j                  d   «      }|j                  «       }ddddddœ}|j                  d|| f«       |j                  «       }t        d	t        |«      › d
�«       g }g }t!        «       }	|D ]E  }
t#        |
«      }||	v r|j%                  |
«       Œ$|	j'                  |«       |j%                  |
«       ŒG |rP|dkD  rK||z   }|D ]  }|j                  d|||d   |f«       Œ |dxx   t        |«      z  cc<   |j)                  «        |D �]�  }
|
d   }|j                  d||f«       |j*                  dk7  rŒ-|j)                  «        	 t-        |
t        j                  d   t        j                  d   t        j                  d   t        j                  d   ¬«      \  }}}|rQ|}t7        |
«      }|j                  d|||||f«       |j*                  dk(  r|dxx   dz  cc<   |j)                  «        Œé|xs d}t        |
d   «      dz   }t9        |«      rC|t;        ||«      z   }|j                  d|||||f«       |j*                  dk(  r@|dxx   dz  cc<   n2|j                  d||||f«       |j*                  dk(  r|d xx   dz  cc<   |j)                  «        �Œ’ |j                  d!| f«       |j                  «       }|D ]  }
|
d   }t=        |
«      \  }}|sŒ|d"v r!|d#k(  rd$nd%}|j                  d&|||||f«       n|j                  d'||||f«       |j*                  dk(  r|d(xx   dz  cc<   |j)                  «        Œ� |j?                  «        tA        d)||d*œ«      d+fS # t.        $ r1}d}dt1        |«      j2                  › dt5        |«      › �}Y d}~�Œúd}~ww xY w),u�  
    Cron endpoint:
      - Auth: X-Api-Key
      - Procesa jobs due (scheduled) y refresca jobs submitted.

    Mejoras:
      - Reintento con backoff para errores transitorios (incl. 5xx/internal_error).
      - Cooldown por cuenta para no disparar 2 publicaciones seguidas en segundos.
      - ProtecciÃ³n anti-atasco: si el worker lanza excepciÃ³n, requeue en lugar de dejar "submitting".
    Úmax_jobsÚ10ÚBULK_MIN_SECONDS_BETWEEN_POSTSÚ180rç   r   )Úscheduled_submittedÚscheduled_failedÚscheduled_requeuedÚsubmitted_updatedÚscheduled_deferred_by_cooldownz–
        SELECT * FROM bulk_jobs
        WHERE status='scheduled' AND next_attempt_ts <= ?
        ORDER BY publish_at_ts ASC
        LIMIT ?
        zDEBUG: Found z	 due jobsz´
                UPDATE bulk_jobs
                SET next_attempt_ts=?, updated_at_ts=?
                WHERE id=? AND status='scheduled' AND next_attempt_ts <= ?
                r…   r  z�
            UPDATE bulk_jobs
            SET status='submitting', updated_at_ts=?
            WHERE id=? AND status='scheduled'
            r    r¹   r½   r¾   r¿   )rÜ   rÁ   rÂ   rÃ   Fzworker_exception: z: Na½  
                UPDATE bulk_jobs
                SET status='submitted',
                    publish_id=?,
                    attempts=attempts+1,
                    last_error=NULL,
                    last_status=NULL,
                    last_fail_reason=NULL,
                    access_token=?,
                    next_attempt_ts=?,
                    updated_at_ts=?
                WHERE id=? AND status='submitting'
                r  Úunknown_errorrû   a  
                UPDATE bulk_jobs
                SET status='scheduled',
                    attempts=?,
                    last_error=?,
                    next_attempt_ts=?,
                    updated_at_ts=?
                WHERE id=? AND status='submitting'
                r  zð
                UPDATE bulk_jobs
                SET status='failed',
                    attempts=?,
                    last_error=?,
                    updated_at_ts=?
                WHERE id=? AND status='submitting'
                r  z}
        SELECT * FROM bulk_jobs
        WHERE status='submitted'
        ORDER BY updated_at_ts ASC
        LIMIT ?
        )ÚPUBLISH_COMPLETEÚFAILEDÚSEND_TO_USER_INBOXr  r   r   zñ
                UPDATE bulk_jobs
                SET status=?,
                    last_status=?,
                    last_fail_reason=?,
                    updated_at_ts=?
                WHERE id=? AND status='submitted'
                zÓ
                UPDATE bulk_jobs
                SET last_status=?,
                    last_fail_reason=?,
                    updated_at_ts=?
                WHERE id=? AND status='submitted'
                r  T)r    Ú	processedr  rÍ   )!r   ro   r   rÎ   rÏ   rÓ   rÔ   Úenvironr   r   rÒ   r/   r!   r"   Úprintr4   r0   r€   r#   Úaddr   Úrowcountr   r$   ÚtypeÚ__name__r`   r   re   rv   r   r•   r   )r  r  Ú
cooldown_sr7   r%   r  ÚdueÚpickedÚdeferredÚseenr}   ÚkeyÚbump_tsr(   rŒ   r    ÚmsgÚ
_init_resprR   r‰   Úused_access_tokenr@   rg   Únext_tsÚsubsr†   Úfail_reasonÚ
new_statuss                               r)   r  r    s’  € ô Ôä”7—<‘<×#Ñ# J°Ó5Ò=¸Ó>€HÜ”—‘“Ó€Fô ”R—Z‘Z—^‘^Ð$DÀeÓLÒUÐPUÓV€Jä”;×%Ñ% nÑ5Ó6€DØ
�+‰+‹-€Cð  !ØØØØ*+ñ€Ið ‡K�Kð	ð 
�Ðôð �,‰,‹.€CÜ	ˆMœ#˜c›(˜ 9Ð
-Ô.à€FØ€HÜ‹5€Dàò ˆÜ˜sÓ#ˆØ�$‰;Ø�O‰O˜CÔ ØØ�‰�ŒØ�‰�cÕðñ �J ’NØ˜:Ñ%ˆØò 	ˆAØ�K‰Kðð
 ˜& ! D¡'¨6Ð2õð	ð 	Ð2Ó3´s¸8³}ÑDÓ3Ø�‰Œàó XˆØ�T‘ˆð 	�‰ðð
 �VÐô	
ð �<‰<˜1ÒØØ�‰Œð
	DÜ";ØÜ&×-Ñ-¨lÑ;Ü +× 2Ñ 2Ð3DÑ EÜ*×1Ñ1Ð2HÑIÜ'×.Ñ.Ð/HÑIô#ÑˆB��Zñ ØˆJÜ <¸SÓ AÐà�K‰Kðð Ð.°¸ÀÐGôð  �|‰|˜qÒ ØÐ/Ó0°AÑ5Ó0Ø�K‰KŒMØð Ò$�_ˆÜ˜C 
™OÓ,¨qÑ0ˆä˜sÔ#ØÔ/°°]ÓCÑCˆGØ�K‰Kðð   W¨f°fÐ=ôð �|‰|˜qÒ ØÐ.Ó/°1Ñ4Ô/à�K‰Kðð   V¨VÐ4ô
ð �|‰|˜qÒ ØÐ,Ó-°Ñ2Ó-à�‰ŽðqXðv ‡K�Kð	ð 
ˆôð �<‰<‹>€Dàò "ˆØ�T‘ˆÜ:¸3Ó?Ñˆ�ÙØð ÐIÑIØ'-Ð1CÒ'C™ÈˆJØ�K‰Kðð ˜V [°&¸&ÐAõ
ð �K‰Kðð ˜ f¨fÐ5ô	ð �<‰<˜1ÒØÐ)Ó*¨aÑ/Ó*Ø�‰�ðE"ðH 	‡J�J„LÜ˜$¨YÀ&ÑIÓJÈCÐOÐOøôg ò 	DØˆBØ&¤t¨A£w×'7Ñ'7Ð&8¸¼3¸q»6¸(ÐCŽCûð	Dús   ÇAO9Ï9	P3Ð&P.Ð.P3)3rÔ   rÓ   rý   rp   Úflaskr   r   r   r   Údb.bulkr   Úservices.authr   r	   Úservices.creatorr
   r   Úservices.mediar   r   r   Úservices.bulk_workerr   r   r   Útiktok_clientr   r   r   r  Úbpr—   r–   r`   r*   Údictr?   Úboolre   ro   rv   r€   rƒ   r�   r˜   Úrouterš   rà   rã   rë   r  rO   rf   r)   ú<module>r4     s«  ðã 	Û Û Û ß :Ó :å ß Jß Mß `Ñ `÷ñ ÷ ZÑ Yáˆz˜8Ó$€â;Ð Ú>Ð ð Có ð" ó ð"3˜Sð 3 Tó 3ðl˜#ð ¨cð °có ð5˜Só 5ð&) ð )¨ó )òð,¸ð ,Èsð ,Ðgjó ,ð^ ‡�Ð
¨ xÐ:L€ÓMñz]ó Nðz]ðz ‡�Ð
 v hÐ9J€ÓKñ<ó Lð<ð4 ‡�Ð
#¨f¨XÐ@X€ÓYñ0ó Zð0ð6 ‡�Ð
¨¨Ð;N€ÓOñKó PðKð\ ‡�Ð
!¨F¨8Ð>T€ÓUñQPó VñQPrf   