+
    ̘ia>                        R t ^ RIt^ RIt^ RIt^ RIHtHt ^ RIHt ^ RIH	t	 ^ RI
Ht ^ RIHt ^ RIHt ^ RIHt ]P$                  ! 4       R	,          R
,          t]R,          R,          t]R,          R,          t]R,          tR.tR tR tR tR tRR ltR R ltR!R ltR tR"R lt R t!R t"R t#R#R lt$R t%]&R8X  d
   ]%! 4        R# R# )$zc
YouTube Smart Playlist Manager
Manages Dustin's curated YouTube playlists with automatic refresh.
N)datetime	timedelta)Path)Request)Credentials)InstalledAppFlow)build)	HttpErrorz	.openclaw	workspacesecretszyoutube-oauth-credentials.jsonzyoutube-token.picklezyoutube-channels.jsonz'https://www.googleapis.com/auth/youtubec                 P   Rp \         P                  4       '       d8   \        \         R4      ;_uu_ 4       p\        P                  ! V4      p RRR4       V '       d   V P
                  '       Eg   V '       d?   V P                  '       d-   V P                  '       d   V P                  \        4       4       Mf\        P                  4       '       g   \        R\         24      h\        P                  ! \        \        4      \        4      pVP!                  RR7      p \         P"                  P%                  RRR7       \        \         R4      ;_uu_ 4       p\        P&                  ! W4       RRR4       \)        R	R
V R7      #   + '       g   i     EL>; i  + '       g   i     L2; i)z&Get authenticated YouTube API service.NrbzCredentials file not found: i  )portT)parentsexist_okwbyoutubev3)credentials)
TOKEN_FILEexistsopenpickleloadvalidexpiredrefresh_tokenrefreshr   CREDENTIALS_FILEFileNotFoundErrorr   from_client_secrets_filestrSCOPESrun_local_serverparentmkdirdumpr   )credstokenflows      Q/Users/tonyclaw/.openclaw/workspace/scripts/youtube-playlists/playlist_manager.pyget_authenticated_servicer+      s   E *d##uKK&E $ U]]]u':':':MM')$#**,,'*FGWFX(YZZ#<<SAQ=RTZ[D))t)4E 	t<*d##uKK% $ De44% $## $#s   FFF	F%	c                     \        \        R4      ;_uu_ 4       p \        P                  ! V 4      uuRRR4       #   + '       g   i     R# ; i)zLoad playlist configuration.rN)r   CONFIG_FILEjsonr   )fs    r*   load_configr1   7   s+    	k3		1yy| 
 			s	   :A	c                 J    \         P                  ! 4       P                  R4      # )z"Get current date in YYMMDD format.z%y%m%d)r   nowstrftime     r*   get_date_suffixr7   =   s    <<>""8,,r6   c                V   ^ RI pVP                  RV 4      pV'       g   ^ # \        VP                  ^4      ;'       g    ^ 4      p\        VP                  ^4      ;'       g    ^ 4      p\        VP                  ^4      ;'       g    ^ 4      pV^<,          V,           V^<,          ,           # )z#Parse ISO 8601 duration to minutes.Nz#PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)rematchintgroup)duration_strr9   r:   hoursminutessecondss   &     r*   parse_durationrA   B   s     HH;\JEA##!$E%++a.%%A&G%++a.%%A&G2:'B,..r6   c                    . pV  FF  pVP                  R^ 4      pV'       d	   WQ8  d   K%  V'       d	   WR8  d   K5  VP                  V4       KH  	  V# )zFilter videos by duration.duration_minutes)getappend)videosmin_minutesmax_minutesfilteredvideodurations   &&&   r*   filter_by_durationrL   O   sK    H99/38181  Or6   c                F   . p\         P                  ! 4       \        VR7      ,
          P                  4       R,           p V P	                  4       P                  RVRV\        V^24      RR7      pVP                  4       pVP                  R. 4       Uu. uF  qR,          R	,          NK  	  p	pV	'       d   V P                  4       P                  R
RP                  V	4      R7      p
V
P                  4       pVP                  R. 4       Fh  p\        VR,          R,          4      pVP                  RVR,          RVR,          R,          RVR,          R,          RVR,          R,          RV/4       Kj  	  V# u upi   \         d   p\        RT RT 24        Rp?T# Rp?ii ; i)z!Get recent videos from a channel.daysZsnippetdaterJ   )part	channelIdorderpublishedAfter
maxResultstypeitemsidvideoIdcontentDetails,snippet,rS   rZ   contentDetailsrK   video_idtitlechannelchannelTitle	publishedpublishedAtrC   z"Error fetching videos for channel : Nr   utcnowr   	isoformatsearchlistminexecuterD   rF   joinrA   rE   r	   print)r   
channel_id	days_backmax_resultsrF   published_afterrequestresponseitem	video_idsdetails_requestdetails_responserK   es   &&&&          r*   get_channel_videosr{   \   s   F(9)+DDOOQTWWO!F.."'' *;+ ( 
 ??$7?||GR7PQ7Pt$Z	**7P	Q%nn.33-88I& 4 O  /668(,,Wb9)$/?*@*LMT
T)_W5tI~>i!?&  : M/ R(  F2:,bDEEMF+   AE9 E4*CE9 4E9 9F FF c                F   . p\         P                  ! 4       \        VR7      ,
          P                  4       R,           p V P	                  4       P                  RVRV\        V^24      RR7      pVP                  4       pVP                  R. 4       Uu. uF  qR,          R	,          NK  	  p	pV	'       d   V P                  4       P                  R
RP                  V	4      R7      p
V
P                  4       pVP                  R. 4       Fh  p\        VR,          R,          4      pVP                  RVR,          RVR,          R,          RVR,          R,          RVR,          R,          RV/4       Kj  	  V# u upi   \         d   p\        RT RT 24        Rp?T# Rp?ii ; i)zSearch YouTube for videos.rN   rP   rQ   	relevancerJ   )rS   qrU   rV   rW   rX   rY   rZ   r[   r\   r]   r^   r_   rK   r`   ra   rb   rc   rd   re   rC   zError searching for '': Nrg   )r   queryrq   rr   rF   rs   rt   ru   rv   rw   rx   ry   rK   rz   s   &&&&          r*   search_videosr      s   F(9)+DDOOQTWWO5.."''*;+ ( 
 ??$7?||GR7PQ7Pt$Z	**7P	Q%nn.33-88I& 4 O  /668(,,Wb9)$/?*@*LMT
T)_W5tI~>i!?&  : M- R&  5%eWCs344M5r|   c                l    V P                  4       P                  RR^2R7      pVP                  4       pVP                  R. 4       FC  pVR,          R,          P	                  V4      '       g   K*  VR,          VR,          R,          3u # 	  R	#   \
         d   p\        RT 24        Rp?R	# Rp?ii ; i)
z&Find existing playlist by name prefix.rQ   T)rS   minerW   rY   ra   rZ   zError finding playlist: NNN)	playlistsrk   rm   rD   
startswithr	   ro   )r   name_prefixrt   ru   rv   rz   s   &&    r*   find_existing_playlistr      s    .##%** + 

 ??$LL"-DIw'22;??Dz4	?7#;;; .   .(,--.s$   A)B 0B B B3B..B3c                     V P                  4       P                  RRRVRV/RRR//R7      pVP                  4       pVR	,          #   \         d   p\	        R
T RT 24        Rp?R# Rp?ii ; i)zCreate a new playlist.zsnippet,statusrQ   ra   descriptionstatusprivacyStatusprivaterS   bodyrZ   zError creating playlist 'r   N)r   insertrm   r	   ro   )r   ra   r   rt   ru   rz   s   &&&   r*   create_playlistr      s    ##%,,!U!; #Y - 
 ??$~ )%A378s   AA A-A((A-c                     V P                  4       P                  VR7      P                  4        R#   \         d   p\	        RT 24        Rp?R# Rp?ii ; i)zDelete a playlist.)rZ   TzError deleting playlist: NF)r   deleterm   r	   ro   )r   playlist_idrz   s   && r*   delete_playlistr      sP    ""k"2::< )!-.s   .2 AAAc                    V P                  4       P                  RRRVRRRRV///R7      P                  4        R#   \         d{   pR	\	        T4      9   g   R
\	        T4      9   d   \        RT R24       M?R\	        T4      P                  4       9   d   \        RT R24       M\        RT RT 24        Rp?R# Rp?ii ; i)zAdd a video to a playlist.rQ   
playlistId
resourceIdkindzyoutube#videor[   r   TzVideo not foundvideoNotFoundz  Video z not found, skipping	duplicatez already in playlistz  Error adding video rf   NF)playlistItemsr   rm   r	   r!   ro   lower)r   r   r`   rz   s   &&& r*   add_video_to_playlistr      s    && + !8# 	' 	
 ') A&/SV*CHXJ&:;<CFLLN*HXJ&:;<)(2aS9:s   9= CA/B==Cc                   \        4       p. pV  Fv  pVR,          P                  4       P                  4       pR F  pVP                  VR4      pK  	  VR,          pWA9  g   KT  VP	                  V4       VP                  V4       Kx  	  V# )z,Remove duplicate videos by title similarity.ra    :N2   N)z| full episodez(full)z[full]z- full)setr   stripreplaceaddrE   )rF   seen_titlesuniquerJ   
normalizedsuffixs   &     r*   dedupe_videosr      s~    %KF7^))+113
FF#++FB7J G_
(OOJ'MM%   Mr6   c           
     	   VP                  V4      pV'       d   VP                  R4      '       d   R# \        RR@ 24       \        RVR,           24       \        R@ 4       VP                  RR4      pVP                  R/ 4      pVP                  R	VP                  R
/ 4      P                  R^4      4      pVP                  R4      pVP                  R^4      p	. p
VR8X  da   VP                  RV4      pVP                  RR4      p\        VP	                  RR4      4      p\        RV RV R24       \        WWR7      p
EMIVR8X  d   VP                  R. 4      pVP                  R/ 4      pV F>  p\        RVR,           24       \        V VR,          ^R7      pV
P                  V4       K@  	  VP                  R. 4       Fm  p\        RV 24       VP                  RR4      p\        VP	                  RR4      4      p\        WVVP                  R^4      R7      pV
P                  V4       Ko  	  MVVP                  R. 4      pV F>  p\        RVR,           24       \        V VR,          ^R7      pV
P                  V4       K@  	  \        R \        V
4       24       \        WVR!7      p
\        R"T R#T;'       g    R$ R%\        V
4       24       \        V
4      p
\        R&\        V
4       24       V
P                  R' R(R)7       V
RV	 p
\        R*V	 R+\        V
4       24       V'       du   \        R,4       V
R-,           F+  p\        R.VR/,          R0,           R1VR2,          R3 R424       K-  	  \        V
4      ^
8  d    \        R5\        V
4      ^
,
           R624       R# \        4       pVR,          pVP                  R7^4      p\        V4      ^8  dC   V^ ,          P                  4       '       d&   V^ ,           R7V R7V^,           2pV^ ,           R72pMV R7V 2pVR8,          p\        V V4      w  ppV'       d"   VV8w  d   \        R9V 24       \!        V V4       \        R:V 24       \#        V V4      pV'       g   \        R;4       R# ^ pV
 F(  p\%        V VVR<,          4      '       g   K  V^,          pK*  	  \        R=V R>\        V
4       R?24       R# )Az)Update a single playlist based on config._N
zProcessing: namerX   channelsdurationFilter
minMinutes_durationRulesglobalMinMinutes
maxMinutes	maxVideosrj   searchQuerysearchPeriod4ddr   zSearching for: z (last z days))rq   rr   hybridzFetching from: rT   )rq   querieszSearching: 7dmaxSearchResultszTotal videos found: )rG   rH   zAfter duration filter (-u   ∞z min): zAfter deduplication: c                     V R ,          # )rd   r5   )xs   &r*   <lambda>!update_playlist.<locals>.<lambda>V  s    anr6   T)keyreversezFinal count (max z): z"
[DRY RUN] Would add these videos::N
   N  - ra   :N<   Nz... (rC   z.0fzmin)z
  ... and z more :N   NzDeleting old playlist: zCreating playlist: zFailed to create playlist!r`   zAdded /z videos to playlist2==================================================)rD   r   ro   r;   r   r   r{   extendlenrL   r   sortr7   splitisdigitr   r   r   r   )r   configplaylist_keydry_runplaylist_configplaylist_typeduration_filterrG   rH   
max_videosrF   r   
period_strrO   r   search_configrb   channel_videossearch_videos_listvdate_suffixr   
name_parts	new_titlesearch_prefixold_id	old_titler   addedrJ   s   &&&&                          r*   update_playlistr     s   jj.Ol55c::	Bvh-	L01
23	VH#''
;M%))*:B?O!%%lFJJ?OQS4T4X4XYkmn4opK!%%l3K $$["5JF  ##M<@$((>
:%%c2./wgdV6:;wV	(	""&&z26'++Hb9  GOGFO#456/9MYZ[NMM.)   #&&y"5EKw'(&**>4@Jz))#r23D!.w;H;L;LM_ac;d"fMM,- 6 #&&z26GOGFO#456/9MYZ[NMM.)  
 
 V
./  [YF	#K=+2F2F1GwsSY{m
\] 6"F	!#f+
/0 KK,dK; KZ F	j\S[M
:;34AD7C)q1C/DS.INO v;Js6{R/067 "#K6"DC#J
:z!}4466!!}oQ{m1Z]OD	%a=/+"m1TF+	#B /wFFI)y('	{34( 
	{
+,!'95K*+ E +uZ7HIIQJE  
F5'3v;-':
;<r6   c                    ^ RI p V P                  RR7      pVP                  RRRR7       VP                  RR	R
R.RR7       VP                  RRRRR7       VP                  RRRRR7       VP                  RRRR7       VP                  4       pR
. R0OR. R1O/p\	        R4       \	        R24       \	        R4       \        4       p\	        R4       VP                  '       d   \	        R4       R# \        4       p\	        R\        V Uu. uF  qfP                  R4      '       d   K  VNK  	  up4       R24       VP                  '       d   VP                  .pEM]VP                  '       dI   VP                  VP                  . 4      p\	        RVP                   RR P                  V4       24       EMVP                  '       d9   VP                  4        Uu. uF  qfP                  R4      '       d   K  VNK  	  ppM\	        R!4       \	        R"4       \	        R#4       \	        R$4       \	        R%4       \	        R&4       \	        R'4       \	        R(4       \	        R)4       \	        R*4       V FC  pVP                  R4      '       d   K  \	        R+V RWX,          P                  R,V4       24       KE  	  R# V F0  p	W9   d   \!        WEWP"                  R-7       K"  \	        R.V	 24       K2  	  \	        R34       \	        R/4       R# u upi u upi )4zMain entry point.NzYouTube Smart Playlist Manager)r   z
--playlistz-pz*Update specific playlist (key from config))helpz--groupz-g12z=Update playlist group (1=News/Ministry, 2=Discovery/Projects))choicesr   z--allz-a
store_truezUpdate all playlists)actionr   z	--dry-runz-nz.Show what would be done without making changesz--authzJust authenticate and exitzAuthenticating...u   ✓ Authenticated successfullyzAuth-only mode, exiting.u   ✓ Loaded config with r   z
 playlistszRunning group rf   z, z
Usage:z,  --playlist KEY  Update a specific playlistzA  --group 1|2     Update playlist group (1=odd days, 2=even days)z&  --all           Update all playlistsz0  --dry-run       Preview without making changesz#  --auth          Just authenticatez
Groups:z:  Group 1 (odd days):  news, extra_news, ai_news, ministryz3  Group 2 (even days): openclaw, projects, peptidesz
Available playlists:r   r   )r   zUnknown playlist: zDone!)news
extra_newsai_newsministry)openclawprojectspeptidesr   z3
==================================================)argparseArgumentParseradd_argument
parse_argsro   r+   authr1   r   r   playlistr<   rD   rn   allkeysr   r   )
r   parserargsGROUPSr   r   kr   r   r   s
             r*   mainr     s   $$1Q$RF
d1]^
	4#s  CB  C
lAWX
T,Euv
<XYD 	:1F
 

*+	(O 

')G	
*+yyy() ]F	#CF(TFq,,sBSF(T$U#VV`
ab }}}]]O		JJtzz2.	tzzl"TYYy-A,BCD	 &G1\\#5FQQ	G	j<=QR67@A34kJKCD&'C>>#&&SEFKOOFC$@#ABC  	 "!G\<<P&|n56	 " 
/	'NE )U Hs   L%LL
5L
__main__r   )   r   )      )zAuto-managed by Tony)F)'__doc__osr/   r   r   r   pathlibr   google.auth.transport.requestsr   google.oauth2.credentialsr   google_auth_oauthlib.flowr   googleapiclient.discoveryr   googleapiclient.errorsr	   home	WORKSPACEr   r   r.   r"   r+   r1   r7   rA   rL   r{   r   r   r   r   r   r   r   r   __name__r5   r6   r*   <module>r     s   
 
   (  2 1 6 + , IIK+%3	y(+KK "%;;
11 4	456-

/
(V&R(.4(q=hAH zF r6   