@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.
- package/app/main.py +97 -110
- 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
|
-
|
|
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([
|
|
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
|
-
|
|
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="⚠️")
|
|
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(
|
|
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
|
-
|
|
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()
|
|
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
|
-
|
|
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("
|
|
7174
|
+
st.success("Dados da conta Google guardados.")
|
|
7219
7175
|
st.rerun()
|
|
7220
7176
|
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
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