@danhachuel/thunderbolt 0.7.8 → 0.7.9

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 +84 -110
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -7086,33 +7086,27 @@ def render_google_accounts(*, include_innertube: bool = True):
7086
7086
  missing_document_parts = list(direct_status.get("missing_cookies", []))
7087
7087
  if not direct_status.get("has_session_info"):
7088
7088
  missing_document_parts.append("sessionInfo")
7089
- account_ready = bool(
7089
+ google_ready = bool(
7090
7090
  account_email_snapshot != "sem e-mail"
7091
7091
  and str(batch_account.get("client_id") or "").strip()
7092
7092
  and str(batch_account.get("client_secret") or "").strip()
7093
- and bool(direct_status.get("document_exists"))
7094
- and not missing_document_parts
7095
7093
  )
7094
+ youtube_upload_ready = bool(direct_status.get("document_exists") and not missing_document_parts)
7095
+ 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")
7096
+ youtube_status_label = "Configured" if youtube_upload_ready else ("Missing key" if not direct_status.get("has_session_info") else "Missing configuration")
7096
7097
  if missing_document_parts:
7097
7098
  youtube_accounts_missing_document.append(account_email_snapshot)
7098
7099
 
7099
7100
  with st.container(border=True):
7100
- account_header_cols = st.columns([3.2, 1.2])
7101
+ account_header_cols = st.columns([2.35, 1.15, 1.35])
7101
7102
  with account_header_cols[0]:
7102
7103
  st.subheader(f"{account_label_snapshot} — {account_email_snapshot}")
7103
7104
  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="⚠️")
7105
+ st.caption("Conta Google")
7106
+ _api_status_badge(google_status_label, "ready" if google_ready else "missing")
7107
+ with account_header_cols[2]:
7108
+ st.caption("YouTube-Video-Upload-Frontend-Api")
7109
+ _api_status_badge(youtube_status_label, "ready" if youtube_upload_ready else "missing")
7116
7110
  with st.expander("Detalhes da conta Google", expanded=False):
7117
7111
  with st.form(f"batch_account_form_{account_id}"):
7118
7112
  account_cols = st.columns(2)
@@ -7122,48 +7116,11 @@ def render_google_accounts(*, include_innertube: bool = True):
7122
7116
  account_client_id = st.text_input("OAuth Client ID", value=str(batch_account.get("client_id", "")), key=f"batch_client_id_{account_id}")
7123
7117
  with account_cols[1]:
7124
7118
  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
7119
  save_account = st.form_submit_button("Guardar dados da conta Google", type="primary", width="stretch")
7149
7120
 
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
7121
 
7165
7122
  account_status = youtube_batch_account_status(batch_account, STORAGE)
7166
- status_cols = st.columns(4)
7123
+ status_cols = st.columns(2)
7167
7124
  with status_cols[0]:
7168
7125
  (st.success if account_status.ok else st.warning)(account_status.message)
7169
7126
  with status_cols[1]:
@@ -7172,26 +7129,19 @@ def render_google_accounts(*, include_innertube: bool = True):
7172
7129
  (st.success if result.ok else st.error)(result.message)
7173
7130
  if result.ok:
7174
7131
  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()
7132
+ if st.button("Apagar conta", icon=":material/delete:", key=f"batch_remove_settings_{account_id}", width="stretch"):
7133
+ delete_youtube_batch_token(batch_account, STORAGE)
7134
+ delete_credentials_document(STORAGE, batch_account)
7135
+ remaining_accounts = [account for account in batch_accounts if str(account.get("id")) != account_id]
7136
+ settings["youtube_batch_accounts"] = remaining_accounts
7137
+ if settings.get("youtube_batch_selected_account_id") == account_id:
7138
+ settings["youtube_batch_selected_account_id"] = str(remaining_accounts[0].get("id")) if remaining_accounts else ""
7139
+ for channel in channel_state:
7140
+ if str(channel.get("google_account_id") or "") == account_id:
7141
+ channel.update({"google_account_id": "", "google_account_email": ""})
7142
+ write_json("channels.json", channel_state)
7143
+ write_json("settings.json", settings)
7144
+ st.rerun()
7195
7145
 
7196
7146
  if save_account:
7197
7147
  if "@" not in account_email.strip():
@@ -7204,61 +7154,85 @@ def render_google_accounts(*, include_innertube: bool = True):
7204
7154
  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
7155
  if credentials_changed:
7206
7156
  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
- )
7157
+ 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
7158
  ensure_credentials_document(STORAGE, existing, settings, channel_state)
7216
7159
  settings["youtube_batch_accounts"] = batch_accounts
7217
7160
  write_json("settings.json", settings)
7218
- st.success("Conta Google/YouTube guardada.")
7161
+ st.success("Dados da conta Google guardados.")
7219
7162
  st.rerun()
7220
7163
 
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
-
7164
+ with st.expander("Detalhes YouTube-Video-Upload-Frontend-Api", expanded=False):
7165
+ health_message = f"SessionInfo: {session_health.message}"
7166
+ if session_health.status == "healthy":
7167
+ st.success(health_message)
7168
+ elif session_health.status in {"expiring", "unknown"}:
7169
+ st.warning(health_message, icon="⚠️")
7170
+ elif session_health.status in {"expired", "blocked_by_google", "invalid_format"}:
7171
+ st.error(health_message, icon="⚠️")
7172
+ account_session_info = st.text_input(
7173
+ "sessionInfo token desta conta Google",
7174
+ value=str(batch_account.get("sessionInfo") or batch_account.get("session_info") or batch_account.get("direct_session_info", "")),
7175
+ type="password",
7176
+ key=f"batch_session_info_{account_id}",
7177
+ help="Token sessionInfo usado pelo Upload directo. É guardado por conta e sincronizado no credentials.json.",
7178
+ )
7179
+ captured_at_value = str(session_health.captured_at or batch_account.get("sessionInfoCapturedAt") or batch_account.get("session_info_captured_at") or "").strip()
7180
+ try:
7181
+ account_session_info_captured_at = datetime.fromisoformat(captured_at_value.replace("Z", "+00:00")).date() if captured_at_value else None
7182
+ except ValueError:
7183
+ account_session_info_captured_at = None
7184
+ 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.")
7185
+ st.markdown("**Área de Upload dos cookies**")
7186
+ 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.")
7187
+ 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.")
7188
+ if missing_document_parts:
7189
+ st.warning(f"Documento incompleto: {', '.join(missing_document_parts)}")
7190
+ else:
7191
+ st.success("Documento completo para a conta Google")
7192
+ st.caption(f"Documento guardado em: {direct_status['document_file']}")
7193
+ document_save = st.button("Guardar documento nesta conta", key=f"save_direct_account_{account_id}", width="stretch")
7194
+ 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."):
7195
+ result = authorize_youtube_agent(settings, STORAGE, account=batch_account)
7196
+ (st.success if result.ok else st.error)(result.message)
7197
+ if result.ok:
7198
+ st.rerun()
7239
7199
  st.markdown("**Renovação de sessionInfo e cookies**")
7240
7200
  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
7201
  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
- )
7202
+ 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
7203
  if renewal_cols[1].button("Renovar agora", key=f"renew_session_info_{account_id}", type="primary", width="stretch"):
7248
7204
  result = renew_account_session(STORAGE, batch_account, settings, force=True, wait_seconds=10)
7249
7205
  (st.success if result.ok else st.error)(result.message)
7250
7206
  if result.ok:
7251
7207
  st.rerun()
7252
7208
  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
- )
7209
+ _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
7210
  if auto_renew != bool(batch_account.get("auto_renew_before_upload", False)):
7258
7211
  batch_account["auto_renew_before_upload"] = bool(auto_renew)
7259
7212
  settings["youtube_batch_accounts"] = batch_accounts
7260
7213
  write_json("settings.json", settings)
7261
7214
  st.success("Preferência de renovação guardada para esta conta.")
7215
+ if document_save:
7216
+ if document_upload is None:
7217
+ st.error("Seleccione um documento JSON de cookies/credenciais antes de guardar.")
7218
+ else:
7219
+ try:
7220
+ merge_credentials_document(STORAGE, batch_account, document_upload.getvalue(), document_upload.name, session_info_override=account_session_info.strip(), channels=channel_state)
7221
+ 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 "")
7222
+ st.success("Documento incorporado e guardado nesta conta Google.")
7223
+ st.rerun()
7224
+ except ValueError as exc:
7225
+ st.error(str(exc))
7226
+ if st.button("Guardar dados YouTube-Video-Upload-Frontend-Api", key=f"save_upload_api_data_{account_id}", width="stretch"):
7227
+ captured_at_iso = account_session_info_captured_at.isoformat() if isinstance(account_session_info_captured_at, date) else ""
7228
+ update_credentials_document_session_info(STORAGE, batch_account, account_session_info.strip(), captured_at=captured_at_iso)
7229
+ for existing in batch_accounts:
7230
+ if str(existing.get("id")) == account_id:
7231
+ existing.update({"sessionInfo": account_session_info.strip(), "sessionInfoCapturedAt": captured_at_iso})
7232
+ settings["youtube_batch_accounts"] = batch_accounts
7233
+ write_json("settings.json", settings)
7234
+ st.success("Dados YouTube-Video-Upload-Frontend-Api guardados.")
7235
+ st.rerun()
7262
7236
 
7263
7237
  if youtube_accounts_missing_document:
7264
7238
  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.09",
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",