feat(youtube): la visibilidad se comprueba desde fuera, no se le cree a la API
Build & Deploy ResearchOwl / build-and-push (push) Successful in 10s
Build & Deploy ResearchOwl / build-and-push (push) Successful in 10s
`/upload_short` contaba lo que la respuesta de la subida decía del vídeo. Eso es la palabra de la API sobre sí misma, y todo el flujo de revisión descansa en ella: el informe de fundamento se lee ANTES de publicar sólo si subir no publica. El 2026-08-12, mirando un vídeo recién subido, la respuesta decía `privacyStatus: private` y el vídeo se veía sin sesión — resultó ser un clic humano en Studio y no un fallo, pero el episodio dejó claro que no había forma de distinguir un caso del otro. Ahora se contrasta: oEmbed contesta 200 a un vídeo que se ve sin sesión y 404 a uno que no. Sin credenciales, sin tocar el scope — `youtube.upload` no puede preguntar por el estado de un vídeo, y ampliarlo a uno que sí pueda significa darle a un token de subida permiso para vaciar el canal. Dos decisiones que van con esto: - **Sólo el 200 es una prueba.** Un 404 no demuestra que el vídeo sea privado: también lo devuelve uno que YouTube aún no ha indexado. Por eso el negativo se mira dos veces y, si sigue negativo, se cuenta como "no se ve desde fuera", no como "es privado". - **No haber podido comprobar no es haber comprobado que no.** Un fallo de red deja `reachable=None` y el parte lo dice, en vez de heredar la garantía que no tiene. Cuando la API dice privado y el vídeo se ve, el aviso va en la PRIMERA línea del mensaje de Telegram: enterarse tiene que costar cero atención. Verificado contra la realidad — el mismo vídeo daba True antes de ocultarlo y False después; un vídeo borrado y uno privado dan False, y uno público True. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+15
-1
@@ -854,7 +854,13 @@ def _upload_message(video, metadata: dict, article_url: Optional[str]) -> str:
|
||||
lines = [f"🎬 {video.title}", "", f"Revisar y publicar: {video.studio_url}",
|
||||
f"Enlace del vídeo: {video.watch_url}", ""]
|
||||
|
||||
if video.privacy_status == "private":
|
||||
if video.visibility_contradiction:
|
||||
# Primera línea del mensaje, no una nota al pie: si esto pasa, el vídeo
|
||||
# ya está en la calle mientras lees el informe de fundamento.
|
||||
lines.insert(0, "🚨 EL VÍDEO SE VE SIN INICIAR SESIÓN, aunque YouTube "
|
||||
"dijo que lo subía en privado. Ocúltalo en Studio antes "
|
||||
"de nada — el enlace de abajo lleva ahí.\n")
|
||||
elif video.privacy_status == "private":
|
||||
lines.append(
|
||||
"🔒 Está PRIVADO. Los vídeos subidos por API desde un proyecto sin "
|
||||
"auditar se quedan así: el candado es del proyecto, no del vídeo, y "
|
||||
@@ -863,6 +869,14 @@ def _upload_message(video, metadata: dict, article_url: Optional[str]) -> str:
|
||||
else:
|
||||
lines.append(f"👁 Visibilidad: {video.privacy_status}")
|
||||
|
||||
# Lo comprobado, aparte de lo que dijo la API: son dos cosas distintas y el
|
||||
# 2026-08-12 se demostró que conviene no confundirlas.
|
||||
if video.reachable is False:
|
||||
lines.append("✔ Comprobado desde fuera: no se ve sin sesión.")
|
||||
elif video.reachable is None:
|
||||
lines.append("⚠️ No se pudo comprobar la visibilidad desde fuera; me "
|
||||
"queda sólo lo que dijo la API. Míralo en Studio.")
|
||||
|
||||
if video.forced_private:
|
||||
lines.append("⚠️ Pediste otra visibilidad y YouTube la forzó a privada. "
|
||||
"Es exactamente la firma de ese candado.")
|
||||
|
||||
Reference in New Issue
Block a user