@danhachuel/thunderbolt 0.3.66 → 0.3.67

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Este manual descreve a instalação local da UI Thunderbolt, baseada no MoneyPrinterTurbo, utilizando o pacote npm `@danhachuel/thunderbolt`. O fluxo recomendado instala automaticamente o ambiente Python, as dependências da aplicação, as dependências do MoneyPrinterTurbo, o Streamlit e o suporte FFmpeg através de `imageio-ffmpeg`.
4
4
 
5
- > **Versão deste manual:** 0.3.66
5
+ > **Versão deste manual:** 0.3.67
6
6
  > **Pacote npm:** `@danhachuel/thunderbolt`
7
7
  > **Porta padrão da UI:** `localhost:3030`
8
8
  > **Repositório:** [github.com/DanHachuel/thunderbolt](https://github.com/DanHachuel/thunderbolt)
@@ -128,13 +128,13 @@ Execute:
128
128
  Windows PowerShell ou MobaXterm:
129
129
 
130
130
  ```powershell
131
- npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.66 install
131
+ npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.67 install
132
132
  ```
133
133
 
134
134
  Linux/macOS:
135
135
 
136
136
  ```bash
137
- npx --yes --prefer-online @danhachuel/thunderbolt@0.3.66 install
137
+ npx --yes --prefer-online @danhachuel/thunderbolt@0.3.67 install
138
138
  ```
139
139
 
140
140
  A instalação normal é **segura para actualizações**: preserva `storage`, Blueprints, Brandings, configurações e artefactos do utilizador. Remove apenas `.venv`, o clone técnico do MoneyPrinterTurbo e dependências que serão recriadas. Uma pasta antiga sem dados do utilizador, como `C:\Users\<utilizador>\AppData\Local\hermes` da tentativa incompleta, pode ser removida; uma pasta antiga que contenha Blueprints, Brandings ou storage é preservada e apenas avisada no terminal. Feche processos Python, Node, Streamlit e MobaXterm que estejam a usar as pastas antes de executar.
@@ -142,7 +142,7 @@ A instalação normal é **segura para actualizações**: preserva `storage`, Bl
142
142
  Se quiser apagar absolutamente tudo de forma intencional, use o comando destrutivo separado:
143
143
 
144
144
  ```powershell
145
- npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.66 install --purge-data
145
+ npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.67 install --purge-data
146
146
  ```
147
147
 
148
148
  O parâmetro `--purge-data` apaga Blueprints, Brandings, configurações, storage e artefactos locais. Não o use numa actualização normal.
@@ -397,6 +397,12 @@ A área **Voz, TTS e música — Azure Speech, restantes serviços e Suno** est
397
397
 
398
398
  Na subaba **Imagem e Video**, a lista **Provider de media** inclui **FAL AI, KIE AI, Agnes AI, Nano Banana, Replicate AI, Pollinations.ai, Hugging Face Inference API, InferencePort Proxy e HeyGen** para a rota Full IA. O cartão **HeyGen** pede a API key, o **Avatar ID** e, opcionalmente, o **Voice ID**; a opção **Provider activo**, **Pool Vídeo**, **Prioridade** e **Testar Chamada API** funcionam como nos restantes cartões. O teste HeyGen usa apenas `GET /v3/users/me` e não consome uma geração. Nano Banana e Hugging Face podem permanecer no catálogo sem serem seleccionados para vídeo quando o cartão não declara capacidade de vídeo.
399
399
 
400
+ ### API Tiktok — várias aplicações
401
+
402
+ Em **Configuração API > API Tiktok**, situada entre **Contas Google** e **Fontes de Materiais**, cada aplicação TikTok é configurada num card independente. O card contém exclusivamente **TikTok Client ID** e **TikTok Client Secret** e apresenta os botões **Testar chamada API**, **Guardar card** e **Apagar card**. Use **Adicionar nova API** no final da lista para criar outra aplicação sem substituir as anteriores.
403
+
404
+ O botão de teste é read-only. Sem um token OAuth TikTok já autorizado, o Thunderbolt informa que é necessário concluir o Playground/autorização do TikTok for Developers, sem inventar um resultado nem enviar credenciais. O token e a autorização não são campos dos cards. Instalações que ainda tenham `tiktok_client_key` e `tiktok_client_secret` são migradas automaticamente para o primeiro card; o adapter usa o primeiro card completo e mantém fallback para os campos antigos.
405
+
400
406
  Na criação de vídeo, abra **Configurações de áudio** e escolha **Upload** em **Modo de narração**. Use **Ficheiro de narração** para seleccionar o áudio, clique em **Guardar áudio de narração** e confirme a pré-visualização. O Thunderbolt valida que o ficheiro existe antes de criar a tarefa e encaminha o caminho ao MoneyPrinterTurbo com `--custom-audio-file`; este argumento é necessário porque o motor não persiste automaticamente o caminho carregado. São aceites ficheiros `.mp3`, `.wav`, `.m4a`, `.aac`, `.flac` e `.ogg`, guardados no storage local em `voiceovers`.
401
407
 
402
408
  O selector **Voiceover Service** oferece **Azure Speech SDK V2** e **Azure TTS V1**. Quando existe **Azure Speech key + região**, a opção V2 é preferida e o worker marca internamente a voz com `-V2`, activando o SDK Azure Speech e evitando o stream `edge_tts`. Tarefas antigas que ainda tenham `Azure TTS V1` guardado também são migradas para V2 quando essas credenciais estão configuradas. Sem credenciais Azure, o V1 continua disponível como fallback sem key; nesse caso o timeout interno do stream passa a 90 segundos para tolerar scripts longos e redes lentas. Um erro de TTS identifica explicitamente **Azure Speech / edge_tts API**, a etapa **Narração TTS** e os providers envolvidos.
@@ -562,7 +568,7 @@ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega
562
568
 
563
569
  Os parâmetros da UI são número de clusters entre 2 e 10, suporte mínimo entre 0,01 e 0,50, país, engagement, intervalo de datas e tags, todos dentro da área principal da aba. O núcleo normaliza os dados, calcula engagement, aplica filtros, faz transformação logarítmica e standardização, executa K-Means e calcula itemsets/regras com FP-Growth. Não são apresentados resultados até ao primeiro clique em **Analisar Nichos**; o mesmo botão aplica alterações posteriores aos filtros. Os resultados são DataFrames de clusters, itemsets frequentes, regras de associação e dados analisados; o gráfico de dispersão é criado nativamente com Plotly.
564
570
 
565
- As dependências adicionais — `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub` — são instaladas pelo procedimento normal de `npx`. Em instalações existentes, execute novamente `npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.66 install`; o instalador detecta e reutiliza o que já estiver válido.
571
+ As dependências adicionais — `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub` — são instaladas pelo procedimento normal de `npx`. Em instalações existentes, execute novamente `npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.67 install`; o instalador detecta e reutiliza o que já estiver válido.
566
572
 
567
573
  ### Niche Finder Apify
568
574
 
package/README.md CHANGED
@@ -25,7 +25,7 @@ A primeira versão implementa a camada UI independente com:
25
25
  | Edição | Menu expansível abaixo de Automação com Limpador de Metadados, Clip Generator local em Cortes e Editor Python inspirado no PYEdit para vídeos e scripts locais |
26
26
  | Upload | YouTube via `youtube-automation-agent` adaptado internamente, fallback ordenado API Oficial → Upload directo → Postiz, upload de MP4 para Postiz via API key/MCP configurável, TikTok, Instagram e Facebook Pages no front end |
27
27
  | MCP | Catálogo local opcional de Short Video Maker, AutoVio, OpenMontage e OpenCut, com portas editáveis e activação |
28
- | Configurações | **Contas Google**, **Configuração API** e **Notificações**; Notificações contém as subabas **Geral** e **Telegram Gateway**, com envio das mesmas operações locais para um Chat ID; API Keys divididas entre **Serviços e modelos** e **Fontes de materiais**, com várias chaves independentes por fonte, além de contas Google/YouTube por cartão, `INNERTUBE_API_KEY` global para todas as contas, providers, TTS, Nano Banana, Postiz, TikTok e Upload-Post |
28
+ | Configurações | **Contas Google**, **Configuração API** e **Notificações**; Notificações contém as subabas **Geral** e **Telegram Gateway**, com envio das mesmas operações locais para um Chat ID; API Keys divididas entre **Serviços e modelos**, **API Tiktok** e **Fontes de materiais**, com várias chaves independentes por fonte, além de contas Google/YouTube por cartão, `INNERTUBE_API_KEY` global para todas as contas, providers, TTS, Nano Banana, Postiz, TikTok e Upload-Post |
29
29
  | Launcher | Execução via `npx`, instalação assistida, diagnóstico e preparação para distribuição |
30
30
 
31
31
  ## AI Influencers — personagens e geração de conteúdo
@@ -48,6 +48,12 @@ Quando uma etapa falha, a tarefa, a notificação e a página **Configurações
48
48
 
49
49
  Na subaba **Configuração API > API Keys > Imagem e Video**, o selector **Provider de media** inclui a mesma lista do pool Full IA. O cartão **HeyGen** usa a API V3, apresenta os campos **Avatar ID** e **Voice ID**, valida a credencial com uma chamada read-only e participa no failover de vídeo quando estiver activo e configurado. Nano Banana e Hugging Face permanecem disponíveis no catálogo; a participação efectiva no pool de vídeo depende da capacidade declarada pelo cartão, para não encaminhar vídeo para um endpoint que apenas suporte imagem.
50
50
 
51
+ ## API Tiktok — múltiplas aplicações TikTok
52
+
53
+ A subaba **Configuração API > API Tiktok** fica entre **Contas Google** e **Fontes de Materiais**. Cada aplicação TikTok é apresentada num card separado e contém apenas **TikTok Client ID** e **TikTok Client Secret**, ambos guardados no storage local. O botão **Adicionar nova API** cria cards adicionais; cada card dispõe de **Guardar card**, **Testar chamada API** e **Apagar card**. A autorização OAuth, os scopes e os tokens de publicação continuam fora desta subaba, no fluxo próprio do TikTok for Developers.
54
+
55
+ As credenciais antigas dos campos únicos `tiktok_client_key` e `tiktok_client_secret` são migradas automaticamente para o primeiro card sem serem eliminadas. Para compatibilidade com integrações existentes, o adapter TikTok utiliza o primeiro card completo e mantém fallback para os campos legados; a UI não altera os campos pedidos nem mistura Client ID/Secret com tokens, redirect URI ou scopes.
56
+
51
57
  ## Upload-Post — publicação para múltiplas plataformas
52
58
 
53
59
  A página **Upload** contém quatro subabas: **Upload convencional**, **Upload directo**, **Postiz** e **Upload-Post**. A quarta alternativa usa a API oficial do [Upload-Post](https://docs.upload-post.com/) para enviar um vídeo local para uma ou mais plataformas ligadas ao perfil configurado. Seleccione as plataformas na própria subaba, confirme o título e a descrição e clique em **Enviar vídeo pelo Upload-Post**.
package/app/main.py CHANGED
@@ -4660,6 +4660,103 @@ def render_upload_conventional():
4660
4660
  st.button("Preparar Facebook Pages", key=f"upload_facebook_{task['id']}", disabled=True, help="UI preparada; publicação Facebook Pages ainda não está activa.")
4661
4661
 
4662
4662
 
4663
+ def _normalise_tiktok_api_cards(settings: dict[str, Any]) -> tuple[list[dict[str, str]], bool]:
4664
+ raw_cards = settings.get("tiktok_api_cards")
4665
+ cards: list[dict[str, str]] = []
4666
+ changed = not isinstance(raw_cards, list)
4667
+ if isinstance(raw_cards, list):
4668
+ for index, raw_card in enumerate(raw_cards):
4669
+ if not isinstance(raw_card, dict):
4670
+ changed = True
4671
+ continue
4672
+ card_id = str(raw_card.get("id") or f"tiktok-api-{index + 1}").strip()
4673
+ client_id = str(raw_card.get("client_id") or raw_card.get("client_key") or "").strip()
4674
+ client_secret = str(raw_card.get("client_secret") or "").strip()
4675
+ normalised = {"id": card_id, "client_id": client_id, "client_secret": client_secret}
4676
+ cards.append(normalised)
4677
+ if normalised != {key: str(raw_card.get(key) or "").strip() for key in ("id", "client_id", "client_secret")}:
4678
+ changed = True
4679
+ if not cards:
4680
+ legacy_client_id = str(settings.get("tiktok_client_key") or "").strip()
4681
+ legacy_client_secret = str(settings.get("tiktok_client_secret") or "").strip()
4682
+ if legacy_client_id or legacy_client_secret:
4683
+ cards = [{"id": "tiktok-api-1", "client_id": legacy_client_id, "client_secret": legacy_client_secret}]
4684
+ changed = True
4685
+ if changed and cards:
4686
+ settings["tiktok_api_cards"] = cards
4687
+ write_json("settings.json", settings)
4688
+ return cards, changed
4689
+
4690
+
4691
+ def _persist_tiktok_api_cards(settings: dict[str, Any], cards: list[dict[str, str]]) -> None:
4692
+ clean_cards = [
4693
+ {
4694
+ "id": str(card.get("id") or f"tiktok-api-{index + 1}").strip(),
4695
+ "client_id": str(card.get("client_id") or "").strip(),
4696
+ "client_secret": str(card.get("client_secret") or "").strip(),
4697
+ }
4698
+ for index, card in enumerate(cards)
4699
+ ]
4700
+ settings["tiktok_api_cards"] = clean_cards
4701
+ first = next((card for card in clean_cards if card["client_id"] and card["client_secret"]), clean_cards[0] if clean_cards else {"client_id": "", "client_secret": ""})
4702
+ settings["tiktok_client_key"] = first["client_id"]
4703
+ settings["tiktok_client_secret"] = first["client_secret"]
4704
+ write_json("settings.json", settings)
4705
+
4706
+
4707
+ def render_tiktok_api_cards(settings: dict[str, Any]) -> None:
4708
+ st.title("API Tiktok")
4709
+ st.caption("Configure várias aplicações TikTok em cards separados. Cada card mantém apenas o TikTok Client ID e o TikTok Client Secret; autorização OAuth, scopes e tokens continuam a ser geridos no TikTok for Developers Playground.")
4710
+ cards, _ = _normalise_tiktok_api_cards(settings)
4711
+ if not cards:
4712
+ st.info("Ainda não existe nenhuma API TikTok configurada. Use o botão abaixo para criar o primeiro card.")
4713
+ for index, card in enumerate(cards):
4714
+ card_id = str(card["id"])
4715
+ with st.container(border=True):
4716
+ header_cols = st.columns([3.2, 1.2])
4717
+ with header_cols[0]:
4718
+ st.subheader(f"API TikTok {index + 1}")
4719
+ with header_cols[1]:
4720
+ configured = bool(card["client_id"] and card["client_secret"])
4721
+ _api_status_badge("Configured" if configured else "Missing configuration", "ready" if configured else "missing")
4722
+ with st.form(f"tiktok_api_card_form_{card_id}"):
4723
+ client_id = st.text_input("TikTok Client ID", value=card["client_id"], key=f"tiktok_api_{card_id}_client_id")
4724
+ client_secret = st.text_input("TikTok Client Secret", value=card["client_secret"], type="password", key=f"tiktok_api_{card_id}_client_secret")
4725
+ action_cols = st.columns(3)
4726
+ with action_cols[0]:
4727
+ test_clicked = st.form_submit_button("Testar chamada API", use_container_width=True)
4728
+ with action_cols[1]:
4729
+ save_clicked = st.form_submit_button("Guardar card", type="primary", use_container_width=True)
4730
+ with action_cols[2]:
4731
+ delete_clicked = st.form_submit_button("Apagar card", use_container_width=True)
4732
+ edited = {"id": card_id, "client_id": client_id.strip(), "client_secret": client_secret.strip()}
4733
+ if test_clicked:
4734
+ cards[index] = edited
4735
+ _persist_tiktok_api_cards(settings, cards)
4736
+ result = test_tiktok_credentials(edited["client_id"], edited["client_secret"], settings.get("tiktok_access_token", ""))
4737
+ if result.get("status") == "success":
4738
+ st.success(result.get("message") or "Chamada TikTok concluída.")
4739
+ elif result.get("status") == "unsupported":
4740
+ st.warning(result.get("message") or "Conclua a autorização TikTok antes do teste.")
4741
+ else:
4742
+ st.error(result.get("message") or "A chamada TikTok falhou.")
4743
+ elif save_clicked:
4744
+ cards[index] = edited
4745
+ _persist_tiktok_api_cards(settings, cards)
4746
+ st.success("Card TikTok guardado.")
4747
+ st.rerun()
4748
+ elif delete_clicked:
4749
+ cards = [item for item in cards if str(item.get("id")) != card_id]
4750
+ _persist_tiktok_api_cards(settings, cards)
4751
+ st.success("Card TikTok apagado.")
4752
+ st.rerun()
4753
+ if st.button("Adicionar nova API", type="primary", use_container_width=True, key="add_tiktok_api_card"):
4754
+ cards.append({"id": f"tiktok-api-{uuid.uuid4().hex[:10]}", "client_id": "", "client_secret": ""})
4755
+ _persist_tiktok_api_cards(settings, cards)
4756
+ st.success("Novo card TikTok criado.")
4757
+ st.rerun()
4758
+
4759
+
4663
4760
  def render_google_accounts():
4664
4761
  st.title("Contas Google")
4665
4762
  st.caption("Gestão das contas Google/YouTube, documentos de credenciais, canais em lote e credenciais globais do YouTube.")
@@ -5440,7 +5537,7 @@ def render_settings():
5440
5537
  key=f"settings_{key}",
5441
5538
  )
5442
5539
 
5443
- api_keys_tab, google_accounts_tab, material_sources_tab, ai_influencers_tab, voice_test_tab = render_localized_tabs(["API Keys", "Contas Google", "Fontes de Materiais", "AI Influencers", "Teste de Voz"])
5540
+ api_keys_tab, google_accounts_tab, tiktok_api_tab, material_sources_tab, ai_influencers_tab, voice_test_tab = render_localized_tabs(["API Keys", "Contas Google", "API Tiktok", "Fontes de Materiais", "AI Influencers", "Teste de Voz"])
5444
5541
 
5445
5542
  with api_keys_tab:
5446
5543
  with st.container(border=True):
@@ -5591,18 +5688,6 @@ def render_settings():
5591
5688
  widget_key="api_test_voice_suno",
5592
5689
  )
5593
5690
 
5594
- with st.expander("TikTok for Developers — Client ID e Client Secret", expanded=False):
5595
- st.caption("Apenas as credenciais da aplicação ficam nesta UI. Redirect URI, scopes, autorização e tokens são geridos no TikTok for Developers Playground.")
5596
- tiktok_client_key = text_setting("TikTok Client ID", "tiktok_client_key", secret=True)
5597
- _render_credential_status(tiktok_client_key)
5598
- tiktok_client_secret = text_setting("TikTok Client Secret", "tiktok_client_secret", secret=True)
5599
- _render_api_test_control(
5600
- settings,
5601
- "tiktok",
5602
- lambda: test_tiktok_credentials(tiktok_client_key, tiktok_client_secret, settings.get("tiktok_access_token", "")),
5603
- widget_key="api_test_tiktok",
5604
- )
5605
-
5606
5691
  with st.expander("Publicação através do Upload-Post", expanded=False):
5607
5692
  upload_post_enabled = st.checkbox("Activar Upload-Post", bool(settings.get("upload_post_enabled", False)))
5608
5693
  upload_post_api_key = text_setting("Upload-Post API key", "upload_post_api_key", secret=True)
@@ -5651,7 +5736,6 @@ def render_settings():
5651
5736
  "elevenlabs_api_key": elevenlabs_api_key, "elevenlabs_model_id": elevenlabs_model_id,
5652
5737
  "chatterbox_base_url": chatterbox_base_url, "chatterbox_api_key": chatterbox_api_key, "chatterbox_model_id": chatterbox_model_id,
5653
5738
  "sonilo_api_key": sonilo_api_key, "sonilo_base_url": sonilo_base_url, "suno_api_key": suno_api_key, "suno_api_base_url": suno_api_base_url, "suno_api_endpoint": suno_api_endpoint,
5654
- "tiktok_client_key": tiktok_client_key, "tiktok_client_secret": tiktok_client_secret,
5655
5739
  "upload_post_enabled": upload_post_enabled, "upload_post_api_key": upload_post_api_key,
5656
5740
  "upload_post_username": upload_post_username, "upload_post_platforms": upload_post_platforms,
5657
5741
  "upload_post_auto_upload": upload_post_auto_upload,
@@ -5671,6 +5755,9 @@ def render_settings():
5671
5755
  with google_accounts_tab:
5672
5756
  render_google_accounts()
5673
5757
 
5758
+ with tiktok_api_tab:
5759
+ render_tiktok_api_cards(settings)
5760
+
5674
5761
  with material_sources_tab:
5675
5762
  render_material_source_api_keys(settings)
5676
5763
 
@@ -114,7 +114,7 @@ _CORE_UI_TEXT_KEYS = (
114
114
  "Início", "Niche Finder", "Pipeline", "Pipeline TikTok", "Automação", "Edição", "AI Influencers", "Configurações",
115
115
  "Criação de Vídeos", "Criação de Músicas", "Roteiros", "Upload", "Prompts Master", "Contas TikTok", "Automação Youtube",
116
116
  "Niche Finder Kaggle", "Niche Finder Apify", "Limpador de Metadados", "Cortes", "Editor Python", "Download Mídia",
117
- "Personagens", "Redes Sociais", "Tutorial Meta", "Tutorial Supabase", "Canais Youtube", "Blueprints Youtube", "MCP", "Contas Google",
117
+ "Personagens", "Redes Sociais", "Tutorial Meta", "Tutorial Supabase", "Canais Youtube", "Blueprints Youtube", "MCP", "Contas Google", "API Tiktok",
118
118
  "Configuração API", "Notificações", "Interface local para operação e automação de conteúdo faceless", "Canais", "activos",
119
119
  "Tarefas", "total registado", "A fazer", "na pipeline", "Em execução", "a decorrer", "Concluídos", "artefactos prontos",
120
120
  "Falhas", "requerem atenção", "Filas locais e dependências da cascata", "Niche", "Blueprints", "Brand", "Script", "Title",
@@ -153,6 +153,9 @@ UI_TRANSLATIONS: dict[str, dict[str, str]] = {
153
153
  },
154
154
  }
155
155
 
156
+ for _translation in UI_TRANSLATIONS.values():
157
+ _translation.setdefault("API Tiktok", "API Tiktok")
158
+
156
159
  # Labels for the independent LLM provider cards.
157
160
  _LLM_TRANSLATIONS: dict[str, dict[str, str]] = {
158
161
  "pt": {
@@ -287,6 +287,7 @@ DEFAULTS: dict[str, Any] = {
287
287
  "postiz_auto_publish": False,
288
288
  "tiktok_client_key": "",
289
289
  "tiktok_client_secret": "",
290
+ "tiktok_api_cards": [],
290
291
  "tiktok_accounts": [],
291
292
  "tiktok_redirect_uri": "http://localhost:3030/oauth/tiktok/callback",
292
293
  "tiktok_scopes": "user.info.basic,video.publish,video.upload",
@@ -528,8 +528,18 @@ class YouTubeAdapter:
528
528
 
529
529
  class TikTokAdapter:
530
530
  def __init__(self, settings: dict[str, Any]):
531
- self.client_key = settings.get("tiktok_client_key", "")
532
- self.client_secret = settings.get("tiktok_client_secret", "")
531
+ cards = settings.get("tiktok_api_cards") if isinstance(settings.get("tiktok_api_cards"), list) else []
532
+ selected = next(
533
+ (
534
+ card for card in cards
535
+ if isinstance(card, dict)
536
+ and str(card.get("client_id") or card.get("client_key") or "").strip()
537
+ and str(card.get("client_secret") or "").strip()
538
+ ),
539
+ {},
540
+ )
541
+ self.client_key = str(selected.get("client_id") or selected.get("client_key") or settings.get("tiktok_client_key", "")).strip()
542
+ self.client_secret = str(selected.get("client_secret") or settings.get("tiktok_client_secret", "")).strip()
533
543
  # Redirect URI, scopes, OAuth e access token são geridos no TikTok for Developers Playground.
534
544
  # A UI guarda apenas as credenciais da aplicação.
535
545
  self.access_token = os.getenv("TIKTOK_ACCESS_TOKEN", "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.3.66",
3
+ "version": "0.3.67",
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",