@danhachuel/thunderbolt 0.4.12 → 0.4.14

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 CHANGED
@@ -1280,8 +1280,30 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
1280
1280
 
1281
1281
 
1282
1282
  def render_home_update_controls() -> None:
1283
- """Render the update controls only on the home page and keep notices dismissible."""
1284
- update_area, version_area, _ = st.columns([1.45, 2.55, 3.0])
1283
+ """Render update controls and only show actionable or execution-related notices."""
1284
+ cache_key = "home_update_version_check"
1285
+ checked_at_key = "home_update_version_checked_at"
1286
+ if not st.session_state.get(cache_key) or time.monotonic() - float(st.session_state.get(checked_at_key, 0)) > 300:
1287
+ st.session_state[cache_key] = check_version(APP_VERSION)
1288
+ st.session_state[checked_at_key] = time.monotonic()
1289
+ version_status = st.session_state[cache_key]
1290
+ notice_signature = f"{version_status.latest_version}|{version_status.update_available}|{version_status.error}"
1291
+ if st.session_state.get("home_update_notice_signature") != notice_signature:
1292
+ st.session_state["home_update_notice_signature"] = notice_signature
1293
+ st.session_state["home_update_notice_dismissed"] = False
1294
+
1295
+ update_result = st.session_state.get("home_update_result")
1296
+ notice_message = ""
1297
+ notice_kind = ""
1298
+ if update_result is not None and (not update_result.ok or update_result.restart_required):
1299
+ notice_message = str(update_result.message or "").strip()
1300
+ notice_kind = "success" if update_result.ok else "error"
1301
+ elif version_status.update_available:
1302
+ notice_message = f"Nova versão disponível: {display_version(version_status.latest_version)}. A versão actual é {APP_VERSION_LABEL or 'desconhecida'}."
1303
+ notice_kind = "info"
1304
+ notice_visible = bool(notice_message) and not st.session_state.get("home_update_notice_dismissed")
1305
+
1306
+ update_area, notice_area, close_area = st.columns([1.45, 3.55, 0.42], gap="small")
1285
1307
  with update_area:
1286
1308
  st.markdown(
1287
1309
  """
@@ -1297,6 +1319,18 @@ def render_home_update_controls() -> None:
1297
1319
  border-color: #c4b5fd;
1298
1320
  filter: brightness(1.08);
1299
1321
  }
1322
+ div[data-testid="stAlert"] {
1323
+ width: fit-content;
1324
+ max-width: 100%;
1325
+ min-height: 0;
1326
+ padding: 0.45rem 0.75rem;
1327
+ margin: 0.2rem 0 0;
1328
+ display: inline-flex;
1329
+ align-items: center;
1330
+ }
1331
+ div[data-testid="stAlert"] p {
1332
+ margin: 0;
1333
+ }
1300
1334
  </style>
1301
1335
  """,
1302
1336
  unsafe_allow_html=True,
@@ -1310,49 +1344,16 @@ def render_home_update_controls() -> None:
1310
1344
  st.success("Actualização concluída. A reiniciar o Thunderbolt para aplicar a nova versão…")
1311
1345
  time.sleep(0.8)
1312
1346
  restart_current_process()
1313
- with version_area:
1314
- cache_key = "home_update_version_check"
1315
- checked_at_key = "home_update_version_checked_at"
1316
- if not st.session_state.get(cache_key) or time.monotonic() - float(st.session_state.get(checked_at_key, 0)) > 300:
1317
- st.session_state[cache_key] = check_version(APP_VERSION)
1318
- st.session_state[checked_at_key] = time.monotonic()
1319
- version_status = st.session_state[cache_key]
1320
- notice_signature = f"{version_status.latest_version}|{version_status.update_available}|{version_status.error}"
1321
- if st.session_state.get("home_update_notice_signature") != notice_signature:
1322
- st.session_state["home_update_notice_signature"] = notice_signature
1323
- st.session_state["home_update_notice_dismissed"] = False
1324
- if version_status.update_available:
1325
- notice_message = f"Nova versão disponível: {display_version(version_status.latest_version)}. A versão actual é {APP_VERSION_LABEL or 'desconhecida'}."
1326
- notice_kind = "info"
1327
- elif version_status.error:
1328
- notice_message = f"Versão actual: {APP_VERSION_LABEL or 'desconhecida'} · verificação de actualização indisponível."
1329
- notice_kind = "caption"
1330
- else:
1331
- notice_message = f"Versão actual: {APP_VERSION_LABEL or 'desconhecida'} · já está actualizada ({display_version(version_status.latest_version)})."
1332
- notice_kind = "success"
1333
- if not st.session_state.get("home_update_notice_dismissed"):
1334
- notice_col, close_col = st.columns([8.5, 1])
1335
- with notice_col:
1336
- if notice_kind == "info":
1337
- st.info(notice_message)
1338
- elif notice_kind == "success":
1339
- st.success(notice_message)
1340
- else:
1341
- st.caption(notice_message)
1342
- with close_col:
1343
- if st.button("×", key="home_update_notice_close", help="Fechar este aviso"):
1344
- st.session_state["home_update_notice_dismissed"] = True
1345
- st.rerun()
1346
- update_result = st.session_state.get("home_update_result")
1347
- if update_result is not None and not st.session_state.get("home_update_notice_dismissed"):
1348
- result_col, result_close_col = st.columns([8.5, 1])
1349
- with result_col:
1350
- if update_result.ok:
1351
- st.success(update_result.message)
1347
+ if notice_visible:
1348
+ with notice_area:
1349
+ if notice_kind == "info":
1350
+ st.info(notice_message)
1351
+ elif notice_kind == "success":
1352
+ st.success(notice_message)
1352
1353
  else:
1353
- st.error(update_result.message)
1354
- with result_close_col:
1355
- if st.button("×", key="home_update_result_close", help="Fechar este resultado"):
1354
+ st.error(notice_message)
1355
+ with close_area:
1356
+ if st.button("×", key="home_update_notice_close", help="Fechar este aviso"):
1356
1357
  st.session_state["home_update_notice_dismissed"] = True
1357
1358
  st.rerun()
1358
1359
 
@@ -6752,6 +6753,22 @@ def render_supabase_tutorial():
6752
6753
  st.markdown(tutorial_content, unsafe_allow_html=True)
6753
6754
 
6754
6755
 
6756
+ def render_google_oauth_tutorial():
6757
+ """Render the Google OAuth setup guide supplied for Thunderbolt."""
6758
+ tutorial_path = ROOT / "seed" / "references" / "tutorial-oauth-google.md"
6759
+ st.title("Tutorial OAuth do Google")
6760
+ st.caption("Guia completo para configurar a autenticação OAuth do Google e o acesso à YouTube Data API v3.")
6761
+ st.markdown("[Abrir Google Cloud Console](https://console.cloud.google.com/)")
6762
+ try:
6763
+ tutorial_content = tutorial_path.read_text(encoding="utf-8").strip()
6764
+ except OSError:
6765
+ tutorial_content = ""
6766
+ if not tutorial_content:
6767
+ st.error("O conteúdo local do tutorial não está disponível. Consulte a documentação OAuth do Google.")
6768
+ return
6769
+ st.markdown(tutorial_content, unsafe_allow_html=False)
6770
+
6771
+
6755
6772
  def render_youtube_frontend_upload_tutorial():
6756
6773
  """Render the safe operational guide for the YouTube direct-upload workflow."""
6757
6774
  tutorial_path = ROOT / "seed" / "references" / "youtube-video-upload-frontend.md"
@@ -7076,6 +7093,7 @@ def main():
7076
7093
  ("Tutorial Kaggle", ":material/menu_book:", "Tutorial Kaggle"),
7077
7094
  ("Tutorial Apify", ":material/menu_book:", "Tutorial Apify"),
7078
7095
  ("Tutorial YouTube Video-Upload Frontend", ":material/video_library:", "Tutorial YouTube Video-Upload Frontend"),
7096
+ ("Tutorial OAuth do Google", ":material/key:", "Tutorial OAuth do Google"),
7079
7097
  ]
7080
7098
  settings_items = [
7081
7099
  ("MCP", ":material/hub:", "MCP"),
@@ -7130,7 +7148,7 @@ def main():
7130
7148
  "AI Influencers": "/ai-influencers", "Personagens": "/ai-influencers/personagens", "Geração de Conteúdo IA": "/ai-influencers/geracao-conteudo", "Motion Control": "/ai-influencers/motion-control", "UGC Products": "/ai-influencers/ugc-products", "Redes Sociais": "/ai-influencers/redes-sociais",
7131
7149
  "Edição": "/edicao", "Limpador de Metadados": "/edicao/limpador-metadados", "Cortes": "/edicao/cortes", "Editor Python": "/edicao/editor-python", "Download Mídia": "/edicao/download-midia",
7132
7150
  "Growth": "/growth", "Analista Growth Youtube": "/growth/youtube", "Analista Growth Tiktok": "/growth/tiktok", "Analista Growth Instagram": "/growth/instagram", "Analista Facebook Pages": "/growth/facebook-pages", "Analista Bilibili": "/growth/bilibili",
7133
- "Documentação": "/documentacao", "Tutorial Meta": "/documentacao/meta", "Tutorial Supabase": "/documentacao/supabase", "Tutorial Kaggle": "/documentacao/kaggle", "Tutorial Apify": "/documentacao/apify", "Tutorial YouTube Video-Upload Frontend": "/documentacao/youtube-video-upload-frontend",
7151
+ "Documentação": "/documentacao", "Tutorial Meta": "/documentacao/meta", "Tutorial Supabase": "/documentacao/supabase", "Tutorial Kaggle": "/documentacao/kaggle", "Tutorial Apify": "/documentacao/apify", "Tutorial YouTube Video-Upload Frontend": "/documentacao/youtube-video-upload-frontend", "Tutorial OAuth do Google": "/documentacao/oauth-google",
7134
7152
  "Configurações": "/configuracoes", "MCP": "/configuracoes/mcp", "Notificações": "/configuracoes/notificacoes", "Logs": "/configuracoes/logs", "Configuração API": "/configuracoes/api",
7135
7153
  }
7136
7154
 
@@ -7238,6 +7256,7 @@ def main():
7238
7256
  "Tutorial Meta": render_models_ai_tutorial,
7239
7257
  "Tutorial Supabase": render_supabase_tutorial,
7240
7258
  "Tutorial YouTube Video-Upload Frontend": render_youtube_frontend_upload_tutorial,
7259
+ "Tutorial OAuth do Google": render_google_oauth_tutorial,
7241
7260
  "Configurações": lambda: render_edit_placeholder("Configurações", "Seleccione uma opção no menu expansível."),
7242
7261
  "MCP": render_mcp,
7243
7262
  "Contas Google": render_google_accounts,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.12",
3
+ "version": "0.4.14",
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",
@@ -0,0 +1,155 @@
1
+ # Tutorial completo: configurar OAuth do Google para o Thunderbolt
2
+
3
+ Este tutorial explica como criar um projecto no Google Cloud, activar a YouTube Data API v3, configurar a tela de consentimento OAuth, criar as credenciais e ligá-las ao Thunderbolt.
4
+
5
+ ## Pré-requisitos
6
+
7
+ Antes de começar, certifique-se de que tem uma conta Google, acesso ao [Google Cloud Console](https://console.cloud.google.com/) e o Thunderbolt instalado e em execução localmente. A aplicação em execução é necessária para confirmar a URI de redireccionamento OAuth utilizada pelo projecto.
8
+
9
+ ## Passo 1 — Aceder ao Google Cloud Console
10
+
11
+ Abra o [Google Cloud Console](https://console.cloud.google.com/) e faça login com a sua conta Google. Se for a primeira utilização, aceite os Termos de Serviço apresentados.
12
+
13
+ ## Passo 2 — Criar ou seleccionar um projecto
14
+
15
+ No topo da página, clique no selector de projectos, junto a **Google Cloud**. No diálogo apresentado, seleccione **Novo projecto** e preencha os campos seguintes:
16
+
17
+ | Campo | Valor sugerido |
18
+ |---|---|
19
+ | Nome do projecto | `Thunderbolt` |
20
+ | Organização | Deixe em branco ou seleccione a organização predefinida |
21
+
22
+ Clique em **Criar**, aguarde a conclusão e use o selector de projectos para mudar para o projecto criado.
23
+
24
+ ## Passo 3 — Activar a YouTube Data API v3
25
+
26
+ No menu lateral, abra **APIs e Serviços → Biblioteca**. Pesquise por **YouTube Data API v3**, abra o resultado correspondente e clique em **Activar**.
27
+
28
+ Se a **Google+ API** ainda aparecer como opção no seu projecto, a sua activação é opcional e pode ser útil para funcionalidades legadas que dependam dela.
29
+
30
+ ## Passo 4 — Configurar a tela de consentimento OAuth
31
+
32
+ > **Este é um passo obrigatório.** A tela de consentimento é apresentada ao utilizador quando o Thunderbolt solicita autorização para aceder à conta Google/YouTube.
33
+
34
+ No menu lateral, abra **APIs e Serviços → Tela de consentimento OAuth**. Clique em **Começar** ou **Configurar tela de consentimento**, conforme a opção apresentada.
35
+
36
+ Em **Tipo de utilizador**, seleccione **Externo** e clique em **Criar**. Na secção **Informações do app**, preencha:
37
+
38
+ | Campo | Valor sugerido |
39
+ |---|---|
40
+ | Nome do app | `Thunderbolt` ou o nome pretendido |
41
+ | E-mail para suporte do utilizador | O seu e-mail |
42
+ | Logótipo do app | Opcional; pode deixar em branco |
43
+ | Domínio da página inicial | Deixe em branco ou utilize `localhost` para desenvolvimento local |
44
+ | Política de privacidade | Opcional nesta fase |
45
+ | Termos de serviço | Opcional nesta fase |
46
+
47
+ Clique em **Próxima**. Em **Público**, mantenha a opção **Externo** e avance novamente.
48
+
49
+ Em **Escopos**, clique em **Adicionar ou remover escopos**, pesquise por `youtube` e seleccione:
50
+
51
+ | Escopo | Finalidade |
52
+ |---|---|
53
+ | `https://www.googleapis.com/auth/youtube` | Gerir a sua conta do YouTube |
54
+ | `https://www.googleapis.com/auth/youtube.upload` | Gerir os seus vídeos do YouTube e efectuar uploads |
55
+ | `https://www.googleapis.com/auth/youtube.readonly` | Visualizar a sua conta do YouTube; opcional |
56
+
57
+ Clique em **Actualizar** e depois em **Próxima**. Em **Informações de contacto**, indique o seu e-mail para receber notificações do Google e avance.
58
+
59
+ Na página **Concluir**, leia a Política de Dados do Utilizador, marque a caixa de concordância e clique em **Continuar** e, depois, em **Criar**.
60
+
61
+ > **Importante:** depois de criada, a tela de consentimento não pode ser removida, embora possa ser editada posteriormente.
62
+
63
+ ## Passo 5 — Adicionar utilizadores de teste
64
+
65
+ Enquanto a aplicação estiver com o estado **Não verificado**, o Google exige que os utilizadores de teste sejam adicionados manualmente para permitir o login.
66
+
67
+ Na página da tela de consentimento OAuth, abra o separador **Público-alvo** e, na área **Utilizadores de teste**, clique em **Adicionar utilizadores**. Introduza o seu e-mail, clique em **Adicionar** e depois em **Guardar**.
68
+
69
+ ## Passo 6 — Criar as credenciais OAuth
70
+
71
+ No menu lateral, abra **APIs e Serviços → Credenciais** e clique em **+ Criar credenciais → ID do cliente OAuth**. Em **Tipo de aplicativo**, seleccione **Aplicativo da Web** e atribua um nome, por exemplo, `Thunderbolt Web Client`.
72
+
73
+ ### Origens JavaScript autorizadas
74
+
75
+ Clique em **Adicionar URI** e introduza a origem onde o frontend é executado, por exemplo:
76
+
77
+ ```text
78
+ http://localhost:3000
79
+ ```
80
+
81
+ Se existir um ambiente de produção, adicione também a origem HTTPS correspondente, por exemplo `https://seudominio.com`.
82
+
83
+ ### URIs de redireccionamento autorizadas
84
+
85
+ Esta é a parte mais crítica. Clique em **Adicionar URI** e introduza a URI exacta utilizada pelo Thunderbolt para o callback OAuth. Exemplos de desenvolvimento local:
86
+
87
+ ```text
88
+ http://localhost:3000/api/auth/google/callback
89
+ http://localhost:5173/oauth2callback
90
+ ```
91
+
92
+ > **A URI tem de ser exactamente igual à configurada no código do Thunderbolt.** Uma barra final diferente, o uso de `http` em vez de `https` ou uma porta diferente causa o erro `redirect_uri_mismatch`.
93
+
94
+ Se não souber qual é a URI utilizada, consulte a configuração ou o ficheiro de ambiente do Thunderbolt e confirme também os logs da aplicação.
95
+
96
+ Clique em **Criar**. Na janela apresentada, copie o **ID do cliente (Client ID)** e a **Chave secreta do cliente (Client Secret)** e guarde-os num local seguro.
97
+
98
+ > **Nunca partilhe o Client Secret nem o envie para o controlo de versão Git.**
99
+
100
+ ## Passo 7 — Configurar o Thunderbolt
101
+
102
+ O projecto utiliza variáveis de ambiente para as credenciais OAuth. Localize o ficheiro de ambiente do Thunderbolt, normalmente `.env`, `.env.local` ou o ficheiro de configuração equivalente, e adicione:
103
+
104
+ ```env
105
+ GOOGLE_CLIENT_ID=seu_client_id_aqui
106
+ GOOGLE_CLIENT_SECRET=sua_client_secret_aqui
107
+ GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/google/callback
108
+ ```
109
+
110
+ Substitua `seu_client_id_aqui` e `sua_client_secret_aqui` pelos valores copiados no passo anterior. Confirme que `GOOGLE_REDIRECT_URI` é exactamente igual à URI registada no Google Cloud Console.
111
+
112
+ ## Passo 8 — Testar a autenticação
113
+
114
+ Reinicie o servidor do Thunderbolt para carregar as novas variáveis de ambiente. Abra a interface e tente iniciar sessão com o Google.
115
+
116
+ O fluxo esperado é:
117
+
118
+ 1. O Google apresenta a tela de consentimento com o nome do app e os escopos solicitados.
119
+ 2. O utilizador autoriza o acesso.
120
+ 3. O Google redirecciona para a URI de callback do Thunderbolt.
121
+ 4. A autenticação é concluída com sucesso na aplicação.
122
+
123
+ ## Solução de problemas
124
+
125
+ ### Erro `redirect_uri_mismatch`
126
+
127
+ Este erro significa que a URI enviada pelo Thunderbolt não corresponde exactamente à URI registada no Google Cloud Console.
128
+
129
+ Verifique a URI utilizada nos logs ou no código do Thunderbolt. Depois, abra **Credenciais**, seleccione o ID do cliente e adicione a URI exacta em **URIs de redireccionamento autorizadas**. Aguarde um a dois minutos para a configuração se propagar, limpe o cache do navegador e tente novamente.
130
+
131
+ ### Erro `invalid_request` ou `access_denied`
132
+
133
+ Confirme se o seu e-mail foi adicionado como utilizador de teste no passo 5. Verifique também se os escopos necessários estão activos, especialmente `youtube.upload`.
134
+
135
+ ### Mensagem “Acesso bloqueado: solicitação inválida”
136
+
137
+ Na maioria dos casos, esta mensagem está relacionada com uma URI de redireccionamento incorrecta. Reveja cuidadosamente os passos 6 e 7, verificando protocolo, domínio, porta, caminho e barras finais.
138
+
139
+ ## Escopos importantes para o YouTube
140
+
141
+ Para efectuar uploads de vídeos, o Thunderbolt utiliza principalmente os seguintes escopos OAuth:
142
+
143
+ | Escopo | Descrição |
144
+ |---|---|
145
+ | `https://www.googleapis.com/auth/youtube` | Gerir a sua conta do YouTube |
146
+ | `https://www.googleapis.com/auth/youtube.upload` | Gerir os seus vídeos do YouTube e efectuar uploads |
147
+ | `https://www.googleapis.com/auth/youtube.readonly` | Visualizar a sua conta do YouTube; opcional |
148
+
149
+ > **Boa prática de segurança:** mantenha o Client Secret fora do código, dos ficheiros versionados e de mensagens públicas. Use variáveis de ambiente e restrinja o acesso às credenciais.
150
+
151
+ ## Referências
152
+
153
+ - [Google Cloud Console](https://console.cloud.google.com/)
154
+ - [YouTube Data API v3](https://developers.google.com/youtube/v3)
155
+ - [Documentação OAuth 2.0 do Google](https://developers.google.com/identity/protocols/oauth2)