@danhachuel/thunderbolt 0.6.7 → 0.6.8

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.
package/app/main.py CHANGED
@@ -118,7 +118,7 @@ from integrations.upload_post import UploadPostAdapter, UPLOAD_POST_PLATFORM_OPT
118
118
  from integrations.bilibili_upload import BilibiliApiAdapter, BILIBILI_DEFAULT_TID, BILIBILI_VIDEO_EXTENSIONS, normalise_bilibili_api_cards
119
119
  from integrations.distrokid_upload import DistroKidAdapter, DISTROKID_AUDIO_EXTENSIONS, DISTROKID_COVER_EXTENSIONS, close_distrokid_session
120
120
  from integrations.music_uploads import JewelMusicAdapter, PushtunesAdapter, YTMusicApiAdapter, MUSIC_UPLOAD_EXTENSIONS, PUSHTUNES_OPERATIONS, PUSHTUNES_SOURCES, PUSHTUNES_TARGETS, YT_MUSIC_UPLOAD_EXTENSIONS
121
- from integrations.upload_routing import COMPOSIO_OPERATION_OPTIONS, OFFICIAL_DAILY_LIMIT, official_upload_count, upload_with_default_route
121
+ from integrations.upload_routing import COMPOSIO_OPERATION_OPTIONS, OFFICIAL_DAILY_LIMIT, official_upload_count, resolve_youtube_account, upload_with_default_route
122
122
  from integrations.youtube_direct_upload import YouTubeDirectUploader
123
123
  from integrations.youtube_direct_credentials import delete_credentials_document, direct_account_status, document_status, ensure_credentials_document, load_credentials_document, merge_credentials_document, parse_credentials_document, save_credentials_document, update_credentials_document_session_info
124
124
  from integrations.session_info_health import check_account_session_info_health
@@ -5597,7 +5597,7 @@ def render_upload_direct():
5597
5597
  with st.container(border=True):
5598
5598
  st.write(f"**{task.get('topic', 'Sem tópico')}** — {task.get('channel_name', 'Canal')}")
5599
5599
  st.caption(video_path or "Sem caminho de vídeo registado")
5600
- account = direct_accounts.get(str(channel.get("google_account_id", "")))
5600
+ account = resolve_youtube_account(settings, channel)
5601
5601
  credential_status = document_status(STORAGE, account, channel, settings, channels) if account else None
5602
5602
  if not account:
5603
5603
  st.warning("Este canal não tem uma conta Google associada. Associe a conta no cartão deste canal em Canais cadastrados.")
@@ -6364,7 +6364,7 @@ def render_upload_conventional():
6364
6364
  st.caption(video_path or "Sem caminho de vídeo registado")
6365
6365
  selected_youtube_channel = upload_targets.get("YouTube") if "YouTube" in destination else None
6366
6366
  channel = selected_youtube_channel or channel_map.get(str(task.get("channel_id")), {})
6367
- account = direct_accounts.get(str(channel.get("google_account_id", "")))
6367
+ account = resolve_youtube_account(settings, channel)
6368
6368
  if "YouTube" in destination:
6369
6369
  detected_language = normalize_video_language(task.get("language") or channel.get("language") or "pt")
6370
6370
  language_state_key = f"yt_language_{task['id']}"
@@ -23,7 +23,7 @@ from typing import Any
23
23
 
24
24
  from integrations.moneyprinter_config import sync_moneyprinter_config
25
25
  from integrations.session_info_health import check_all_accounts_session_info_health, emit_session_info_health_alerts
26
- from integrations.upload_routing import upload_with_default_route
26
+ from integrations.upload_routing import resolve_youtube_account, upload_with_default_route
27
27
  from hermes_ui.creative_generation import CreativeGenerationError, generate_creative_package, generate_title_and_keywords, generate_thumbnail_prompt, generate_topic_for_channel
28
28
  from hermes_ui.script_documents import save_script_document
29
29
  from hermes_ui.script_generation import generate_script_document
@@ -1566,7 +1566,7 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
1566
1566
  settings,
1567
1567
  storage_root=STORAGE,
1568
1568
  channel=channel,
1569
- account=next((item for item in settings.get("youtube_batch_accounts", []) if isinstance(item, dict) and str(item.get("id")) == str(channel.get("google_account_id"))), None),
1569
+ account=resolve_youtube_account(settings, channel),
1570
1570
  video_path=str(video_path),
1571
1571
  title=title,
1572
1572
  description=str(script.get("summary") or "") + "\n\n" + str(script.get("content") or "")[:5000],
@@ -45,11 +45,41 @@ def _attempt_record(name: str, result: IntegrationResult, *, skipped: bool = Fal
45
45
  }
46
46
 
47
47
 
48
+ def resolve_youtube_account(settings: dict[str, Any], channel: dict[str, Any] | None) -> dict[str, Any] | None:
49
+ """Resolve the configured Google account without relying only on the channel ID.
50
+
51
+ Older channel records may retain the account e-mail while the account ID is
52
+ missing or stale. Prefer an exact ID match, then use a case-insensitive
53
+ e-mail match, and finally accept a sole configured account only when the
54
+ channel has no contradictory account reference.
55
+ """
56
+ if not isinstance(channel, dict):
57
+ return None
58
+ accounts = [item for item in settings.get("youtube_batch_accounts", []) if isinstance(item, dict) and item.get("id")]
59
+ if not accounts:
60
+ return None
61
+ account_id = str(channel.get("google_account_id") or "").strip()
62
+ if account_id:
63
+ match = next((item for item in accounts if str(item.get("id") or "").strip() == account_id), None)
64
+ if match is not None:
65
+ return match
66
+ account_email = str(channel.get("google_account_email") or "").strip().casefold()
67
+ if account_email:
68
+ match = next((item for item in accounts if str(item.get("email") or "").strip().casefold() == account_email), None)
69
+ if match is not None:
70
+ return match
71
+ if not account_id and not account_email and len(accounts) == 1:
72
+ return accounts[0]
73
+ return None
74
+
75
+
48
76
  def _quota_key(channel: dict[str, Any], account: dict[str, Any] | None) -> str:
49
77
  if account and account.get("id"):
50
78
  return f"account:{account['id']}"
51
79
  if channel.get("google_account_id"):
52
80
  return f"account:{channel['google_account_id']}"
81
+ if channel.get("google_account_email"):
82
+ return f"account-email:{str(channel['google_account_email']).strip().casefold()}"
53
83
  return f"channel:{channel.get('id', 'unknown')}"
54
84
 
55
85
 
@@ -107,6 +137,7 @@ def upload_with_default_route(
107
137
  composio_publisher: Callable[..., IntegrationResult] | None = None,
108
138
  ) -> IntegrationResult:
109
139
  attempts: list[dict[str, Any]] = []
140
+ account = account or resolve_youtube_account(settings, channel)
110
141
  composio_enabled = bool(settings.get("composio_enabled", False)) and bool(settings.get("composio_auto_upload", True))
111
142
  composio_ready = composio_enabled and bool(settings.get("composio_api_key")) and bool(settings.get("composio_tool_slug"))
112
143
  if composio_enabled and not composio_ready:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.07",
3
+ "version": "0.6.08",
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",