@danhachuel/thunderbolt 0.4.46 → 0.4.48

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.
Files changed (2) hide show
  1. package/app/main.py +61 -12
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -1858,6 +1858,16 @@ def _tiktok_channel_records() -> list[dict[str, Any]]:
1858
1858
  return [channel for channel in read_json("channels.json", []) if isinstance(channel, dict) and channel.get("platform") == "tiktok"]
1859
1859
 
1860
1860
 
1861
+ def _tiktok_avatar_url(value: dict[str, Any]) -> str:
1862
+ for key in ("avatar_url", "thumbnail_url", "profile_image_url", "avatar", "image_url"):
1863
+ candidate = value.get(key)
1864
+ if isinstance(candidate, dict):
1865
+ candidate = candidate.get("url") or candidate.get("src")
1866
+ if str(candidate or "").strip():
1867
+ return str(candidate).strip()
1868
+ return ""
1869
+
1870
+
1861
1871
  def _tiktok_prompt_options() -> tuple[list[str], dict[str, str]]:
1862
1872
  files = list_prompt_master_files()
1863
1873
  labels = {path.name: get_display_name("prompt_masters", path, path.stem) for path in files}
@@ -1873,6 +1883,40 @@ def render_tiktok_channels():
1873
1883
  prompt_ids, prompt_labels = _tiktok_prompt_options()
1874
1884
  with import_tab:
1875
1885
  st.caption("A pesquisa consulta exclusivamente a página pública do TikTok. Não existe método alternativo nem utilização de API.")
1886
+ st.subheader(f"Canais TikTok cadastrados ({len(channels)})")
1887
+ if channels:
1888
+ for channel in channels:
1889
+ channel_id = str(channel.get("id") or "")
1890
+ with st.container(border=True):
1891
+ card_cols = st.columns([0.7, 2.3, 1.0, 1.0, 1.35, 1.25])
1892
+ with card_cols[0]:
1893
+ avatar_url = _tiktok_avatar_url(channel)
1894
+ if avatar_url:
1895
+ st.image(avatar_url, width=58)
1896
+ else:
1897
+ st.markdown("### TT")
1898
+ with card_cols[1]:
1899
+ st.markdown(f"**{channel.get('name') or 'Perfil TikTok'}**")
1900
+ st.caption(f"{channel.get('handle') or 'sem handle'} · {channel.get('url') or 'sem URL'}")
1901
+ st.caption(channel.get("description") or "Perfil carregado da página pública do TikTok.")
1902
+ with card_cols[2]:
1903
+ st.metric("Seguidores", format_metric_number(channel.get("subscriber_count")))
1904
+ with card_cols[3]:
1905
+ st.metric("Curtidas", format_metric_number(channel.get("likes_count")))
1906
+ with card_cols[4]:
1907
+ current_prompt = str(channel.get("default_prompt_master") or channel.get("prompt_master") or "")
1908
+ selected_prompt = st.selectbox("Prompt Master", prompt_ids, index=prompt_ids.index(current_prompt) if current_prompt in prompt_ids else 0, format_func=lambda item: prompt_labels.get(item, item), key=f"tiktok_import_card_prompt_{channel_id}")
1909
+ with card_cols[5]:
1910
+ st.caption("Formato")
1911
+ st.write("Portrait 9:16")
1912
+ if st.button("Guardar", key=f"tiktok_import_card_save_{channel_id}", use_container_width=True, type="primary"):
1913
+ update_channel(channel_id, {"platform": "tiktok", "default_prompt_master": selected_prompt, "prompt_master": selected_prompt, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait"})
1914
+ st.success("Prompt Master do canal guardado.")
1915
+ st.rerun()
1916
+ else:
1917
+ st.info("Ainda não existem canais TikTok cadastrados. Use o campo abaixo para pesquisar e cadastrar um perfil público.")
1918
+ st.divider()
1919
+ st.subheader("Importar novo canal pela página pública")
1876
1920
  source = st.text_input("URL pública ou @handle", placeholder="https://www.tiktok.com/@conta", key="tiktok_channel_source")
1877
1921
  lookup_cols = st.columns([1, 1])
1878
1922
  with lookup_cols[0]:
@@ -1893,7 +1937,7 @@ def render_tiktok_channels():
1893
1937
  with st.container(border=True):
1894
1938
  profile_cols = st.columns([0.8, 2.2, 1, 1, 1])
1895
1939
  with profile_cols[0]:
1896
- avatar = imported.get("avatar_url") or imported.get("avatar")
1940
+ avatar = _tiktok_avatar_url(imported)
1897
1941
  if avatar:
1898
1942
  st.image(avatar, width=82)
1899
1943
  else:
@@ -1932,7 +1976,8 @@ def render_tiktok_channels():
1932
1976
  description = st.text_area("Descrição", value=str(imported.get("bio") or ""), key="tiktok_import_description")
1933
1977
  if st.form_submit_button("Cadastrar canal TikTok", type="primary"):
1934
1978
  channel = create_channel(name.strip(), url.strip(), {"platform": "tiktok", "handle": handle.strip(), "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16", "metrics_source": "tiktok_public_page", "subscriber_count": imported.get("subscriber_count"), "video_count": imported.get("video_count")})
1935
- update_channel(channel["id"], {"platform": "tiktok", "default_prompt_master": prompt, "prompt_master": prompt, "avatar_url": imported.get("avatar_url") or imported.get("avatar") or "", "thumbnail_url": imported.get("avatar_url") or imported.get("avatar") or ""})
1979
+ avatar_url = _tiktok_avatar_url(imported)
1980
+ update_channel(channel["id"], {"platform": "tiktok", "default_prompt_master": prompt, "prompt_master": prompt, "avatar_url": avatar_url, "thumbnail_url": avatar_url})
1936
1981
  st.session_state.pop("tiktok_channel_import", None)
1937
1982
  st.success(f"Canal {channel['name']} cadastrado.")
1938
1983
  st.rerun()
@@ -1953,6 +1998,17 @@ def render_tiktok_channels():
1953
1998
  st.success(f"{created} canal(is) TikTok cadastrado(s).")
1954
1999
  st.rerun()
1955
2000
  st.caption("A planilha usa os campos de URL, nome, handle, idioma, nicho, Prompt Master, descrição e horário.")
2001
+ st.divider()
2002
+ st.subheader(f"Canais TikTok cadastrados ({len(channels)})")
2003
+ if channels:
2004
+ st.dataframe(
2005
+ [{k: c.get(v, "") for k, v in {"URL canal": "url", "Nome canal": "name", "Handle canal": "handle", "Idioma": "language", "Nicho": "niche", "Prompt Master": "default_prompt_master", "Proporção": "video_aspect_ratio", "Activo": "active", "Descrição": "description"}.items()} for c in channels],
2006
+ use_container_width=True,
2007
+ hide_index=True,
2008
+ height=360,
2009
+ )
2010
+ else:
2011
+ st.info("Ainda não existem canais TikTok cadastrados.")
1956
2012
  with manual_tab:
1957
2013
  with st.form("tiktok_channel_manual_form"):
1958
2014
  url = st.text_input("URL canal", placeholder="https://www.tiktok.com/@conta", key="tiktok_manual_channel_url")
@@ -1968,14 +2024,6 @@ def render_tiktok_channels():
1968
2024
  update_channel(channel["id"], {"platform": "tiktok"})
1969
2025
  st.success("Canal TikTok cadastrado.")
1970
2026
  st.rerun()
1971
- st.divider()
1972
- st.subheader(f"Canais TikTok cadastrados ({len(channels)})")
1973
- if channels:
1974
- st.dataframe([{k: c.get(v, "") for k, v in {"URL canal": "url", "Nome canal": "name", "Handle canal": "handle", "Idioma": "language", "Nicho": "niche", "Prompt Master": "default_prompt_master", "Proporção": "video_aspect_ratio", "Activo": "active", "Descrição": "description"}.items()} for c in channels], use_container_width=True, hide_index=True, height=360)
1975
- else:
1976
- st.info("Ainda não existem canais TikTok cadastrados.")
1977
-
1978
-
1979
2027
  def render_channels():
1980
2028
  st.title("Canais Youtube")
1981
2029
  st.caption("Escolha entre importar dados públicos do YouTube ou preencher o canal manualmente.")
@@ -4607,7 +4655,7 @@ def render_tiktok_automation():
4607
4655
  with st.container(border=True):
4608
4656
  header_cols = st.columns([0.55, 2.35, 1.35, 1.5, 1.35])
4609
4657
  with header_cols[0]:
4610
- profile_image = channel.get("avatar_url") or channel.get("thumbnail_url")
4658
+ profile_image = _tiktok_avatar_url(channel)
4611
4659
  if profile_image:
4612
4660
  st.image(profile_image, width=48)
4613
4661
  else:
@@ -4639,7 +4687,8 @@ def render_tiktok_automation():
4639
4687
  if not valid_hhmm(schedule_time):
4640
4688
  st.error("Use o formato HH:MM, por exemplo 08:30.")
4641
4689
  else:
4642
- update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "video_aspect_ratio": "Portrait 9:16"})
4690
+ avatar_url = _tiktok_avatar_url(channel)
4691
+ update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
4643
4692
  st.success("Automação TikTok guardada.")
4644
4693
  st.rerun()
4645
4694
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.46",
3
+ "version": "0.4.48",
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",