@danhachuel/thunderbolt 0.6.3 → 0.6.5
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 +31 -11
- package/hermes_ui/storage.py +8 -5
- package/integrations/upload_routing.py +27 -9
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -118,7 +118,7 @@ from integrations.upload_post import UploadPostAdapter, UPLOAD_POST_PLATFORM_OPT
|
|
|
118
118
|
from integrations.bilibili_upload import BilibiliApiAdapter, BILIBILI_DEFAULT_TID, BILIBILI_VIDEO_EXTENSIONS, normalise_bilibili_api_cards
|
|
119
119
|
from integrations.distrokid_upload import DistroKidAdapter, DISTROKID_AUDIO_EXTENSIONS, DISTROKID_COVER_EXTENSIONS, close_distrokid_session
|
|
120
120
|
from integrations.music_uploads import JewelMusicAdapter, PushtunesAdapter, YTMusicApiAdapter, MUSIC_UPLOAD_EXTENSIONS, PUSHTUNES_OPERATIONS, PUSHTUNES_SOURCES, PUSHTUNES_TARGETS, YT_MUSIC_UPLOAD_EXTENSIONS
|
|
121
|
-
from integrations.upload_routing import OFFICIAL_DAILY_LIMIT, official_upload_count, upload_with_default_route
|
|
121
|
+
from integrations.upload_routing import COMPOSIO_OPERATION_OPTIONS, OFFICIAL_DAILY_LIMIT, official_upload_count, upload_with_default_route
|
|
122
122
|
from integrations.youtube_direct_upload import YouTubeDirectUploader
|
|
123
123
|
from integrations.youtube_direct_credentials import delete_credentials_document, direct_account_status, document_status, ensure_credentials_document, load_credentials_document, merge_credentials_document, parse_credentials_document, save_credentials_document, update_credentials_document_session_info
|
|
124
124
|
from integrations.session_info_health import check_account_session_info_health
|
|
@@ -8028,19 +8028,39 @@ def render_settings():
|
|
|
8028
8028
|
_api_status_badge("Configured" if bilibili_ready else "Missing configuration", "ready" if bilibili_ready else "missing")
|
|
8029
8029
|
render_bilibili_api_cards(settings)
|
|
8030
8030
|
with st.expander("Composio", expanded=False):
|
|
8031
|
-
|
|
8031
|
+
composio_operation_options = COMPOSIO_OPERATION_OPTIONS
|
|
8032
|
+
composio_operation_slugs = list(composio_operation_options)
|
|
8033
|
+
st.caption("Os slugs e parâmetros técnicos do Composio são definidos pelo Thunderbolt conforme a operação; a autenticação da conta continua a ser feita pelo Connect Link do Composio.")
|
|
8032
8034
|
composio_enabled = st.checkbox("Activar Composio", value=bool(settings.get("composio_enabled", False)), key="upload_composio_enabled")
|
|
8033
8035
|
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.")
|
|
8034
8036
|
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.")
|
|
8035
8037
|
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.")
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8038
|
+
current_tool_slug = str(settings.get("composio_tool_slug") or "upload_video")
|
|
8039
|
+
if current_tool_slug not in composio_operation_slugs:
|
|
8040
|
+
current_tool_slug = "upload_video"
|
|
8041
|
+
composio_tool_slug = st.selectbox("Slug da ferramenta para Automação Youtube", composio_operation_slugs, index=composio_operation_slugs.index(current_tool_slug), format_func=lambda slug: f"{composio_operation_options[slug]} ({slug})", key="upload_composio_tool_slug", help="O Thunderbolt selecciona internamente o slug da operação; `upload_video` é o default.")
|
|
8042
|
+
current_toolkit = str(settings.get("composio_toolkit") or composio_tool_slug)
|
|
8043
|
+
if current_toolkit not in composio_operation_slugs:
|
|
8044
|
+
current_toolkit = composio_tool_slug
|
|
8045
|
+
composio_toolkit = st.selectbox("Toolkit preferido (opcional)", composio_operation_slugs, index=composio_operation_slugs.index(current_toolkit), format_func=lambda slug: f"{composio_operation_options[slug]} ({slug})", key="upload_composio_toolkit", help="A lista segue as operações suportadas pelo Thunderbolt.")
|
|
8046
|
+
st.text_input("Campo do ficheiro na ferramenta", value="videoFilePath", disabled=True, help="Configurado internamente pelo backend. O path do vídeo é injectado automaticamente a partir do artefacto da tarefa.")
|
|
8047
|
+
composio_privacy_options = ["unlisted", "listed"]
|
|
8048
|
+
current_privacy = str(settings.get("composio_privacy_status") or "unlisted")
|
|
8049
|
+
if current_privacy not in composio_privacy_options:
|
|
8050
|
+
current_privacy = "unlisted"
|
|
8051
|
+
composio_privacy_status = st.selectbox("Campo de privacidade", composio_privacy_options, index=composio_privacy_options.index(current_privacy), key="upload_composio_privacy_status", help="O valor é enviado pelo backend para a operação de publicação; `unlisted` é o default.")
|
|
8052
|
+
composio_category_options = [str(category_id) for category_id in range(1, 101)]
|
|
8053
|
+
current_category = str(settings.get("composio_category_id") or "22")
|
|
8054
|
+
if current_category not in composio_category_options:
|
|
8055
|
+
current_category = "22"
|
|
8056
|
+
composio_category_id = st.selectbox("Campo de categoria", composio_category_options, index=composio_category_options.index(current_category), key="upload_composio_category_id", help="Categorias YouTube de 1 a 100; `22` é o default.")
|
|
8057
|
+
composio_language_options = list(LANGUAGE_CODES)
|
|
8058
|
+
current_language = str(settings.get("composio_language") or "en")
|
|
8059
|
+
if current_language not in composio_language_options:
|
|
8060
|
+
current_language = "en"
|
|
8061
|
+
composio_language = st.selectbox("Campo de idioma", composio_language_options, index=composio_language_options.index(current_language), format_func=language_label, key="upload_composio_language", help="Idioma usado pelo backend nos argumentos da ferramenta; inglês é o default.")
|
|
8062
|
+
composio_channel_field = str(settings.get("composio_channel_field") or "")
|
|
8063
|
+
composio_arguments_json = str(settings.get("composio_arguments_json") or "{}")
|
|
8044
8064
|
_render_credential_status(composio_api_key)
|
|
8045
8065
|
composio_action_cols = st.columns(2)
|
|
8046
8066
|
with composio_action_cols[0]:
|
|
@@ -8048,7 +8068,7 @@ def render_settings():
|
|
|
8048
8068
|
with composio_action_cols[1]:
|
|
8049
8069
|
test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
|
|
8050
8070
|
if save_composio:
|
|
8051
|
-
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
|
|
8071
|
+
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, "composio_tool_slug": composio_tool_slug, "composio_file_field": "videoFilePath", "composio_channel_field": composio_channel_field, "composio_privacy_status": composio_privacy_status, "composio_category_id": composio_category_id, "composio_language": composio_language, "composio_privacy_field": "privacyStatus", "composio_category_field": "categoryId", "composio_language_field": "defaultLanguage", "composio_arguments_json": composio_arguments_json or "{}"})
|
|
8052
8072
|
write_json("settings.json", settings)
|
|
8053
8073
|
st.success("Configuração Composio guardada.")
|
|
8054
8074
|
st.rerun()
|
package/hermes_ui/storage.py
CHANGED
|
@@ -291,13 +291,16 @@ DEFAULTS: dict[str, Any] = {
|
|
|
291
291
|
"composio_auto_upload": True,
|
|
292
292
|
"composio_api_key": "",
|
|
293
293
|
"composio_user_id": "thunderbolt-local",
|
|
294
|
-
"composio_toolkit": "",
|
|
295
|
-
"composio_tool_slug": "",
|
|
294
|
+
"composio_toolkit": "upload_video",
|
|
295
|
+
"composio_tool_slug": "upload_video",
|
|
296
296
|
"composio_file_field": "videoFilePath",
|
|
297
297
|
"composio_channel_field": "",
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
"
|
|
298
|
+
"composio_privacy_status": "unlisted",
|
|
299
|
+
"composio_category_id": "22",
|
|
300
|
+
"composio_language": "en",
|
|
301
|
+
"composio_privacy_field": "privacyStatus",
|
|
302
|
+
"composio_category_field": "categoryId",
|
|
303
|
+
"composio_language_field": "defaultLanguage",
|
|
301
304
|
"composio_arguments_json": "{}",
|
|
302
305
|
"postiz_enabled": False,
|
|
303
306
|
"postiz_api_key": "",
|
|
@@ -25,6 +25,15 @@ from hermes_ui.languages import language_locale
|
|
|
25
25
|
|
|
26
26
|
OFFICIAL_DAILY_LIMIT = 5
|
|
27
27
|
QUOTA_FILENAME = "official_upload_quota.json"
|
|
28
|
+
COMPOSIO_VIDEO_FILE_FIELD = "videoFilePath"
|
|
29
|
+
COMPOSIO_OPERATION_OPTIONS = {
|
|
30
|
+
"upload_video": "Upload Video",
|
|
31
|
+
"update_video": "Update video",
|
|
32
|
+
"upload_tiktok_video": "Upload TikTok Video",
|
|
33
|
+
"upload_instagram_media": "Upload Instagram vídeo/Reel/foto",
|
|
34
|
+
}
|
|
35
|
+
COMPOSIO_PRIVACY_VALUES = {"listed", "unlisted"}
|
|
36
|
+
COMPOSIO_CATEGORY_RANGE = range(1, 101)
|
|
28
37
|
|
|
29
38
|
|
|
30
39
|
def _attempt_record(name: str, result: IntegrationResult, *, skipped: bool = False) -> dict[str, Any]:
|
|
@@ -232,7 +241,8 @@ def upload_with_default_route(
|
|
|
232
241
|
def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwargs: Any) -> IntegrationResult:
|
|
233
242
|
slug = str(settings.get("composio_tool_slug") or "").strip()
|
|
234
243
|
normalized_slug = slug.upper().replace("-", "_")
|
|
235
|
-
|
|
244
|
+
connected_account_tool = normalized_slug in {"UPLOAD_VIDEO", "UPDATE_VIDEO", "UPLOAD_TIKTOK_VIDEO", "UPLOAD_INSTAGRAM_MEDIA"}
|
|
245
|
+
youtube_upload_tool = normalized_slug in {"UPLOAD_VIDEO", "YOUTUBE_UPLOAD_VIDEO", "YOUTUBE_MULTIPART_UPLOAD_VIDEO"} or ("YOUTUBE" in normalized_slug and "UPLOAD" in normalized_slug and normalized_slug != "YOUTUBE_UPLOAD")
|
|
236
246
|
channel_id = str(
|
|
237
247
|
channel.get("youtube_channel_id")
|
|
238
248
|
or channel.get("youtube_id")
|
|
@@ -245,24 +255,32 @@ def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwa
|
|
|
245
255
|
match = re.search(r"/channel/(UC[\w-]+)", str(channel.get("url") or ""))
|
|
246
256
|
channel_id = match.group(1) if match else ""
|
|
247
257
|
channel_field = str(settings.get("composio_channel_field") or "channel_id").strip()
|
|
248
|
-
if not youtube_upload_tool and not channel_id:
|
|
258
|
+
if not connected_account_tool and not youtube_upload_tool and not channel_id:
|
|
249
259
|
return IntegrationResult(False, "Composio não foi executado: o canal da tarefa não tem um YouTube channel ID sincronizado.", {"missing": ["youtube_channel_id"]})
|
|
250
|
-
if not youtube_upload_tool and not channel_field:
|
|
260
|
+
if not connected_account_tool and not youtube_upload_tool and not channel_field:
|
|
251
261
|
return IntegrationResult(False, "Composio não foi executado: configure o campo de canal da ferramenta.", {"missing": ["composio_channel_field"]})
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
file_field = "videoFilePath"
|
|
262
|
+
# O path técnico nunca vem da configuração editável: é injectado pelo backend.
|
|
263
|
+
file_field = COMPOSIO_VIDEO_FILE_FIELD
|
|
255
264
|
try:
|
|
256
265
|
arguments = str(settings.get("composio_arguments_json") or "{}").strip() or "{}"
|
|
257
266
|
import json
|
|
258
267
|
parsed_arguments = json.loads(arguments)
|
|
259
268
|
if not isinstance(parsed_arguments, dict):
|
|
260
269
|
return IntegrationResult(False, "Composio não foi executado: os argumentos JSON devem ser um objecto.", {})
|
|
270
|
+
privacy_value = str(settings.get("composio_privacy_status") or kwargs.get("privacy_status") or "unlisted").strip().lower()
|
|
271
|
+
if privacy_value not in COMPOSIO_PRIVACY_VALUES:
|
|
272
|
+
privacy_value = "unlisted"
|
|
273
|
+
try:
|
|
274
|
+
category_value = int(str(settings.get("composio_category_id") or kwargs.get("category_id") or "22").strip())
|
|
275
|
+
except ValueError:
|
|
276
|
+
category_value = 22
|
|
277
|
+
if category_value not in COMPOSIO_CATEGORY_RANGE:
|
|
278
|
+
category_value = 22
|
|
261
279
|
locked_values = {
|
|
262
280
|
**({channel_field: channel_id} if channel_field and not youtube_upload_tool else {}),
|
|
263
|
-
str(settings.get("composio_privacy_field") or "privacy_status").strip():
|
|
264
|
-
str(settings.get("composio_category_field") or "category_id").strip(): str(
|
|
265
|
-
str(settings.get("composio_language_field") or "language").strip(): language_locale(kwargs.get("language") or channel.get("language") or "
|
|
281
|
+
str(settings.get("composio_privacy_field") or "privacy_status").strip(): privacy_value,
|
|
282
|
+
str(settings.get("composio_category_field") or "category_id").strip(): str(category_value),
|
|
283
|
+
str(settings.get("composio_language_field") or "language").strip(): language_locale(settings.get("composio_language") or kwargs.get("language") or channel.get("language") or "en"),
|
|
266
284
|
}
|
|
267
285
|
for field, expected in locked_values.items():
|
|
268
286
|
if not field:
|
package/package.json
CHANGED