@danhachuel/thunderbolt 0.4.63 → 0.4.64

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.
@@ -839,3 +839,5 @@ Antes de clicar em **Enviar vídeo via Composio**, confirme o vídeo, o toolkit,
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
840
 
841
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.
842
+
843
+ Na Automação Youtube, o Composio recebe ainda os mesmos parâmetros do upload oficial: privacidade `unlisted` (Não listado), categoria `22` e o locale do idioma do vídeo/canal, como `pt-BR`. Os nomes dos campos podem ser ajustados no painel, mas valores divergentes nos argumentos JSON são bloqueados.
package/app/main.py CHANGED
@@ -7206,6 +7206,9 @@ def render_settings():
7206
7206
  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.")
7207
7207
  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")
7208
7208
  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.")
7209
+ composio_privacy_field = st.text_input("Campo de privacidade", value=str(settings.get("composio_privacy_field") or "privacy_status"), key="upload_composio_privacy_field", help="Recebe sempre `unlisted` (Não listado), como no upload oficial.")
7210
+ composio_category_field = st.text_input("Campo de categoria", value=str(settings.get("composio_category_field") or "category_id"), key="upload_composio_category_field", help="Recebe sempre `22`, categoria Pessoas e blogs, como no upload oficial.")
7211
+ composio_language_field = st.text_input("Campo de idioma", value=str(settings.get("composio_language_field") or "language"), key="upload_composio_language_field", help="Recebe o locale do idioma do vídeo/canal, por exemplo `pt-BR`.")
7209
7212
  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)
7210
7213
  _render_credential_status(composio_api_key)
7211
7214
  composio_action_cols = st.columns(2)
@@ -7214,7 +7217,7 @@ def render_settings():
7214
7217
  with composio_action_cols[1]:
7215
7218
  test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
7216
7219
  if save_composio:
7217
- 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 "{}"})
7220
+ 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_privacy_field": composio_privacy_field.strip() or "privacy_status", "composio_category_field": composio_category_field.strip() or "category_id", "composio_language_field": composio_language_field.strip() or "language", "composio_arguments_json": composio_arguments_json.strip() or "{}"})
7218
7221
  write_json("settings.json", settings)
7219
7222
  st.success("Configuração Composio guardada.")
7220
7223
  st.rerun()
@@ -295,6 +295,9 @@ DEFAULTS: dict[str, Any] = {
295
295
  "composio_tool_slug": "",
296
296
  "composio_file_field": "file",
297
297
  "composio_channel_field": "channel_id",
298
+ "composio_privacy_field": "privacy_status",
299
+ "composio_category_field": "category_id",
300
+ "composio_language_field": "language",
298
301
  "composio_arguments_json": "{}",
299
302
  "postiz_enabled": False,
300
303
  "postiz_api_key": "",
@@ -20,6 +20,7 @@ from integrations.youtube_direct_credentials import document_status
20
20
  from integrations.youtube_direct_upload import YouTubeDirectUploader
21
21
  from integrations.composio_upload import ComposioUploadError, execute_upload
22
22
  from hermes_ui.storage import read_json, write_json
23
+ from hermes_ui.languages import language_locale
23
24
 
24
25
  OFFICIAL_DAILY_LIMIT = 5
25
26
  QUOTA_FILENAME = "official_upload_quota.json"
@@ -108,6 +109,9 @@ def upload_with_default_route(
108
109
  settings,
109
110
  channel=channel,
110
111
  video_path=video_path,
112
+ category_id=category_id,
113
+ language=language or channel.get("language") or "pt-BR",
114
+ privacy_status=privacy_status or "unlisted",
111
115
  title=title,
112
116
  description=description,
113
117
  tags=tags or [],
@@ -226,10 +230,21 @@ def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwa
226
230
  parsed_arguments = json.loads(arguments)
227
231
  if not isinstance(parsed_arguments, dict):
228
232
  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
233
+ locked_values = {
234
+ channel_field: channel_id,
235
+ str(settings.get("composio_privacy_field") or "privacy_status").strip(): str(kwargs.get("privacy_status") or "unlisted"),
236
+ str(settings.get("composio_category_field") or "category_id").strip(): str(kwargs.get("category_id") or "22"),
237
+ str(settings.get("composio_language_field") or "language").strip(): language_locale(kwargs.get("language") or channel.get("language") or "pt"),
238
+ }
239
+ for field, expected in locked_values.items():
240
+ if not field:
241
+ return IntegrationResult(False, "Composio não foi executado: existe um campo obrigatório vazio na configuração.", {})
242
+ configured_value = parsed_arguments.get(field)
243
+ if configured_value not in (None, "", expected):
244
+ if field == channel_field:
245
+ return IntegrationResult(False, f"Composio bloqueado: o campo `{field}` aponta para outro canal.", {"field": field, "expected": expected})
246
+ return IntegrationResult(False, f"Composio bloqueado: o campo `{field}` tem um valor diferente do upload oficial ({expected}).", {"field": field, "expected": expected})
247
+ parsed_arguments[field] = expected
233
248
  result = execute_upload(
234
249
  str(settings.get("composio_api_key") or ""),
235
250
  str(settings.get("composio_user_id") or ""),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.63",
3
+ "version": "0.4.64",
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",