@danhachuel/thunderbolt 0.5.98 → 0.5.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -142,6 +142,7 @@ def upload_with_default_route(
142
142
  privacy_status=privacy_status,
143
143
  thumbnail_path=thumbnail_path,
144
144
  captions_path=captions_path,
145
+ account=account,
145
146
  )
146
147
  except Exception as exc: # Keep fallback actionable and deterministic.
147
148
  official_result = IntegrationResult(False, f"API Oficial falhou: {exc}", {})
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  from typing import Any, Callable
9
9
 
10
10
  from integrations.platforms import IntegrationResult
11
- from integrations.youtube_batch import loopback_host, loopback_port, loopback_redirect_uri
11
+ from integrations.youtube_batch import loopback_host, loopback_port, loopback_redirect_uri, token_path as batch_token_path
12
12
 
13
13
 
14
14
  # Mantemos os mesmos escopos usados pelo youtube-automation-agent.
@@ -89,10 +89,11 @@ def build_agent_video_metadata(
89
89
 
90
90
 
91
91
  class _GoogleYouTubeBase:
92
- def __init__(self, settings: dict[str, Any], token_path: Path, scopes: list[str], alternate_token_path: Path | None = None):
92
+ def __init__(self, settings: dict[str, Any], token_path: Path, scopes: list[str], alternate_token_path: Path | None = None, account: dict[str, Any] | None = None):
93
93
  self.settings = settings
94
- self.client_id = _safe_text(settings.get("youtube_client_id")) or os.getenv("YOUTUBE_CLIENT_ID", "")
95
- self.client_secret = _safe_text(settings.get("youtube_client_secret")) or os.getenv("YOUTUBE_CLIENT_SECRET", "")
94
+ self.account = account if isinstance(account, dict) else None
95
+ self.client_id = _safe_text((self.account or {}).get("client_id")) or _safe_text(settings.get("youtube_client_id")) or os.getenv("YOUTUBE_CLIENT_ID", "")
96
+ self.client_secret = _safe_text((self.account or {}).get("client_secret")) or _safe_text(settings.get("youtube_client_secret")) or os.getenv("YOUTUBE_CLIENT_SECRET", "")
96
97
  self.token_path = token_path
97
98
  self.alternate_token_path = alternate_token_path
98
99
  self.scopes = scopes
@@ -107,9 +108,11 @@ class _GoogleYouTubeBase:
107
108
 
108
109
  def _token_candidates(self) -> list[Path]:
109
110
  paths = [self.token_path]
111
+ if self.account:
112
+ paths.append(batch_token_path(self.token_path.parents[1], self.account))
110
113
  if self.alternate_token_path and self.alternate_token_path not in paths:
111
114
  paths.append(self.alternate_token_path)
112
- return paths
115
+ return list(dict.fromkeys(paths))
113
116
 
114
117
  def _client_config(self) -> dict[str, Any]:
115
118
  return {
@@ -135,6 +138,10 @@ class _GoogleYouTubeBase:
135
138
  if isinstance(raw, dict) and isinstance(raw.get("youtube"), dict):
136
139
  raw = raw["youtube"]
137
140
  credentials = Credentials.from_authorized_user_info(raw, self.scopes)
141
+ granted_scopes = {str(scope).strip() for scope in (credentials.scopes or [])}
142
+ if "https://www.googleapis.com/auth/youtube.upload" in self.scopes and granted_scopes and "https://www.googleapis.com/auth/youtube.upload" not in granted_scopes:
143
+ email = _safe_text((self.account or {}).get("email")) or "a conta seleccionada"
144
+ raise RuntimeError(f"{email} está autorizada apenas para leitura/listagem. Autorize novamente esta conta com o escopo de upload YouTube.")
138
145
  if credentials.expired and credentials.refresh_token:
139
146
  credentials.refresh(Request())
140
147
  self._save_credentials(credentials)
@@ -292,8 +299,8 @@ class _GoogleYouTubeBase:
292
299
  class YouTubeAutomationAgentUploader(_GoogleYouTubeBase):
293
300
  """Adaptation of the source agent's PublishingSchedulingAgent inside Thunderbolt."""
294
301
 
295
- def __init__(self, settings: dict[str, Any], storage_root: Path):
296
- super().__init__(settings, storage_root / "state" / "youtube_agent_tokens.json", AGENT_SCOPES)
302
+ def __init__(self, settings: dict[str, Any], storage_root: Path, account: dict[str, Any] | None = None):
303
+ super().__init__(settings, storage_root / "state" / "youtube_agent_tokens.json", AGENT_SCOPES, account=account)
297
304
 
298
305
  def upload(self, **kwargs: Any) -> IntegrationResult:
299
306
  result = self._upload_common(**kwargs)
@@ -304,12 +311,13 @@ class YouTubeAutomationAgentUploader(_GoogleYouTubeBase):
304
311
  class DirectYouTubeOAuthUploader(_GoogleYouTubeBase):
305
312
  """Independent minimal OAuth upload path used only after the primary path fails."""
306
313
 
307
- def __init__(self, settings: dict[str, Any], storage_root: Path):
314
+ def __init__(self, settings: dict[str, Any], storage_root: Path, account: dict[str, Any] | None = None):
308
315
  super().__init__(
309
316
  settings,
310
317
  storage_root / "state" / "youtube_oauth_fallback_token.json",
311
318
  FALLBACK_SCOPES,
312
319
  alternate_token_path=storage_root / "state" / "youtube_agent_tokens.json",
320
+ account=account,
313
321
  )
314
322
 
315
323
  def upload(self, **kwargs: Any) -> IntegrationResult:
@@ -332,6 +340,7 @@ def upload_youtube_with_fallback(
332
340
  publish_at: str | None = None,
333
341
  thumbnail_path: str | None = None,
334
342
  captions_path: str | None = None,
343
+ account: dict[str, Any] | None = None,
335
344
  on_attempt: Callable[[UploadAttempt], None] | None = None,
336
345
  ) -> IntegrationResult:
337
346
  """Run the adapted agent path first and OAuth direct only as redundancy."""
@@ -348,7 +357,7 @@ def upload_youtube_with_fallback(
348
357
  "captions_path": captions_path,
349
358
  }
350
359
  attempts: list[dict[str, Any]] = []
351
- primary = YouTubeAutomationAgentUploader(settings, storage_root)
360
+ primary = YouTubeAutomationAgentUploader(settings, storage_root, account=account)
352
361
  primary_result = primary.upload(**kwargs)
353
362
  primary_result.data.setdefault("mechanism", "youtube-automation-agent-adaptado")
354
363
  primary_attempt = UploadAttempt("youtube-automation-agent-adaptado", primary_result)
@@ -359,7 +368,7 @@ def upload_youtube_with_fallback(
359
368
  primary_result.data["attempts"] = attempts
360
369
  return primary_result
361
370
 
362
- fallback = DirectYouTubeOAuthUploader(settings, storage_root)
371
+ fallback = DirectYouTubeOAuthUploader(settings, storage_root, account=account)
363
372
  fallback_result = fallback.upload(**kwargs)
364
373
  fallback_result.data.setdefault("mechanism", "oauth-direct-fallback")
365
374
  fallback_attempt = UploadAttempt("oauth-direct-fallback", fallback_result)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.5.98",
3
+ "version": "0.5.99",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",