@danhachuel/thunderbolt 0.4.58 → 0.4.59

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.
@@ -837,3 +837,5 @@ Quando o provider ainda não estiver conectado, use **Autorizar toolkit no Compo
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
838
 
839
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.
840
+
841
+ Para garantir o canal correcto, configure também o **Campo do canal na ferramenta**. O worker lê o `youtube_channel_id` do canal associado à tarefa e substitui explicitamente esse campo nos argumentos enviados ao Composio. Se a tarefa não tiver um YouTube channel ID sincronizado, ou se os argumentos configurarem outro ID, o envio Composio é bloqueado e os fallbacks podem actuar; a publicação nunca é enviada silenciosamente para um canal diferente.
package/app/main.py CHANGED
@@ -7173,6 +7173,7 @@ def render_settings():
7173
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
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
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_channel_field = st.text_input("Campo do canal na ferramenta", value=str(settings.get("composio_channel_field") or "channel_id"), key="upload_composio_channel_field", help="O worker substitui este campo pelo YouTube channel ID do canal da tarefa, impedindo o envio para outro canal.")
7176
7177
  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)
7177
7178
  _render_credential_status(composio_api_key)
7178
7179
  composio_action_cols = st.columns(2)
@@ -7181,7 +7182,7 @@ def render_settings():
7181
7182
  with composio_action_cols[1]:
7182
7183
  test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
7183
7184
  if save_composio:
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 "{}"})
7185
+ 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_channel_field": composio_channel_field.strip() or "channel_id", "composio_arguments_json": composio_arguments_json.strip() or "{}"})
7185
7186
  write_json("settings.json", settings)
7186
7187
  st.success("Configuração Composio guardada.")
7187
7188
  st.rerun()
@@ -294,6 +294,7 @@ DEFAULTS: dict[str, Any] = {
294
294
  "composio_toolkit": "",
295
295
  "composio_tool_slug": "",
296
296
  "composio_file_field": "file",
297
+ "composio_channel_field": "channel_id",
297
298
  "composio_arguments_json": "{}",
298
299
  "postiz_enabled": False,
299
300
  "postiz_api_key": "",
@@ -106,6 +106,7 @@ def upload_with_default_route(
106
106
  try:
107
107
  composio_result = composio_publisher(
108
108
  settings,
109
+ channel=channel,
109
110
  video_path=video_path,
110
111
  title=title,
111
112
  description=description,
@@ -212,15 +213,30 @@ def upload_with_default_route(
212
213
  return IntegrationResult(False, "Nenhum método de envio conseguiu publicar o vídeo.", {"attempts": attempts, "route": "none"})
213
214
 
214
215
 
215
- def _composio_upload(settings: dict[str, Any], **kwargs: Any) -> IntegrationResult:
216
+ def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwargs: Any) -> IntegrationResult:
217
+ channel_id = str(channel.get("youtube_channel_id") or "").strip()
218
+ if not channel_id:
219
+ return IntegrationResult(False, "Composio não foi executado: o canal da tarefa não tem um YouTube channel ID sincronizado.", {"missing": ["youtube_channel_id"]})
220
+ channel_field = str(settings.get("composio_channel_field") or "channel_id").strip()
221
+ if not channel_field:
222
+ return IntegrationResult(False, "Composio não foi executado: configure o campo de canal da ferramenta.", {"missing": ["composio_channel_field"]})
216
223
  try:
224
+ arguments = str(settings.get("composio_arguments_json") or "{}").strip() or "{}"
225
+ import json
226
+ parsed_arguments = json.loads(arguments)
227
+ if not isinstance(parsed_arguments, dict):
228
+ return IntegrationResult(False, "Composio não foi executado: os argumentos JSON devem ser um objecto.", {})
229
+ configured_channel = parsed_arguments.get(channel_field)
230
+ if configured_channel not in (None, "", channel_id):
231
+ return IntegrationResult(False, f"Composio bloqueado: o campo `{channel_field}` aponta para outro canal.", {"expected_channel_id": channel_id})
232
+ parsed_arguments[channel_field] = channel_id
217
233
  result = execute_upload(
218
234
  str(settings.get("composio_api_key") or ""),
219
235
  str(settings.get("composio_user_id") or ""),
220
236
  str(settings.get("composio_tool_slug") or ""),
221
237
  str(kwargs.get("video_path") or ""),
222
238
  str(settings.get("composio_file_field") or "file"),
223
- str(settings.get("composio_arguments_json") or "{}"),
239
+ json.dumps(parsed_arguments, ensure_ascii=False),
224
240
  )
225
241
  except ComposioUploadError as exc:
226
242
  return IntegrationResult(False, str(exc), {})
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.58",
3
+ "version": "0.4.59",
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",