@danhachuel/thunderbolt 0.7.8 → 0.7.10

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.
Files changed (2) hide show
  1. package/app/main.py +97 -110
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -5910,6 +5910,19 @@ def render_automation():
5910
5910
  with header_cols[1]:
5911
5911
  st.write(f"**{channel.get('name', 'Sem nome')}**")
5912
5912
  st.caption(channel.get("handle") or channel.get("url") or "sem URL")
5913
+ channel_url = str(channel.get("url") or "").strip()
5914
+ if not channel_url:
5915
+ channel_id_or_handle = str(channel.get("youtube_channel_id") or channel.get("youtube_id") or channel.get("handle") or "").strip()
5916
+ if channel_id_or_handle.startswith("http"):
5917
+ channel_url = channel_id_or_handle
5918
+ elif channel_id_or_handle.startswith("UC"):
5919
+ channel_url = f"https://www.youtube.com/channel/{channel_id_or_handle}"
5920
+ elif channel_id_or_handle.startswith(("@", "channel/")):
5921
+ channel_url = f"https://www.youtube.com/{channel_id_or_handle}"
5922
+ elif channel_id_or_handle:
5923
+ channel_url = f"https://www.youtube.com/@{channel_id_or_handle}"
5924
+ if channel_url:
5925
+ st.link_button("Abrir canal", channel_url, type="primary", width="content")
5913
5926
  with header_cols[2]:
5914
5927
  st.markdown("**Thumbnail Blueprint**")
5915
5928
  st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
@@ -7086,33 +7099,27 @@ def render_google_accounts(*, include_innertube: bool = True):
7086
7099
  missing_document_parts = list(direct_status.get("missing_cookies", []))
7087
7100
  if not direct_status.get("has_session_info"):
7088
7101
  missing_document_parts.append("sessionInfo")
7089
- account_ready = bool(
7102
+ google_ready = bool(
7090
7103
  account_email_snapshot != "sem e-mail"
7091
7104
  and str(batch_account.get("client_id") or "").strip()
7092
7105
  and str(batch_account.get("client_secret") or "").strip()
7093
- and bool(direct_status.get("document_exists"))
7094
- and not missing_document_parts
7095
7106
  )
7107
+ youtube_upload_ready = bool(direct_status.get("document_exists") and not missing_document_parts)
7108
+ google_status_label = "Configured" if google_ready else ("Missing key" if not str(batch_account.get("client_id") or "").strip() or not str(batch_account.get("client_secret") or "").strip() else "Missing configuration")
7109
+ youtube_status_label = "Configured" if youtube_upload_ready else ("Missing key" if not direct_status.get("has_session_info") else "Missing configuration")
7096
7110
  if missing_document_parts:
7097
7111
  youtube_accounts_missing_document.append(account_email_snapshot)
7098
7112
 
7099
7113
  with st.container(border=True):
7100
- account_header_cols = st.columns([3.2, 1.2])
7114
+ account_header_cols = st.columns([2.35, 1.15, 1.35])
7101
7115
  with account_header_cols[0]:
7102
7116
  st.subheader(f"{account_label_snapshot} — {account_email_snapshot}")
7103
7117
  with account_header_cols[1]:
7104
- _api_status_badge("Configured" if account_ready else "Missing configuration", "ready" if account_ready else "missing")
7105
- health_message = f"SessionInfo: {session_health.message}"
7106
- if session_health.status == "healthy":
7107
- st.caption(health_message)
7108
- elif session_health.status == "expiring":
7109
- st.warning(health_message, icon="⚠️")
7110
- elif session_health.status == "expired":
7111
- st.error(health_message, icon="⚠️")
7112
- elif session_health.status == "unknown":
7113
- st.warning(health_message, icon="⚠️")
7114
- elif session_health.status in {"blocked_by_google", "invalid_format"}:
7115
- st.error(health_message, icon="⚠️")
7118
+ st.caption("Conta Google")
7119
+ _api_status_badge(google_status_label, "ready" if google_ready else "missing")
7120
+ with account_header_cols[2]:
7121
+ st.caption("YouTube-Video-Upload-Frontend-Api")
7122
+ _api_status_badge(youtube_status_label, "ready" if youtube_upload_ready else "missing")
7116
7123
  with st.expander("Detalhes da conta Google", expanded=False):
7117
7124
  with st.form(f"batch_account_form_{account_id}"):
7118
7125
  account_cols = st.columns(2)
@@ -7122,48 +7129,11 @@ def render_google_accounts(*, include_innertube: bool = True):
7122
7129
  account_client_id = st.text_input("OAuth Client ID", value=str(batch_account.get("client_id", "")), key=f"batch_client_id_{account_id}")
7123
7130
  with account_cols[1]:
7124
7131
  account_client_secret = st.text_input("OAuth Client Secret", value=str(batch_account.get("client_secret", "")), type="password", key=f"batch_client_secret_{account_id}")
7125
- account_session_info = st.text_input(
7126
- "sessionInfo token desta conta Google",
7127
- value=str(batch_account.get("sessionInfo") or batch_account.get("session_info") or batch_account.get("direct_session_info", "")),
7128
- type="password",
7129
- key=f"batch_session_info_{account_id}",
7130
- help="Token sessionInfo usado pelo Upload directo. É guardado por conta e sincronizado no credentials.json; os cookies e restantes valores continuam exclusivamente no documento.",
7131
- )
7132
- captured_at_value = str(
7133
- session_health.captured_at
7134
- or batch_account.get("sessionInfoCapturedAt")
7135
- or batch_account.get("session_info_captured_at")
7136
- or ""
7137
- ).strip()
7138
- try:
7139
- account_session_info_captured_at = datetime.fromisoformat(captured_at_value.replace("Z", "+00:00")).date() if captured_at_value else None
7140
- except ValueError:
7141
- account_session_info_captured_at = None
7142
- account_session_info_captured_at = st.date_input(
7143
- "Data de Captura",
7144
- value=account_session_info_captured_at,
7145
- key=f"batch_session_info_captured_at_{account_id}",
7146
- help="Data em que o sessionInfo token foi capturado. É usada para calcular o alerta de expiração e não revela o token.",
7147
- )
7148
7132
  save_account = st.form_submit_button("Guardar dados da conta Google", type="primary", width="stretch")
7149
7133
 
7150
- st.markdown("**Documento de cookies/credenciais desta conta Google**")
7151
- st.caption("O documento padrão é criado automaticamente. Suba um JSON completo ou apenas o documento de cookies; os valores preenchidos são incorporados e mantidos em credentials.json.")
7152
- document_upload = st.file_uploader(
7153
- "Subir documento de cookies/credenciais",
7154
- type=["json"],
7155
- key=f"direct_credentials_document_{account_id}",
7156
- help="Aceita o JSON do YouTube-Video-Upload-Frontend-Api. Um documento parcial de cookies também é incorporado sem apagar os restantes campos.",
7157
- )
7158
- if missing_document_parts:
7159
- st.warning(f"Documento incompleto: {', '.join(missing_document_parts)}")
7160
- else:
7161
- st.success("Documento completo para a conta Google")
7162
- st.caption(f"Documento guardado em: {direct_status['document_file']}")
7163
- document_save = st.button("Guardar documento nesta conta", key=f"save_direct_account_{account_id}", width="stretch")
7164
7134
 
7165
7135
  account_status = youtube_batch_account_status(batch_account, STORAGE)
7166
- status_cols = st.columns(4)
7136
+ status_cols = st.columns(2)
7167
7137
  with status_cols[0]:
7168
7138
  (st.success if account_status.ok else st.warning)(account_status.message)
7169
7139
  with status_cols[1]:
@@ -7172,26 +7142,19 @@ def render_google_accounts(*, include_innertube: bool = True):
7172
7142
  (st.success if result.ok else st.error)(result.message)
7173
7143
  if result.ok:
7174
7144
  st.rerun()
7175
- with status_cols[2]:
7176
- if st.button("Autorizar upload YouTube", key=f"batch_authorize_upload_{account_id}", width="stretch", help="Autoriza esta conta com o escopo de publicação YouTube; é separado da autorização de leitura/listagem dos canais." ):
7177
- result = authorize_youtube_agent(settings, STORAGE, account=batch_account)
7178
- (st.success if result.ok else st.error)(result.message)
7179
- if result.ok:
7180
- st.rerun()
7181
- with status_cols[3]:
7182
- if st.button("Apagar conta", icon=":material/delete:", key=f"batch_remove_settings_{account_id}", width="stretch"):
7183
- delete_youtube_batch_token(batch_account, STORAGE)
7184
- delete_credentials_document(STORAGE, batch_account)
7185
- remaining_accounts = [account for account in batch_accounts if str(account.get("id")) != account_id]
7186
- settings["youtube_batch_accounts"] = remaining_accounts
7187
- if settings.get("youtube_batch_selected_account_id") == account_id:
7188
- settings["youtube_batch_selected_account_id"] = str(remaining_accounts[0].get("id")) if remaining_accounts else ""
7189
- for channel in channel_state:
7190
- if str(channel.get("google_account_id") or "") == account_id:
7191
- channel.update({"google_account_id": "", "google_account_email": ""})
7192
- write_json("channels.json", channel_state)
7193
- write_json("settings.json", settings)
7194
- st.rerun()
7145
+ if st.button("Apagar conta", icon=":material/delete:", key=f"batch_remove_settings_{account_id}", width="stretch"):
7146
+ delete_youtube_batch_token(batch_account, STORAGE)
7147
+ delete_credentials_document(STORAGE, batch_account)
7148
+ remaining_accounts = [account for account in batch_accounts if str(account.get("id")) != account_id]
7149
+ settings["youtube_batch_accounts"] = remaining_accounts
7150
+ if settings.get("youtube_batch_selected_account_id") == account_id:
7151
+ settings["youtube_batch_selected_account_id"] = str(remaining_accounts[0].get("id")) if remaining_accounts else ""
7152
+ for channel in channel_state:
7153
+ if str(channel.get("google_account_id") or "") == account_id:
7154
+ channel.update({"google_account_id": "", "google_account_email": ""})
7155
+ write_json("channels.json", channel_state)
7156
+ write_json("settings.json", settings)
7157
+ st.rerun()
7195
7158
 
7196
7159
  if save_account:
7197
7160
  if "@" not in account_email.strip():
@@ -7204,61 +7167,85 @@ def render_google_accounts(*, include_innertube: bool = True):
7204
7167
  credentials_changed = any(existing.get(field, "") != value for field, value in (("email", account_email.strip()), ("client_id", account_client_id.strip()), ("client_secret", account_client_secret.strip())))
7205
7168
  if credentials_changed:
7206
7169
  delete_youtube_batch_token(existing, STORAGE)
7207
- captured_at_iso = account_session_info_captured_at.isoformat() if isinstance(account_session_info_captured_at, date) else ""
7208
- existing.update({"label": account_label.strip() or "Canais YouTube", "email": account_email.strip(), "client_id": account_client_id.strip(), "client_secret": account_client_secret.strip(), "sessionInfo": account_session_info.strip(), "sessionInfoCapturedAt": captured_at_iso})
7209
- update_credentials_document_session_info(
7210
- STORAGE,
7211
- existing,
7212
- account_session_info.strip(),
7213
- captured_at=captured_at_iso,
7214
- )
7170
+ existing.update({"label": account_label.strip() or "Canais YouTube", "email": account_email.strip(), "client_id": account_client_id.strip(), "client_secret": account_client_secret.strip()})
7215
7171
  ensure_credentials_document(STORAGE, existing, settings, channel_state)
7216
7172
  settings["youtube_batch_accounts"] = batch_accounts
7217
7173
  write_json("settings.json", settings)
7218
- st.success("Conta Google/YouTube guardada.")
7174
+ st.success("Dados da conta Google guardados.")
7219
7175
  st.rerun()
7220
7176
 
7221
- if document_save:
7222
- if document_upload is None:
7223
- st.error("Seleccione um documento JSON de cookies/credenciais antes de guardar.")
7224
- else:
7225
- try:
7226
- merge_credentials_document(
7227
- STORAGE,
7228
- batch_account,
7229
- document_upload.getvalue(),
7230
- document_upload.name,
7231
- session_info_override=str(batch_account.get("sessionInfo") or ""),
7232
- channels=channel_state,
7233
- )
7234
- st.success("Documento incorporado e guardado nesta conta Google.")
7235
- st.rerun()
7236
- except ValueError as exc:
7237
- st.error(str(exc))
7238
-
7177
+ with st.expander("Detalhes YouTube-Video-Upload-Frontend-Api", expanded=False):
7178
+ health_message = f"SessionInfo: {session_health.message}"
7179
+ if session_health.status == "healthy":
7180
+ st.success(health_message)
7181
+ elif session_health.status in {"expiring", "unknown"}:
7182
+ st.warning(health_message, icon="⚠️")
7183
+ elif session_health.status in {"expired", "blocked_by_google", "invalid_format"}:
7184
+ st.error(health_message, icon="⚠️")
7185
+ account_session_info = st.text_input(
7186
+ "sessionInfo token desta conta Google",
7187
+ value=str(batch_account.get("sessionInfo") or batch_account.get("session_info") or batch_account.get("direct_session_info", "")),
7188
+ type="password",
7189
+ key=f"batch_session_info_{account_id}",
7190
+ help="Token sessionInfo usado pelo Upload directo. É guardado por conta e sincronizado no credentials.json.",
7191
+ )
7192
+ captured_at_value = str(session_health.captured_at or batch_account.get("sessionInfoCapturedAt") or batch_account.get("session_info_captured_at") or "").strip()
7193
+ try:
7194
+ account_session_info_captured_at = datetime.fromisoformat(captured_at_value.replace("Z", "+00:00")).date() if captured_at_value else None
7195
+ except ValueError:
7196
+ account_session_info_captured_at = None
7197
+ account_session_info_captured_at = st.date_input("Data de Captura", value=account_session_info_captured_at, key=f"batch_session_info_captured_at_{account_id}", help="Data em que o sessionInfo token foi capturado.")
7198
+ st.markdown("**Área de Upload dos cookies**")
7199
+ st.caption("O documento padrão é criado automaticamente. Suba um JSON completo ou apenas o documento de cookies; os valores preenchidos são incorporados e mantidos em credentials.json.")
7200
+ document_upload = st.file_uploader("Subir documento de cookies/credenciais", type=["json"], key=f"direct_credentials_document_{account_id}", help="Aceita o JSON do YouTube-Video-Upload-Frontend-Api. Um documento parcial de cookies também é incorporado sem apagar os restantes campos.")
7201
+ if missing_document_parts:
7202
+ st.warning(f"Documento incompleto: {', '.join(missing_document_parts)}")
7203
+ else:
7204
+ st.success("Documento completo para a conta Google")
7205
+ st.caption(f"Documento guardado em: {direct_status['document_file']}")
7206
+ document_save = st.button("Guardar documento nesta conta", key=f"save_direct_account_{account_id}", width="stretch")
7207
+ if st.button("Autorizar upload YouTube", key=f"batch_authorize_upload_{account_id}", width="stretch", help="Autoriza esta conta com o escopo de publicação YouTube."):
7208
+ result = authorize_youtube_agent(settings, STORAGE, account=batch_account)
7209
+ (st.success if result.ok else st.error)(result.message)
7210
+ if result.ok:
7211
+ st.rerun()
7239
7212
  st.markdown("**Renovação de sessionInfo e cookies**")
7240
7213
  st.caption("A renovação é manual ou opt-in antes do upload; não há timer residente nem execução ao seleccionar a conta.")
7241
7214
  renewal_cols = st.columns([2, 1, 1])
7242
- auto_renew = renewal_cols[0].checkbox(
7243
- "Renovar automaticamente antes do upload",
7244
- value=bool(batch_account.get("auto_renew_before_upload", False)),
7245
- key=f"auto_renew_before_upload_{account_id}",
7246
- )
7215
+ auto_renew = renewal_cols[0].checkbox("Renovar automaticamente antes do upload", value=bool(batch_account.get("auto_renew_before_upload", False)), key=f"auto_renew_before_upload_{account_id}")
7247
7216
  if renewal_cols[1].button("Renovar agora", key=f"renew_session_info_{account_id}", type="primary", width="stretch"):
7248
7217
  result = renew_account_session(STORAGE, batch_account, settings, force=True, wait_seconds=10)
7249
7218
  (st.success if result.ok else st.error)(result.message)
7250
7219
  if result.ok:
7251
7220
  st.rerun()
7252
7221
  with renewal_cols[2]:
7253
- _api_status_badge(
7254
- "Configured" if session_health.status == "healthy" else ("Missing key" if session_health.status == "missing" else "Missing configuration"),
7255
- "ready" if session_health.status == "healthy" else "missing",
7256
- )
7222
+ _api_status_badge("Configured" if session_health.status == "healthy" else ("Missing key" if session_health.status == "missing" else "Missing configuration"), "ready" if session_health.status == "healthy" else "missing")
7257
7223
  if auto_renew != bool(batch_account.get("auto_renew_before_upload", False)):
7258
7224
  batch_account["auto_renew_before_upload"] = bool(auto_renew)
7259
7225
  settings["youtube_batch_accounts"] = batch_accounts
7260
7226
  write_json("settings.json", settings)
7261
7227
  st.success("Preferência de renovação guardada para esta conta.")
7228
+ if document_save:
7229
+ if document_upload is None:
7230
+ st.error("Seleccione um documento JSON de cookies/credenciais antes de guardar.")
7231
+ else:
7232
+ try:
7233
+ merge_credentials_document(STORAGE, batch_account, document_upload.getvalue(), document_upload.name, session_info_override=account_session_info.strip(), channels=channel_state)
7234
+ update_credentials_document_session_info(STORAGE, batch_account, account_session_info.strip(), captured_at=account_session_info_captured_at.isoformat() if isinstance(account_session_info_captured_at, date) else "")
7235
+ st.success("Documento incorporado e guardado nesta conta Google.")
7236
+ st.rerun()
7237
+ except ValueError as exc:
7238
+ st.error(str(exc))
7239
+ if st.button("Guardar dados YouTube-Video-Upload-Frontend-Api", key=f"save_upload_api_data_{account_id}", width="stretch"):
7240
+ captured_at_iso = account_session_info_captured_at.isoformat() if isinstance(account_session_info_captured_at, date) else ""
7241
+ update_credentials_document_session_info(STORAGE, batch_account, account_session_info.strip(), captured_at=captured_at_iso)
7242
+ for existing in batch_accounts:
7243
+ if str(existing.get("id")) == account_id:
7244
+ existing.update({"sessionInfo": account_session_info.strip(), "sessionInfoCapturedAt": captured_at_iso})
7245
+ settings["youtube_batch_accounts"] = batch_accounts
7246
+ write_json("settings.json", settings)
7247
+ st.success("Dados YouTube-Video-Upload-Frontend-Api guardados.")
7248
+ st.rerun()
7262
7249
 
7263
7250
  if youtube_accounts_missing_document:
7264
7251
  st.info("Contas que ainda precisam de dados no documento: " + ", ".join(youtube_accounts_missing_document))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.7.08",
3
+ "version": "0.7.10",
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",