@danhachuel/thunderbolt 0.3.91 → 0.3.93

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
@@ -47,7 +47,7 @@ from hermes_ui.notifications import clear_notifications, list_notifications, mar
47
47
  from hermes_ui.influencers import BACKEND_OPTIONS, DOCUMENT_EXTENSIONS, IMAGE_EXTENSIONS, backend_name, backend_status, get_repository, test_backend
48
48
  from hermes_ui.logs import list_logs, logs_to_rows
49
49
  from hermes_ui.languages import LANGUAGE_CODES, VIDEO_LANGUAGE_CODES, LANGUAGE_FLAG_DATA_URIS, language_code, language_label, ui_language_menu_label, ui_text, video_language_label, video_language_options
50
- from hermes_ui.api_key_tests import test_apify_credentials, test_influencer_database, test_kaggle_credentials, test_material_source_credentials, test_media_provider_card, test_nano_banana_credentials, test_postiz_credentials, test_telegram_credentials, test_tiktok_credentials, test_upload_post_credentials, test_voice_provider
50
+ from hermes_ui.api_key_tests import test_apify_credentials, test_influencer_database, test_innertube_api_key, test_kaggle_credentials, test_material_source_credentials, test_media_provider_card, test_nano_banana_credentials, test_postiz_credentials, test_telegram_credentials, test_tiktok_credentials, test_upload_post_credentials, test_voice_provider
51
51
  from hermes_ui.tutorials import tutorial_body, tutorial_caption, tutorial_title
52
52
 
53
53
  from hermes_ui.script_documents import list_script_documents, read_script_document, save_script_document, script_storage_path
@@ -1969,10 +1969,17 @@ def render_channels():
1969
1969
  channel_account_ids.append(current_channel_account_id)
1970
1970
  youtube_account_labels[current_channel_account_id] = "Conta Google não configurada"
1971
1971
  with st.expander("Upload directo — documento da conta deste canal", expanded=False):
1972
- st.caption("O DELEGATED_SESSION_ID deste canal é individual, mas fica apenas no documento JSON da conta Google. A UI não mostra nem edita esse valor; associe apenas o canal à conta que contém o documento.")
1972
+ st.caption("O DELEGATED_SESSION_ID é individual deste canal. Guarde-o aqui; o valor fica no registo local do canal e não é copiado para o documento JSON partilhado da conta Google.")
1973
1973
  with st.form(f"channel_direct_credentials_{channel_id}"):
1974
1974
  channel_account_id = st.selectbox("Conta Google do documento deste canal", channel_account_ids, index=channel_account_ids.index(current_channel_account_id) if current_channel_account_id in channel_account_ids else 0, format_func=lambda item: youtube_account_labels.get(item, item or "Sem conta Google associada"), key=f"channel_account_{channel_id}")
1975
- save_channel_direct_credentials = st.form_submit_button("Associar conta Google ao canal", type="primary", use_container_width=True)
1975
+ channel_delegated_session_id = st.text_input(
1976
+ "DELEGATED_SESSION_ID deste canal",
1977
+ value=str(channel.get("delegated_session_id") or ""),
1978
+ type="password",
1979
+ key=f"channel_delegated_session_id_{channel_id}",
1980
+ help="Identificador individual usado pelo Upload directo deste canal. Não é partilhado com outros canais nem mostrado nos diagnósticos.",
1981
+ )
1982
+ save_channel_direct_credentials = st.form_submit_button("Guardar conta Google e DELEGATED_SESSION_ID", type="primary", use_container_width=True)
1976
1983
  selected_channel_account = youtube_accounts_by_id.get(channel_account_id)
1977
1984
  if selected_channel_account:
1978
1985
  selected_account_status = document_status(STORAGE, selected_channel_account, channel, settings, channels)
@@ -1992,8 +1999,15 @@ def render_channels():
1992
1999
  else:
1993
2000
  st.info("Associe este canal a uma conta Google para validar o documento de credenciais.")
1994
2001
  if save_channel_direct_credentials:
1995
- update_channel(channel_id, {"google_account_id": channel_account_id.strip(), "google_account_email": str(youtube_accounts_by_id.get(channel_account_id, {}).get("email", ""))})
1996
- st.success("Conta Google associada ao canal. O DELEGATED_SESSION_ID continua exclusivamente no documento da conta.")
2002
+ update_channel(
2003
+ channel_id,
2004
+ {
2005
+ "google_account_id": channel_account_id.strip(),
2006
+ "google_account_email": str(youtube_accounts_by_id.get(channel_account_id, {}).get("email", "")),
2007
+ "delegated_session_id": channel_delegated_session_id.strip(),
2008
+ },
2009
+ )
2010
+ st.success("Conta Google e DELEGATED_SESSION_ID individual do canal guardados.")
1997
2011
  st.rerun()
1998
2012
 
1999
2013
  render_channel_videos(channel)
@@ -5204,6 +5218,12 @@ def render_google_accounts():
5204
5218
  key="global_innertube_api_key",
5205
5219
  help="Chave global usada pelo Upload directo para todas as contas Google/YouTube. Guarde-a na configuração global, separada dos documentos de cookies.",
5206
5220
  )
5221
+ _render_api_test_control(
5222
+ settings,
5223
+ "innertube",
5224
+ lambda: test_innertube_api_key(innertube_api_key_value),
5225
+ widget_key="api_test_innertube",
5226
+ )
5207
5227
  save_innertube_api_key = st.form_submit_button("Guardar INNERTUBE_API_KEY global", type="primary", use_container_width=True)
5208
5228
  if save_innertube_api_key:
5209
5229
  settings["direct_innertube_api_key"] = innertube_api_key_value.strip()
@@ -95,6 +95,24 @@ def test_apify_credentials(api_token: str) -> dict[str, Any]:
95
95
  return _get(f"{APIFY_API_BASE}/users/me", headers={"Authorization": f"Bearer {api_token}"})
96
96
 
97
97
 
98
+ def test_innertube_api_key(api_key: str) -> dict[str, Any]:
99
+ """Validate a global InnerTube key with a public, read-only guide request."""
100
+ api_key = str(api_key or "").strip()
101
+ if not api_key:
102
+ return _missing("Introduza a INNERTUBE_API_KEY antes de testar.")
103
+ return _post(
104
+ f"https://www.youtube.com/youtubei/v1/guide?key={quote(api_key, safe='')}",
105
+ json={
106
+ "context": {
107
+ "client": {
108
+ "clientName": "WEB",
109
+ "clientVersion": "2.20250305.01.00",
110
+ }
111
+ }
112
+ },
113
+ )
114
+
115
+
98
116
  def test_nano_banana_credentials(api_key: str, model: str) -> dict[str, Any]:
99
117
  """Validate the configured Gemini image model metadata without generating an image."""
100
118
  api_key = str(api_key or "").strip()
@@ -307,6 +325,7 @@ __all__ = [
307
325
  "test_apify_credentials",
308
326
  "test_azure_speech_credentials",
309
327
  "test_elevenlabs_credentials",
328
+ "test_innertube_api_key",
310
329
  "test_kaggle_credentials",
311
330
  "test_influencer_database",
312
331
  "test_material_source_credentials",
@@ -363,6 +363,9 @@ def merge_credentials_document(
363
363
 
364
364
 
365
365
  def delegated_session_id(document: dict[str, Any], channel: dict[str, Any]) -> str:
366
+ channel_value = str(channel.get("delegated_session_id") or "").strip() if isinstance(channel, dict) else ""
367
+ if channel_value:
368
+ return channel_value
366
369
  mapping = document.get("delegated_session_ids", {}) if isinstance(document, dict) else {}
367
370
  if not isinstance(mapping, dict):
368
371
  return ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.3.91",
3
+ "version": "0.3.93",
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",