@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.
- package/app/main.py +84 -110
- 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
|
-
|
|
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([
|
|
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
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
st.caption(
|
|
7108
|
-
|
|
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(
|
|
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
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
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
|
-
|
|
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("
|
|
7161
|
+
st.success("Dados da conta Google guardados.")
|
|
7219
7162
|
st.rerun()
|
|
7220
7163
|
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
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