@danhachuel/thunderbolt 0.4.57 → 0.4.58
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/MANUAL-INSTALACAO.md
CHANGED
|
@@ -835,3 +835,5 @@ Configure primeiro **Configurações > Configuração API > API Keys Upload > Co
|
|
|
835
835
|
Quando o provider ainda não estiver conectado, use **Autorizar toolkit no Composio**. O Thunderbolt apresenta o Connect Link devolvido pelo Composio; conclua a autorização nesse link e repita o envio. O Thunderbolt não implementa nem armazena um fluxo OAuth próprio.
|
|
836
836
|
|
|
837
837
|
Antes de clicar em **Enviar vídeo via Composio**, confirme o vídeo, o toolkit, o slug da ferramenta, o campo do ficheiro e os argumentos JSON. O resultado guarda apenas um registo local mínimo com o estado e, quando devolvido, o `log_id` do Composio. URLs presigned, tokens, headers e a API key não são guardados no histórico.
|
|
838
|
+
|
|
839
|
+
Na **Automação Youtube**, Composio é a rota padrão quando a integração está activa e tem API key, slug da ferramenta e campo do ficheiro configurados. A ordem automática passa a ser **Composio → API Oficial → Upload directo → Postiz**. Se Composio falhar ou não estiver configurado, o worker tenta os fallbacks disponíveis e mantém o comportamento de publicação existente.
|
package/app/main.py
CHANGED
|
@@ -7167,9 +7167,13 @@ def render_settings():
|
|
|
7167
7167
|
with st.expander("Composio", expanded=False):
|
|
7168
7168
|
st.caption("A API key é guardada apenas no storage local. O slug da ferramenta e o provider são descobertos no Upload via Composio; a autenticação da conta é feita pelo Connect Link do Composio.")
|
|
7169
7169
|
composio_enabled = st.checkbox("Activar Composio", value=bool(settings.get("composio_enabled", False)), key="upload_composio_enabled")
|
|
7170
|
+
composio_auto_upload = st.checkbox("Usar Composio por defeito na Automação Youtube", value=bool(settings.get("composio_auto_upload", True)), key="upload_composio_auto_upload", help="Quando estiver configurado, Composio é tentado antes da API Oficial, Upload directo e Postiz.")
|
|
7170
7171
|
composio_api_key = st.text_input("Composio API key", value=str(settings.get("composio_api_key") or ""), type="password", key="upload_composio_api_key", help="Use a API key de projecto do Composio Platform. Nunca coloque esta chave no GitHub, em URLs ou em mensagens.")
|
|
7171
7172
|
composio_user_id = st.text_input("Composio user ID", value=str(settings.get("composio_user_id") or "thunderbolt-local"), key="upload_composio_user_id", help="Identidade estável usada para associar as contas conectadas no Composio.")
|
|
7172
7173
|
composio_toolkit = st.text_input("Toolkit preferido (opcional)", value=str(settings.get("composio_toolkit") or ""), key="upload_composio_toolkit", help="Deixe vazio para descobrir ferramentas em todos os toolkits.")
|
|
7174
|
+
composio_tool_slug = st.text_input("Slug da ferramenta para Automação Youtube", value=str(settings.get("composio_tool_slug") or ""), key="upload_composio_tool_slug", help="Depois de descobrir uma ferramenta em Upload via Composio, copie aqui o slug para a Automação Youtube.")
|
|
7175
|
+
composio_file_field = st.text_input("Campo do ficheiro na ferramenta", value=str(settings.get("composio_file_field") or "file"), key="upload_composio_file_field")
|
|
7176
|
+
composio_arguments_json = st.text_area("Argumentos JSON da Automação Youtube", value=str(settings.get("composio_arguments_json") or "{}"), key="upload_composio_arguments_json", height=120)
|
|
7173
7177
|
_render_credential_status(composio_api_key)
|
|
7174
7178
|
composio_action_cols = st.columns(2)
|
|
7175
7179
|
with composio_action_cols[0]:
|
|
@@ -7177,7 +7181,7 @@ def render_settings():
|
|
|
7177
7181
|
with composio_action_cols[1]:
|
|
7178
7182
|
test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
|
|
7179
7183
|
if save_composio:
|
|
7180
|
-
settings.update({"composio_enabled": bool(composio_enabled), "composio_api_key": composio_api_key.strip(), "composio_user_id": composio_user_id.strip() or "thunderbolt-local", "composio_toolkit": composio_toolkit.strip()})
|
|
7184
|
+
settings.update({"composio_enabled": bool(composio_enabled), "composio_auto_upload": bool(composio_auto_upload), "composio_api_key": composio_api_key.strip(), "composio_user_id": composio_user_id.strip() or "thunderbolt-local", "composio_toolkit": composio_toolkit.strip(), "composio_tool_slug": composio_tool_slug.strip(), "composio_file_field": composio_file_field.strip() or "file", "composio_arguments_json": composio_arguments_json.strip() or "{}"})
|
|
7181
7185
|
write_json("settings.json", settings)
|
|
7182
7186
|
st.success("Configuração Composio guardada.")
|
|
7183
7187
|
st.rerun()
|
|
@@ -1474,9 +1474,10 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
|
|
|
1474
1474
|
return _update(task_id, stage="upload", state="done", progress=100, artifacts=artifacts, video_ready=True, error=None)
|
|
1475
1475
|
|
|
1476
1476
|
configured_account_id = str(channel.get("google_account_id") or "").strip()
|
|
1477
|
+
configured_composio = bool(settings.get("composio_enabled", False)) and bool(settings.get("composio_auto_upload", True)) and bool(settings.get("composio_api_key")) and bool(settings.get("composio_tool_slug"))
|
|
1477
1478
|
configured_upload_post = bool(settings.get("upload_post_enabled", False)) and bool(settings.get("upload_post_auto_upload", False))
|
|
1478
1479
|
configured_postiz = bool(settings.get("postiz_enabled", False)) and bool(settings.get("postiz_auto_publish", False))
|
|
1479
|
-
if not (configured_account_id or configured_upload_post or configured_postiz):
|
|
1480
|
+
if not (configured_composio or configured_account_id or configured_upload_post or configured_postiz):
|
|
1480
1481
|
artifacts["upload"] = {
|
|
1481
1482
|
"route": "local",
|
|
1482
1483
|
"status": "skipped",
|
package/hermes_ui/storage.py
CHANGED
|
@@ -287,6 +287,14 @@ DEFAULTS: dict[str, Any] = {
|
|
|
287
287
|
"upload_post_username": "",
|
|
288
288
|
"upload_post_platforms": "tiktok,instagram",
|
|
289
289
|
"upload_post_auto_upload": False,
|
|
290
|
+
"composio_enabled": True,
|
|
291
|
+
"composio_auto_upload": True,
|
|
292
|
+
"composio_api_key": "",
|
|
293
|
+
"composio_user_id": "thunderbolt-local",
|
|
294
|
+
"composio_toolkit": "",
|
|
295
|
+
"composio_tool_slug": "",
|
|
296
|
+
"composio_file_field": "file",
|
|
297
|
+
"composio_arguments_json": "{}",
|
|
290
298
|
"postiz_enabled": False,
|
|
291
299
|
"postiz_api_key": "",
|
|
292
300
|
"postiz_base_url": "https://api.postiz.com/public/v1",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Ordered YouTube upload routing for Thunderbolt.
|
|
2
2
|
|
|
3
3
|
The route is intentionally deterministic:
|
|
4
|
-
1.
|
|
5
|
-
2.
|
|
6
|
-
3.
|
|
4
|
+
1. Composio, when the configured upload tool is available.
|
|
5
|
+
2. Official YouTube API, up to five successful sends per Google account/day.
|
|
6
|
+
3. Internal browser-session upload, when the account document is complete.
|
|
7
|
+
4. Postiz, when configured with an API key and integration ID.
|
|
7
8
|
"""
|
|
8
9
|
|
|
9
10
|
from __future__ import annotations
|
|
@@ -17,6 +18,7 @@ from integrations.postiz import PostizAdapter
|
|
|
17
18
|
from integrations.session_info_health import check_account_session_info_health, emit_session_info_health_alerts
|
|
18
19
|
from integrations.youtube_direct_credentials import document_status
|
|
19
20
|
from integrations.youtube_direct_upload import YouTubeDirectUploader
|
|
21
|
+
from integrations.composio_upload import ComposioUploadError, execute_upload
|
|
20
22
|
from hermes_ui.storage import read_json, write_json
|
|
21
23
|
|
|
22
24
|
OFFICIAL_DAILY_LIMIT = 5
|
|
@@ -91,8 +93,32 @@ def upload_with_default_route(
|
|
|
91
93
|
official_uploader: Callable[..., IntegrationResult] | None = None,
|
|
92
94
|
direct_uploader: Callable[..., IntegrationResult] | None = None,
|
|
93
95
|
postiz_publisher: Callable[..., IntegrationResult] | None = None,
|
|
96
|
+
composio_publisher: Callable[..., IntegrationResult] | None = None,
|
|
94
97
|
) -> IntegrationResult:
|
|
95
98
|
attempts: list[dict[str, Any]] = []
|
|
99
|
+
composio_enabled = bool(settings.get("composio_enabled", False)) and bool(settings.get("composio_auto_upload", True))
|
|
100
|
+
composio_ready = composio_enabled and bool(settings.get("composio_api_key")) and bool(settings.get("composio_tool_slug"))
|
|
101
|
+
if composio_enabled and not composio_ready:
|
|
102
|
+
composio_result = IntegrationResult(False, "Composio está activo, mas falta API key ou slug da ferramenta de upload.", {})
|
|
103
|
+
attempts.append(_attempt_record("Composio", composio_result, skipped=True))
|
|
104
|
+
elif composio_ready:
|
|
105
|
+
composio_publisher = composio_publisher or _composio_upload
|
|
106
|
+
try:
|
|
107
|
+
composio_result = composio_publisher(
|
|
108
|
+
settings,
|
|
109
|
+
video_path=video_path,
|
|
110
|
+
title=title,
|
|
111
|
+
description=description,
|
|
112
|
+
tags=tags or [],
|
|
113
|
+
thumbnail_path=thumbnail_path,
|
|
114
|
+
captions_path=captions_path,
|
|
115
|
+
)
|
|
116
|
+
except Exception as exc:
|
|
117
|
+
composio_result = IntegrationResult(False, f"Composio falhou: {type(exc).__name__}: {exc}", {})
|
|
118
|
+
attempts.append(_attempt_record("Composio", composio_result))
|
|
119
|
+
if composio_result.ok:
|
|
120
|
+
return _result_with_attempts(composio_result, attempts, "Composio")
|
|
121
|
+
|
|
96
122
|
quota_count = official_upload_count(channel, account)
|
|
97
123
|
if quota_count >= OFFICIAL_DAILY_LIMIT:
|
|
98
124
|
quota_result = IntegrationResult(False, f"Limite local de {OFFICIAL_DAILY_LIMIT} envios oficiais por conta Google atingido hoje; a tentar o próximo método.", {"count": quota_count, "limit": OFFICIAL_DAILY_LIMIT})
|
|
@@ -184,3 +210,22 @@ def upload_with_default_route(
|
|
|
184
210
|
return _result_with_attempts(postiz_result, attempts, "Postiz")
|
|
185
211
|
|
|
186
212
|
return IntegrationResult(False, "Nenhum método de envio conseguiu publicar o vídeo.", {"attempts": attempts, "route": "none"})
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _composio_upload(settings: dict[str, Any], **kwargs: Any) -> IntegrationResult:
|
|
216
|
+
try:
|
|
217
|
+
result = execute_upload(
|
|
218
|
+
str(settings.get("composio_api_key") or ""),
|
|
219
|
+
str(settings.get("composio_user_id") or ""),
|
|
220
|
+
str(settings.get("composio_tool_slug") or ""),
|
|
221
|
+
str(kwargs.get("video_path") or ""),
|
|
222
|
+
str(settings.get("composio_file_field") or "file"),
|
|
223
|
+
str(settings.get("composio_arguments_json") or "{}"),
|
|
224
|
+
)
|
|
225
|
+
except ComposioUploadError as exc:
|
|
226
|
+
return IntegrationResult(False, str(exc), {})
|
|
227
|
+
return IntegrationResult(
|
|
228
|
+
bool(result.get("successful")),
|
|
229
|
+
str(result.get("error") or "Upload via Composio concluído."),
|
|
230
|
+
{"composio_data": result.get("data") or {}, "log_id": result.get("log_id") or ""},
|
|
231
|
+
)
|
package/package.json
CHANGED