
    u
i                       d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
 ddlmZmZmZmZ ddlmZ ddlmZmZmZmZmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlm Z  ddl!m"Z" ddl#Z$dZ%dZ&dZ'dZ(g dZ)e
 G d d             Z*d&dZ+d'dZ,d(dZ-d(dZ.d)dZ/ G d d      Z0 G d d      Z1 G d d      Z2	 	 	 	 	 	 d*d Z3	 	 	 	 	 	 	 	 d+d!Z4d,d"Z5d-d#Z6d.d$Z7e8d%k(  r e9 e7             y)/a]  Recolector recurrente de metricas YouTube.

Publico (API key):
- Seguidores (subscriberCount)
- Vistas totales del canal (viewCount)
- Contenidos recientes subidos
- Viewers en vivo por stream activo (concurrentViewers)

Privado (OAuth, opcional):
- Vistas en un periodo (Analytics API)
- Monetizacion general en un periodo
- Monetizacion por video
    )annotationsN)	dataclass)datedatetime	timedeltatimezone)Path)AnyDictListOptionalSequence)load_dotenv)Request)Credentials)InstalledAppFlow)build)	HttpErroryoutubev3youtubeAnalyticsv2)z5https://www.googleapis.com/auth/yt-analytics.readonlyz>https://www.googleapis.com/auth/yt-analytics-monetary.readonlyz0https://www.googleapis.com/auth/youtube.readonlyc                      e Zd ZU ded<   ded<   ded<   ded<   ded	<   ded
<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   ded<   y)CollectorConfigstrapi_key	List[str]channel_idsr	   
output_dirintuploads_limitlive_scan_limitinterval_secondszOptional[Path]oauth_client_secretsoauth_token_fileprivate_lookback_daysprivate_recent_videos_limitOptional[str]
mysql_host
mysql_usermysql_passwordmysql_databaseN)__name__
__module____qualname____annotations__     </var/www/html/site13/youtube/v2/youtube_metrics_collector.pyr   r   1   sU    L(($$!$$!!!!r2   r   c                     t        j                  t        j                        j	                  d      j                         S )Nr   )microsecond)r   nowr   utcreplace	isoformatr1   r2   r3   utc_now_isor:   C   s,    <<%--!-<FFHHr2   c                    | sy | j                  dd      }|j                  d      r|d d S |j                  d      r|d d S |S )NT Zz+00:00i)r8   endswith)value
normalizeds     r3   iso_to_mysql_datetimerC   G   sR    sC(J3#28$#2r2   c                    | j                   j                  dd       | j                  dd      5 }|j                  t	        j
                  |d      dz          d d d        y # 1 sw Y   y xY w)	NTparentsexist_okautf-8encodingF)ensure_ascii
)parentmkdiropenwritejsondumpspathpayloadfhs      r3   append_jsonlrX   S   s`    KKdT2	3	) AR
G%84?@A A As   *A$$A-c                    | j                   j                  dd       | j                  dd      5 }t        j                  ||dd       d d d        y # 1 sw Y   y xY w)	NTrE   wrI   rJ   F   )rL   indent)rN   rO   rP   rR   dumprT   s      r3   
write_jsonr^   Y   sR    KKdT2	3	) =R		'2E!<= = =s   AAc                    t                t        j                  dd      j                         } t        j                  dd      j                         }t	        t        j                  dd            j                         }t        t        j                  dd            }t        t        j                  dd	            }t        t        j                  d
d            }t        t        j                  dd            }t        t        j                  dd            }t        j                  dd      j                         xs d }t        j                  dd      j                         xs d }	t        j                  dd      j                         xs d }
t        j                  dd      j                         xs d }t        j                  dd      j                         xs d }t        j                  dd      j                         xs d }|j                  d      D cg c]#  }|j                         s|j                         % }}| st        d      |st        d      t        | ||t        d|      t        d|      t        d|      |rt	        |      j                         nd |rt	        |      j                         nd t        d|      t        d|      ||	|
|      S c c}w )NYOUTUBE_API_KEY YOUTUBE_CHANNEL_IDSYT_OUTPUT_DIRz./dataYT_UPLOADS_LIMIT8YT_LIVE_SCAN_LIMIT12YT_INTERVAL_SECONDS300YT_PRIVATE_LOOKBACK_DAYS7YT_PRIVATE_RECENT_VIDEOS_LIMIT5
MYSQL_HOST
MYSQL_USERMYSQL_PASSWORDMYSQL_DATABASEYT_OAUTH_CLIENT_SECRETSYT_OAUTH_TOKEN,z$Falta YOUTUBE_API_KEY en el entorno.z(Falta YOUTUBE_CHANNEL_IDS en el entorno.      r   )r   r   r   r!   r"   r#   r$   r%   r&   r'   r)   r*   r+   r,   )r   osgetenvstripr	   resolver    split
ValueErrorr   max)r   raw_channel_idsr   r!   r"   r#   r&   r'   r)   r*   r+   r,   r$   r%   cidr   s                   r3   parse_env_configr   _   sZ   Mii)2.446Gii 5r:@@BObii:;CCEJ		"4c:;M"))$8$?@O299%:EBC		*Dc JK"%bii0PRU&V"W<,224<J<,224<JYY/4::<DNYY/4::<DN99%>CIIKStyy!126<<>F$*9*?*?*DT3		399;TKT?@@CDD!]+A/R!12EYT"67??A_c=M./779SW!!%:;$'+F$G%%  Us   K#4K#c                  :    e Zd ZddZd	dZd Zd
dZddZddZy)MySQLSnapshotStorec                    || _         y N)configselfr   s     r3   __init__zMySQLSnapshotStore.__init__   s	    r2   c                    t        | j                  j                  | j                  j                  | j                  j                  | j                  j
                  g      S r   )allr   r)   r*   r+   r,   r   s    r3   
is_enabledzMySQLSnapshotStore.is_enabled   sH    &&&&****	
 	
r2   c                   | j                         st        d      t        j                  j	                  | j
                  j                  | j
                  j                  | j
                  j                  | j
                  j                        S )Nz-Persistencia MySQL no configurada en entorno.)hostuserpassworddatabase)
r   RuntimeErrormysql	connectorconnectr   r)   r*   r+   r,   r   s    r3   r   zMySQLSnapshotStore.connect   sh     NOO&&''''[[//[[//	 ' 
 	
r2   c                    | j                         }|j                         }g d}|D ]  }|j                  |        |j                          |j	                          |j	                          y )N)	a  
            CREATE TABLE IF NOT EXISTS youtube_channel_metrics (
                id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
                collected_at_utc DATETIME NOT NULL,
                channel_id VARCHAR(64) NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                subscriber_count BIGINT UNSIGNED NOT NULL,
                total_view_count BIGINT UNSIGNED NOT NULL,
                video_count BIGINT UNSIGNED NOT NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                KEY idx_channel_metrics_channel_time (channel_id, collected_at_utc)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a  
            CREATE TABLE IF NOT EXISTS youtube_live_metrics (
                id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
                collected_at_utc DATETIME NOT NULL,
                channel_id VARCHAR(64) NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                video_id VARCHAR(32) NOT NULL,
                title VARCHAR(255) NOT NULL,
                concurrent_viewers BIGINT UNSIGNED NOT NULL,
                video_view_count BIGINT UNSIGNED NOT NULL,
                started_at DATETIME NULL,
                video_url VARCHAR(255) NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                KEY idx_live_metrics_video_time (video_id, collected_at_utc),
                KEY idx_live_metrics_channel_time (channel_id, collected_at_utc)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a6  
            CREATE TABLE IF NOT EXISTS youtube_uploads (
                video_id VARCHAR(32) NOT NULL,
                channel_id VARCHAR(64) NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                title VARCHAR(255) NOT NULL,
                published_at DATETIME NULL,
                video_url VARCHAR(255) NULL,
                first_seen_utc DATETIME NOT NULL,
                last_seen_utc DATETIME NOT NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (video_id),
                KEY idx_uploads_channel_published (channel_id, published_at)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            ay  
            CREATE TABLE IF NOT EXISTS youtube_channel_metrics_current (
                channel_id VARCHAR(64) NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                collected_at_utc DATETIME NOT NULL,
                subscriber_count BIGINT UNSIGNED NOT NULL,
                total_view_count BIGINT UNSIGNED NOT NULL,
                video_count BIGINT UNSIGNED NOT NULL,
                updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (channel_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a.  
            CREATE TABLE IF NOT EXISTS youtube_live_metrics_current (
                video_id VARCHAR(32) NOT NULL,
                channel_id VARCHAR(64) NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                collected_at_utc DATETIME NOT NULL,
                title VARCHAR(255) NOT NULL,
                concurrent_viewers BIGINT UNSIGNED NOT NULL,
                video_view_count BIGINT UNSIGNED NOT NULL,
                started_at DATETIME NULL,
                video_url VARCHAR(255) NULL,
                updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (video_id),
                KEY idx_live_current_channel (channel_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a  
            CREATE TABLE IF NOT EXISTS youtube_private_channel_analytics (
                id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
                collected_at_utc DATETIME NOT NULL,
                channel_id VARCHAR(64) NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                start_date DATE NOT NULL,
                end_date DATE NOT NULL,
                views BIGINT UNSIGNED NOT NULL,
                estimated_revenue DECIMAL(18,6) NOT NULL,
                monetized_playbacks DECIMAL(18,2) NOT NULL,
                cpm DECIMAL(18,6) NOT NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                KEY idx_private_channel_time (channel_id, collected_at_utc),
                KEY idx_private_channel_period (channel_id, start_date, end_date)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a  
            CREATE TABLE IF NOT EXISTS youtube_private_channel_analytics_current (
                channel_id VARCHAR(64) NOT NULL,
                start_date DATE NOT NULL,
                end_date DATE NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                collected_at_utc DATETIME NOT NULL,
                views BIGINT UNSIGNED NOT NULL,
                estimated_revenue DECIMAL(18,6) NOT NULL,
                monetized_playbacks DECIMAL(18,2) NOT NULL,
                cpm DECIMAL(18,6) NOT NULL,
                updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (channel_id, start_date, end_date)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a  
            CREATE TABLE IF NOT EXISTS youtube_private_video_analytics (
                id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
                collected_at_utc DATETIME NOT NULL,
                channel_id VARCHAR(64) NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                video_id VARCHAR(32) NOT NULL,
                start_date DATE NOT NULL,
                end_date DATE NOT NULL,
                views BIGINT UNSIGNED NOT NULL,
                estimated_revenue DECIMAL(18,6) NOT NULL,
                cpm DECIMAL(18,6) NOT NULL,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                KEY idx_private_video_time (video_id, collected_at_utc),
                KEY idx_private_video_period (video_id, start_date, end_date),
                KEY idx_private_video_channel (channel_id, start_date, end_date)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a  
            CREATE TABLE IF NOT EXISTS youtube_private_video_analytics_current (
                channel_id VARCHAR(64) NOT NULL,
                video_id VARCHAR(32) NOT NULL,
                start_date DATE NOT NULL,
                end_date DATE NOT NULL,
                channel_title VARCHAR(255) NOT NULL,
                collected_at_utc DATETIME NOT NULL,
                views BIGINT UNSIGNED NOT NULL,
                estimated_revenue DECIMAL(18,6) NOT NULL,
                cpm DECIMAL(18,6) NOT NULL,
                updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (channel_id, video_id, start_date, end_date)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            )r   cursorexecutecommitclose)r   connr   
statements	statements        r3   ensure_schemaz MySQLSnapshotStore.ensure_schema   s[    ||~P

d $ 	&INN9%	& 	

r2   c                   | j                         }|j                         }t        |d         }d}d}d}d}d}	|j                  dg       D ]d  }
||
d   |
d	   |
d
   |
d   |
d   f}|j	                  ||       |j	                  ||
d   |
d	   ||
d
   |
d   |
d   f       |
j                  dg       D ]  }t        |j                  d            }||
d   |
d	   |d   |d   |d   |d   ||j                  d      f	}|j	                  ||       |j	                  ||d   |
d   |
d	   ||d   |d   |d   ||j                  d      f	        |
j                  dg       D ]Q  }t        |j                  d            }|j	                  |	|d   |
d   |
d	   |d   ||j                  d      ||f       S g |j                          |j                          |j                          y )Ncollected_at_utcz
            INSERT INTO youtube_channel_metrics (
                collected_at_utc, channel_id, channel_title,
                subscriber_count, total_view_count, video_count
            ) VALUES (%s, %s, %s, %s, %s, %s)
        a2  
            INSERT INTO youtube_channel_metrics_current (
                channel_id, channel_title, collected_at_utc,
                subscriber_count, total_view_count, video_count
            ) VALUES (%s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                channel_title = VALUES(channel_title),
                collected_at_utc = VALUES(collected_at_utc),
                subscriber_count = VALUES(subscriber_count),
                total_view_count = VALUES(total_view_count),
                video_count = VALUES(video_count)
        a  
            INSERT INTO youtube_live_metrics (
                collected_at_utc, channel_id, channel_title, video_id,
                title, concurrent_viewers, video_view_count, started_at, video_url
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
        a  
            INSERT INTO youtube_live_metrics_current (
                video_id, channel_id, channel_title, collected_at_utc,
                title, concurrent_viewers, video_view_count, started_at, video_url
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                channel_id = VALUES(channel_id),
                channel_title = VALUES(channel_title),
                collected_at_utc = VALUES(collected_at_utc),
                title = VALUES(title),
                concurrent_viewers = VALUES(concurrent_viewers),
                video_view_count = VALUES(video_view_count),
                started_at = VALUES(started_at),
                video_url = VALUES(video_url)
        a9  
            INSERT INTO youtube_uploads (
                video_id, channel_id, channel_title, title,
                published_at, video_url, first_seen_utc, last_seen_utc
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                channel_id = VALUES(channel_id),
                channel_title = VALUES(channel_title),
                title = VALUES(title),
                published_at = VALUES(published_at),
                video_url = VALUES(video_url),
                last_seen_utc = VALUES(last_seen_utc)
        channels
channel_idchannel_titlesubscriber_counttotal_view_countvideo_countlive_now
started_atvideo_idtitleconcurrent_viewersvideo_view_count	video_urllatest_uploadspublished_atr   r   rC   getr   r   r   )r   snapshotr   r   collected_atchannel_metrics_sqlchannel_current_sqllive_metrics_sqllive_current_sqluploads_sqlchannelchannel_values	live_itemr   live_valuesuploadr   s                    r3   save_public_snapshotz'MySQLSnapshotStore.save_public_snapshot@  sm   ||~,X6H-IJ  ||J3 A	G%(*+*+&N NN.?NN#L)O, ././M*
 %[[R8 	29==3NO
 L)O,j)g&2301MM+.
 /=$!*--0$!'*!"67!"45"!k2
: "++&6; 4VZZ5OPz*-0w$

;/$$	gA	F 	

r2   c                N   | j                         }|j                         }t        |d         }|j                  di       }|j                  d      xs d}|j                  d      xs d}|j                  di       }|j                  di       }	d	}
d
}d}d}||||d   |d   |d   |	d   |	d   |	d   f	}|j	                  |
|       |j	                  |||d   |d   |||d   |	d   |	d   |	d   f	       |j                  di       j                  dg       D ]^  }||||d   |d   |d   |d   |d   |d   f	}|j	                  ||       |j	                  |||d   |d   |d   |||d   |d   |d   f	       ` |j                          |j                          |j                          y )Nr   r   r   MINEr   Canal autenticadoperiod_viewsgeneral_monetizationa  
            INSERT INTO youtube_private_channel_analytics (
                collected_at_utc, channel_id, channel_title, start_date, end_date,
                views, estimated_revenue, monetized_playbacks, cpm
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
        a  
            INSERT INTO youtube_private_channel_analytics_current (
                channel_id, start_date, end_date, channel_title, collected_at_utc,
                views, estimated_revenue, monetized_playbacks, cpm
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                channel_title = VALUES(channel_title),
                collected_at_utc = VALUES(collected_at_utc),
                views = VALUES(views),
                estimated_revenue = VALUES(estimated_revenue),
                monetized_playbacks = VALUES(monetized_playbacks),
                cpm = VALUES(cpm)
        a  
            INSERT INTO youtube_private_video_analytics (
                collected_at_utc, channel_id, channel_title, video_id,
                start_date, end_date, views, estimated_revenue, cpm
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
        a0  
            INSERT INTO youtube_private_video_analytics_current (
                channel_id, video_id, start_date, end_date, channel_title,
                collected_at_utc, views, estimated_revenue, cpm
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                channel_title = VALUES(channel_title),
                collected_at_utc = VALUES(collected_at_utc),
                views = VALUES(views),
                estimated_revenue = VALUES(estimated_revenue),
                cpm = VALUES(cpm)
        
start_dateend_dateviewsestimated_revenuemonetized_playbackscpmvideo_monetizationvideosr   r   )r   rV   r   r   r   channel_infor   r   r   monetizationchannel_history_sqlr   video_history_sqlvideo_current_sqlr   videovideo_valuess                    r3   save_private_snapshotz(MySQLSnapshotStore.save_private_snapshot  s+   ||~,W5G-HI{{9b1!%%l3=v
$((9P=P{{>26{{#92> &$!,-./

 	*N;\*Z(W%0123U#
	
 [[!5r:>>xL 	Ej!\*Z(g)*e
L NN,l;NN!*% . ,! 'N-.%L
	8 	

r2   Nr   r   )returnboolr   None)r   Dict[str, Any]r   r   )rV   r   r   r   )	r-   r.   r/   r   r   r   r   r   r   r1   r2   r3   r   r      s%    
	
ZxBir2   r   c                  H    e Zd ZddZ	 	 	 	 	 	 d	dZd
dZddZddZddZy)YouTubeMetricsCollectorc                \    || _         t        t        t        |j                        | _        y )N)developerKey)r   r   DATA_API_SERVICEDATA_API_VERSIONr   r   r   s     r3   r   z YouTubeMetricsCollector.__init__.  s    -/?fnn]r2   c                    | j                   j                         j                  d||      j                         }|j	                  dg       S )Nzsnippet,contentDetails)part
playlistId
maxResultsitems)r   playlistItemslistr   r   )r   uploads_playlist_idmax_resultsresponses       r3   get_recent_playlist_itemsz1YouTubeMetricsCollector.get_recent_playlist_items2  sN     <<--/44)*" 5 
 ')	 	 ||GR((r2   c           
     8   | j                   j                         j                  d|d      j                         }|j	                  dg       }|st        d|       |d   }|j	                  di       }|j	                  di       }|j	                  d	i       }|j	                  d
i       j	                  d      }||j	                  d      t        |j	                  dd            t        |j	                  dd            t        |j	                  dd            |dS )Nz!snippet,statistics,contentDetailsru   )r   idr   r   zNo se encontro el canal: r   snippet
statisticscontentDetailsrelatedPlaylistsuploadsr   subscriberCount	viewCount
videoCount)r   r   r   r   r   r   )r   r   r   r   r   r|   r    )	r   r   r   r   r   r   statsdetailsuploads_playlists	            r3   get_channel_public_metricsz2YouTubeMetricsCollector.get_channel_public_metrics=  s   <<((*//4 0 
 ')	 	 Wb)8EFF(++i,L"-++.3";;'92>BB9M %$[[1 #EII.?$C D #EIIk1$= >uyyq9:#3
 	
r2   c                    g }|d | D ]r  }|j                  di       }|j                  di       }|j                  d      }|j                  ||j                  d      |j                  d      |rd| nd d       t |S )Nr   r   videoIdr   videoPublishedAt https://www.youtube.com/watch?v=)r   r   r   r   )r   append)r   playlist_itemsr   r   itemr   contentr   s           r3   get_latest_uploadsz*YouTubeMetricsCollector.get_latest_uploadsW  s    (*"<K0 	Dhhy"-Ghh/4G{{9-HNN ($[[1$+KK0B$CRZ#CH:!N`d			 r2   c                   |D cg c]#  }|j                  di       j                  d      % }}|D cg c]  }|s|	 }}|sg S | j                  j                         j                  ddj	                  |            j                         }g }|j                  dg       D ]  }|j                  di       }|j                  di       }	|j                  d	i       }
|	j                  d
      dk7  rNd|v rS|j                  |j                  d      |	j                  d      t        |j                  dd            t        |
j                  dd            |j                  d      d|j                  d       d        |S c c}w c c}w )Nr   r   z'snippet,liveStreamingDetails,statisticsrt   )r   r   r   liveStreamingDetailsr   r   liveBroadcastContentliveactualEndTimer   r   concurrentViewersr   r   actualStartTimer   )r   r   r   r   r   r   )r   r   r   r   joinr   r   r    )r   r   r   idsv	video_idsvideos_responseoutr  r   r   s              r3   get_live_realtime_viewsz/YouTubeMetricsCollector.get_live_realtime_viewsh  sz   IWXtxx("-11)<XX #)1qQ)	)I,,--/44:xx	" 5 
 ') 	
 %'#''4 	D882B7Dhhy"-GHH\2.E{{12f<$&JJ $$[[1*-dhh7JA.N*O(+EIIk1,E(F"&((+<"=#CDHHTNCS!T		* 
A Y)s   (FFFc           
     ,   t               g d}| j                  j                  D ]r  }	 | j                  |      }|j	                  d      }|rD| j                  |t        | j                  j                  | j                  j                              ng }| j                  || j                  j                        }| j                  |d | j                  j                         }i |||d}|d   j                  |       t        | j                  j                  dz  |d   ||d   |d   |d	   d
       |D ]-  }	t        | j                  j                  dz  |d   |d|	       / |D ]-  }
t        | j                  j                  dz  |d   |d|
       / u t%        | j                  j                  dz  |       |S # t        $ r"}t        j                   d||       Y d }~d }~wt"        $ r"}t        j                   d||       Y d }~d }~ww xY w)N)r   r   r   )r   r   r   zpublic_channel_metrics.jsonlr   r   r   r   )r   r   r   r   r   zrealtime_live_views.jsonl)r   r   zlatest_uploads.jsonlzError API publico para %s: %szError inesperado canal %s: %szlatest_public_snapshot.json)r:   r   r   r   r   r   r}   r!   r"   r  r  r   rX   r   r   loggingerror	Exceptionr^   )r   r   r   public_metricsr   r   r   r   channel_payload
live_videor   errs               r3   collect_public_snapshotz/YouTubeMetricsCollector.collect_public_snapshot  sG    +

 ++11 9	PJ8P!%!@!@!L&4&8&89N&O# +	 22+DKK55t{{7R7RS
   ++NDKK<U<UV   77GdIdId8ef#$# (&4#
 $++O<KK**-KK,45G,H&0,:;M,N,:;M,N'5m'D	 #+ J ..1LL089K0L*4 ) - F ..1GG089K0L*4 %[9	Pv 	4;;)),II8T  P=z3OO P=z3OOPs$   E.F??	HG%%H1HHNr   )r   r   r   r    r   List[Dict[str, Any]])r   r   r   r   )r   r  r   r    r   r  )r   r  r   r  )r   r   )	r-   r.   r/   r   r   r   r  r  r  r1   r2   r3   r   r   -  s;    ^	)#&	)58	)		)
4"!FBr2   r   c                  Z    e Zd Zd
dZddZddZd ZddZddZddZ		 	 	 	 	 	 	 	 ddZ
y	)YouTubePrivateAnalyticsc                     || _         || _        y r   )r$   r%   )r   r$   r%   s      r3   r   z YouTubePrivateAnalytics.__init__  s    $8! 0r2   c                F   | j                   j                         st        d| j                          t        j                  | j                   j                  d            }d|v rt        d      t        j                  t        | j                         t              }|j                  d      }| j                  j                  j                  dd	       | j                  j                  |j!                         d       t#        j$                  d
| j                         y )Nz)No existe archivo de credenciales OAuth: rI   rJ   webzcEl client_secret configurado es de tipo web. Usa /yt/oauth2start.php para generar oauth_token.json.r   )portTrE   zToken OAuth guardado en %s)r$   existsFileNotFoundErrorrR   loads	read_textr   r   from_client_secrets_filer   ANALYTICS_SCOPESrun_local_serverr%   rN   rO   
write_textto_jsonr  info)r   secrets_payloadflowcredss       r3   
init_oauthz"YouTubePrivateAnalytics.init_oauth  s    ((//1#;D<U<U;VW  **T%>%>%H%HRY%H%Z[O#u   88))*,<
 %%1%-$$**4$*G((7(K143H3HIr2   c                   | j                   j                         st        d| j                    d      t        j                  t        | j                               }|j                  rP|j                  rD|j                  t                      | j                   j                  |j                         d       |j                  st        d      |S )NzNo existe token OAuth en z. Ejecuta --oauth-initrI   rJ   zCredenciales OAuth invalidas.)r%   r   r!  r   from_authorized_user_filer   expiredrefresh_tokenrefreshr   r'  r(  validr   r   r,  s     r3   _load_credentialsz)YouTubePrivateAnalytics._load_credentials  s    $$++-#+D,A,A+BBXY  55c$:O:O6PQ==U00MM')$!!,,U]]_w,O{{>??r2   c                N    | j                         }t        t        t        |      S )Ncredentials)r5  r   ANALYTICS_API_SERVICEANALYTICS_API_VERSIONr4  s     r3   build_servicez%YouTubePrivateAnalytics.build_service  s!    &&(*,AuUUr2   c                `   | j                         }t        t        t        |      }|j	                         j                  ddd      j                         }|j                  dg       }|st        d      |d   }|j                  d	d
      |j                  di       j                  dd      dS )Nr7  z
id,snippetTru   )r   miner   r   z7No fue posible resolver el canal autenticado con OAuth.r   r   r   r   r   r   r   r   )	r5  r   r   r   r   r   r   r   r   )r   r,  r   r   r   r   s         r3   get_authenticated_channelz1YouTubePrivateAnalytics.get_authenticated_channel   s    &&((*:N##%**4TU*V^^`Wb)XYY(!++dF3$[[B7;;GEXY
 	
r2   c                    | j                         }|j                         j                  d||d      j                         }|j	                  dg       }|rt        |d   d         nd}|||dS )Nchannel==MINEr   r
  	startDateendDatemetricsrowsr   )r   r   r   )r;  reportsqueryr   r   r    )r   r   r   serviceresultrF  total_viewss          r3   get_views_in_periodz+YouTubePrivateAnalytics.get_views_in_period  s~    $$&"(( 	 ) 

 ') 	 zz&"%)-c$q'!*o1$  
 	
r2   c                r   | j                         }	 |j                         j                  d||d      j                         }|j	                  dg       }|r|d   ng d\  }}}||t        |      t        |      t        |      dS # t
        $ r&}	t        j                  d|	       d\  }}}Y d }	~	Md }	~	ww xY w)	NrA  z'estimatedRevenue,monetizedPlaybacks,cpmrB  rF  r   r   r   r   zGMonetizacion no disponible para el token actual (%s). Se guardan ceros.)r   r   r   r   r   )	r;  rG  rH  r   r   r   r  warningfloat)
r   r   r   rI  rJ  rF  revenuer   r   r  s
             r3   get_general_monetizationz0YouTubePrivateAnalytics.get_general_monetization  s    $$&	:__&,,#$ A	 - 
 gi  ::fb)D<@ai-G(# % !&w#()<#=:
 	
  	: OOY 1:-G(#	:s   AB 	B6B11B6c           	        | j                         }g }|D ]  }	 |j                         j                  d||dd|       }|j                         }|j	                  dg       }	|	r|	d   ng d\  }
}}|j                  |t        |
      t        |      t        |      d	        |||d
S # t
        $ r'}t        j                  d||       d\  }
}}Y d }~dd }~ww xY w)NrA  zestimatedRevenue,views,cpmzvideo==)r
  rC  rD  rE  filtersrF  r   rN  zDMonetizacion por video no disponible para %s (%s). Se guardan ceros.)r   r   r   r   )r   r   r   )r;  rG  rH  r   r   r   r  rO  r   rP  r    )r   r   r   r  rI  video_resultsr   rH  rJ  rF  rQ  r   r   r  s                 r3   get_video_monetizationz.YouTubePrivateAnalytics.get_video_monetization:  s    $$&.0! 	H0)//'($8%hZ0 0  zz&"-26tAwI#    ().w Z :	)	< % #
 	
#  0Z
 '0#0s   AB**	C3CCN)r$   r	   r%   r	   r   )r   r   )r   zDict[str, str])r   r   r   r   r   r   )r   r   r   r   r  r   r   r   )r-   r.   r/   r   r-  r5  r;  r?  rL  rR  rV  r1   r2   r3   r  r    sL    1J*$V

 
8'
'
),'
9B'
	'
r2   r  c                d   | j                   r| j                   }n<t        j                         t        |j                  dz
        z
  j                         }| j                  xs" t        j                         j                         }t        |d       t        |d       ||kD  rt        d      ||fS )Nru   )daysz
start-datezend-datez+start-date no puede ser mayor que end-date.)	r   r   todayr   r&   r9   r   validate_date_strr|   )argsr   r   r   s       r3   resolve_private_date_ranger\  d  s     __
jjlYF4P4PST4T%UU``b
}}8

 6 6 8Hj,/h
+HFGGxr2   c                   | j                   j                  d      D cg c]#  }|j                         s|j                         % }}|r|S |j                  dk  rg S g }t	               }|j                  dg       D ]q  }|j                  dg       D ]Z  }|j                  d      }	|	r|	|v r|j                  |	       |j                  |	       t        |      |j                  k\  sV|c c S  s |S c c}w )Nrt   r   r   r   r   )	r  r{   ry   r'   setr   r   addlen)
r[  r   r   r  explicit_ids
recent_idsseen_idsr   r   r   s
             r3   resolve_private_video_idsrd  v  s     (,~~';';C'@N!AGGIAGGINLN))Q.	JuH<<
B/ "kk"2B7 	"Fzz*-Hx83h'LL":&"D"DD!!	"" ' Os
   C1C1c                    t        j                  d      } | j                  dt        d d       | j                  dt        dd       | j                  d	d
d       | j                  dt        d       | j                  dt        d       | j                  dt        dd       | j                  dd
d       | j                  dt        d d       | j                  dt        d d       | j                         S )Nz$Recolector YouTube publico y privado)descriptionz--interval-secondszHIntervalo entre ejecuciones en modo loop (default: YT_INTERVAL_SECONDS).)typedefaulthelpz--runsru   z-Cantidad de iteraciones. Usa 0 para infinito.z--oauth-init
store_truez-Inicializa el token OAuth para Analytics API.)actionri  z--start-datez5Inicio de periodo YYYY-MM-DD para analytics privados.)rg  ri  z
--end-datez2Fin de periodo YYYY-MM-DD para analytics privados.z--video-idsra   z<IDs de video separados por coma para monetizacion por video.z--include-privatez>Intenta extraer datos privados (views periodo y monetizacion).z--private-lookback-dayszSSi no indicas fechas, usa esta ventana de dias hacia atras para analytics privados.z--private-recent-videos-limitz`Si no indicas video IDs, consulta monetizacion para los uploads mas recientes hasta este limite.)argparseArgumentParseradd_argumentr    r   
parse_args)parsers    r3   ro  ro    s?   $$1WXF
W	   <	   <  
 D  
 A  
 K	   M  
 !b	   'o	   r2   c                v    	 t        j                  |        y # t        $ r}t        d| d|        |d }~ww xY w)NzFecha invalida para z: )r   fromisoformatr|   )rA   
field_namer  s      r3   rZ  rZ    sC    P5! P/
|2eWEFCOPs    	838c                 
   t        j                  t         j                  dt        j                  t        j
                        g       t               } 	 t               }| j                  rt        d| j                        |_
        | j                  t        d| j                        |_        | j                  t        d| j                        |_        t        |      }t        |      }d }|j                   r,|j"                  r t%        |j                   |j"                        }| j&                  r)|st        j                  d       y|j)                          y| j*                  dk(  }| j*                  }|s|dkD  r-t-               }t        j.                  d	|       |j1                         }	t        j.                  d
t3        |	d                |j5                         r8	 |j7                          |j9                  |	       t        j.                  d       nt        j.                  d       | j<                  r|st        j>                  d       n 	 d }
	 |jA                         }
|
r|
jC                  d      ry|
jC                  d      |jD                  vr\t        j                  d|
jC                  dd      |
jC                  dd      djG                  |jD                               tI        d      tK        | |      \  }}|jM                  ||      }|jO                  ||      }|	jC                  d      r|	d   d   ni }|
xs i jC                  d      xs" |jC                  d      xs |jD                  d   |
xs i jC                  d      xs |jC                  d      xs dd}t-               |||d}tQ        | ||	      }|r|jS                  |||      |d<   tU        |jV                  dz  |       tY        |jV                  dz  |       |j5                         r!|j7                          |j[                  |       t        j.                  d       |s|dz  }|dk  r	 yt        j.                  d |j                         t]        j^                  |j                         |r&|dkD  r-y# t        $ r }t        j                  d|       Y d }~yd }~ww xY w# t:        $ r!}t        j                  d|       Y d }~d }~ww xY w# t:        $ r!}t        j>                  d|       Y d }~d }~ww xY w# t:        $ r!}t        j                  d|       Y d }~d }~ww xY w)!Nz'%(asctime)s [%(levelname)s] %(message)s)levelformathandlerszConfiguracion invalida: %sr[   rv   ru   r   zEFaltan YT_OAUTH_CLIENT_SECRETS y/o YT_OAUTH_TOKEN para iniciar OAuth.zInicio de ciclo %sz(Snapshot publico generado con %d canalesr   z$Snapshot publico persistido en MySQLz.No fue posible persistir snapshot en MySQL: %sz:Persistencia MySQL no configurada; se omite guardado en DBzCSe solicito include-private, pero faltan variables OAuth; se omite.zoNo fue posible validar canal OAuth con channels.mine (%s). Se usa canal del snapshot publico para persistencia.r   zxEl token OAuth esta autenticado para el canal %s (%s), pero YOUTUBE_CHANNEL_IDS=%s. Se omite guardado de datos privados.r   Canaldesconocidort   zSCanal OAuth no coincide con YOUTUBE_CHANNEL_IDS. Reautoriza con la cuenta correcta.r   r>  )r   r   r   r   r   zprivate_analytics.jsonlzlatest_private_analytics.jsonzDatos privados actualizadosz)No fue posible extraer datos privados: %szEsperando %s segundos)0r  basicConfigINFOStreamHandlersysstdoutro  r   r|   r  r#   r}   r&   r'   r   r   r$   r%   r  
oauth_initr-  runsr:   r)  r  r`  r   r   r   r  include_privaterO  r?  r   r   r	  r   r\  rL  rR  rd  rV  rX   r   r^   r   timesleep)r[  r   r  	collectormysql_storeprivate_analyticsrun_foreverremaining_runsstartedr   authenticated_channelauth_errr   r   r   r   first_channelr   private_payloadr  s                       r3   runr    s   ll8''

34 <D!#
 "%b$*?*?"@!!-'*1d.H.H'I$''3-0D4T4T-U*'/I$V,K;?""v'>'>3'')@)@
  MMW $$&))q.KYYN
!+-)73446?XjEYAZ[!!#U))+00:CD LLUV$YETFJ)0A0[0[0]- .155lC155lC6K]K]]`155owO155lMRHHV%7%78 +q  ,FdF+S(J#4#H#HU]#^L+<+U+U"H,( @H||J?WHZ$8$;]_M 38b==lK 5,00>5%11!4 38b==oN 3,00A32$L -8M#/(40D	7O !:$ QI -DD *Hi ((<= !!2!25N!NP_`v003RRTcd"--/#113#99/JLL!>? aN"
  	,f.E.EF

6**+I !+L M  2C8X  UNPSTTU % S$ @ ! TMM"MsSSTss   
R) 6S T/ T $G,T/ )	S2SS	S?S::S?	T,T'!T/ 'T,,T/ /	U8UU__main__)r   r   )rA   r(   r   r(   )rU   r	   rV   r   r   r   )r   r   )r[  argparse.Namespacer   r   r   ztuple[str, str])r[  r  r   r   r   r   r   r   )r   r  )rA   r   rs  r   r   r   )r   r    ):__doc__
__future__r   rl  rR   r  rw   r}  r  dataclassesr   r   r   r   r   pathlibr	   typingr
   r   r   r   r   dotenvr   google.auth.transport.requestsr   google.oauth2.credentialsr   google_auth_oauthlib.flowr   googleapiclient.discoveryr   googleapiclient.errorsr   mysql.connectorr   r   r   r9  r:  r%  r   r:   rC   rX   r^   r   r   r   r  r\  rd  ro  rZ  r  r-   
SystemExitr1   r2   r3   <module>r     s5   #    	 
  ! 8 8  6 6  2 1 6 + ,   *    " " ""I	A=)X_ _D` `FQ
 Q
h 
 &5  $
&5AO24nPQh z
SU
 r2   