@danhachuel/thunderbolt 0.7.13 → 0.7.15
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 +52 -16
- package/integrations/tiktok_public.py +1 -1
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -2419,7 +2419,7 @@ def render_tiktok_channels():
|
|
|
2419
2419
|
for channel in visible_tiktok_channels:
|
|
2420
2420
|
channel_id = str(channel.get("id") or "")
|
|
2421
2421
|
with st.container(border=True):
|
|
2422
|
-
card_cols = st.columns([0.7,
|
|
2422
|
+
card_cols = st.columns([0.7, 2.55, 1.15, 1.05, 1.05, 1.25, 1.35], gap="small")
|
|
2423
2423
|
with card_cols[0]:
|
|
2424
2424
|
avatar_url = _tiktok_avatar_url(channel)
|
|
2425
2425
|
if avatar_url:
|
|
@@ -2432,12 +2432,16 @@ def render_tiktok_channels():
|
|
|
2432
2432
|
st.caption(channel.get("description") or "Perfil carregado da página pública do TikTok.")
|
|
2433
2433
|
st.caption(f"Nicho: {channel.get('niche') or 'não configurado'}")
|
|
2434
2434
|
with card_cols[2]:
|
|
2435
|
-
|
|
2435
|
+
tiktok_url = _tiktok_channel_url(channel)
|
|
2436
|
+
if tiktok_url:
|
|
2437
|
+
st.link_button("Abrir canal", tiktok_url, type="primary", width="content")
|
|
2436
2438
|
with card_cols[3]:
|
|
2437
|
-
st.metric("
|
|
2439
|
+
st.metric("Seguidores", format_metric_number(channel.get("subscriber_count")))
|
|
2438
2440
|
with card_cols[4]:
|
|
2439
|
-
st.metric("
|
|
2441
|
+
st.metric("Curtidas", format_metric_number(channel.get("likes_count")))
|
|
2440
2442
|
with card_cols[5]:
|
|
2443
|
+
st.metric("Vídeos", format_metric_number(channel.get("video_count")))
|
|
2444
|
+
with card_cols[6]:
|
|
2441
2445
|
if st.button("↻", key=f"refresh_tiktok_metrics_{channel_id}", help="Actualizar Seguidores, Vídeos e Curtidas", width="stretch"):
|
|
2442
2446
|
with st.spinner("A actualizar métricas TikTok…"):
|
|
2443
2447
|
refreshed, message = _refresh_tiktok_channel_metrics(channel)
|
|
@@ -2651,20 +2655,37 @@ def _refresh_youtube_channel_metrics(channel: dict[str, Any], youtube: YouTubeAd
|
|
|
2651
2655
|
|
|
2652
2656
|
|
|
2653
2657
|
def _refresh_tiktok_channel_metrics(channel: dict[str, Any]) -> tuple[bool, str]:
|
|
2654
|
-
|
|
2658
|
+
channel_ref = str(channel.get("url") or channel.get("handle") or "").strip()
|
|
2659
|
+
if not channel_ref:
|
|
2660
|
+
return False, "Este canal não tem URL ou handle TikTok para actualizar as métricas."
|
|
2661
|
+
try:
|
|
2662
|
+
result = fetch_public_tiktok_profile(channel_ref)
|
|
2663
|
+
except Exception as exc:
|
|
2664
|
+
return False, f"Não foi possível actualizar as métricas TikTok ({type(exc).__name__}). Tente novamente."
|
|
2655
2665
|
if not result.ok or not isinstance(result.data, dict):
|
|
2656
2666
|
return False, result.message
|
|
2657
2667
|
data = result.data
|
|
2658
|
-
|
|
2659
|
-
"subscriber_count": data.get("subscriber_count"),
|
|
2660
|
-
"likes_count": data.get("likes_count"),
|
|
2661
|
-
"video_count": data.get("video_count"),
|
|
2668
|
+
updates = {
|
|
2662
2669
|
"metrics_source": data.get("metrics_source", "tiktok_public_page"),
|
|
2663
2670
|
"last_public_lookup_at": data.get("last_public_lookup_at", now()),
|
|
2664
|
-
}
|
|
2671
|
+
}
|
|
2672
|
+
for field in ("subscriber_count", "likes_count", "video_count", "view_count", "name", "handle", "url", "avatar_url", "thumbnail_url"):
|
|
2673
|
+
if data.get(field) is not None and data.get(field) != "":
|
|
2674
|
+
updates[field] = data[field]
|
|
2675
|
+
update_channel(str(channel["id"]), updates)
|
|
2665
2676
|
return True, "Métricas TikTok actualizadas."
|
|
2666
2677
|
|
|
2667
2678
|
|
|
2679
|
+
def _tiktok_channel_url(channel: dict[str, Any]) -> str:
|
|
2680
|
+
source = str(channel.get("url") or channel.get("handle") or channel.get("username") or "").strip()
|
|
2681
|
+
if not source:
|
|
2682
|
+
return ""
|
|
2683
|
+
try:
|
|
2684
|
+
return str(normalize_tiktok_reference(source).get("url") or "").strip()
|
|
2685
|
+
except ValueError:
|
|
2686
|
+
return source if source.startswith(("http://", "https://")) else ""
|
|
2687
|
+
|
|
2688
|
+
|
|
2668
2689
|
def render_channels():
|
|
2669
2690
|
st.title("Canais Youtube")
|
|
2670
2691
|
st.caption("Escolha entre importar dados públicos do YouTube ou preencher o canal manualmente.")
|
|
@@ -3096,7 +3117,7 @@ def render_channels():
|
|
|
3096
3117
|
channel_id = str(channel["id"])
|
|
3097
3118
|
edit_key = f"edit_channel_{channel_id}"
|
|
3098
3119
|
with st.container(border=True):
|
|
3099
|
-
header_cols = st.columns([0.7, 2.75, 1.1, 1.0, 1.35, 1.35])
|
|
3120
|
+
header_cols = st.columns([0.7, 2.75, 1.15, 1.1, 1.0, 1.35, 1.35])
|
|
3100
3121
|
channel_url = str(channel.get("url") or "").strip()
|
|
3101
3122
|
if not channel_url:
|
|
3102
3123
|
channel_id_or_handle = str(channel.get("youtube_channel_id") or channel.get("youtube_id") or channel.get("handle") or "").strip()
|
|
@@ -3119,14 +3140,15 @@ def render_channels():
|
|
|
3119
3140
|
st.caption(channel_niche_label(channel))
|
|
3120
3141
|
st.caption(f"{channel.get('handle') or channel.get('url') or 'sem URL'} · {channel.get('metrics_source', 'manual')}")
|
|
3121
3142
|
with header_cols[2]:
|
|
3122
|
-
|
|
3143
|
+
if channel_url:
|
|
3144
|
+
st.link_button("Abrir canal", channel_url, type="primary", width="content")
|
|
3123
3145
|
with header_cols[3]:
|
|
3124
|
-
st.metric("
|
|
3146
|
+
st.metric("Inscritos", _format_channel_count(channel.get("subscriber_count")))
|
|
3125
3147
|
with header_cols[4]:
|
|
3126
|
-
st.metric("
|
|
3148
|
+
st.metric("Vídeos", _format_channel_count(channel.get("video_count")))
|
|
3127
3149
|
with header_cols[5]:
|
|
3128
|
-
|
|
3129
|
-
|
|
3150
|
+
st.metric("Visualizações", _format_channel_count(channel.get("view_count")))
|
|
3151
|
+
with header_cols[6]:
|
|
3130
3152
|
if st.button("↻", key=f"refresh_youtube_metrics_{channel_id}", help="Actualizar Inscritos, Vídeos e Visualizações", width="stretch"):
|
|
3131
3153
|
with st.spinner("A actualizar métricas YouTube…"):
|
|
3132
3154
|
refreshed, message = _refresh_youtube_channel_metrics(channel, youtube)
|
|
@@ -5589,6 +5611,11 @@ def _render_tiktok_automation_cards():
|
|
|
5589
5611
|
st.subheader("Vídeos cadastrados TikTok")
|
|
5590
5612
|
st.caption("Esta fila mostra exclusivamente tarefas associadas a canais TikTok.")
|
|
5591
5613
|
tiktok_tasks = load_automation_tasks_for_platform("tiktok")
|
|
5614
|
+
tiktok_channels_by_id = {
|
|
5615
|
+
str(channel.get("id")): channel
|
|
5616
|
+
for channel in read_json("channels.json", [])
|
|
5617
|
+
if isinstance(channel, dict) and classify_channel_platform(channel) == "tiktok"
|
|
5618
|
+
}
|
|
5592
5619
|
if not tiktok_tasks:
|
|
5593
5620
|
st.info("Ainda não existem vídeos TikTok cadastrados.")
|
|
5594
5621
|
for task in tiktok_tasks:
|
|
@@ -5607,6 +5634,15 @@ def _render_tiktok_automation_cards():
|
|
|
5607
5634
|
st.caption("Thumbnail ainda não pronta")
|
|
5608
5635
|
st.write(f"**{task.get('title') or task.get('topic') or 'Vídeo TikTok'}**")
|
|
5609
5636
|
st.caption(f"{task.get('channel_name') or 'Canal TikTok'} · {task_id}")
|
|
5637
|
+
task_channel = tiktok_channels_by_id.get(str(task.get("channel_id") or ""))
|
|
5638
|
+
if task_channel is None:
|
|
5639
|
+
task_channel = next(
|
|
5640
|
+
(channel for channel in tiktok_channels_by_id.values() if str(channel.get("name") or "").strip() == str(task.get("channel_name") or "").strip()),
|
|
5641
|
+
None,
|
|
5642
|
+
)
|
|
5643
|
+
task_channel_url = _tiktok_channel_url(task_channel or {})
|
|
5644
|
+
if task_channel_url:
|
|
5645
|
+
st.link_button("Abrir canal", task_channel_url, type="primary", width="content")
|
|
5610
5646
|
thumbnail_download_col, prompt_download_col = st.columns(2, gap="small")
|
|
5611
5647
|
with thumbnail_download_col:
|
|
5612
5648
|
st.download_button(
|
|
@@ -212,7 +212,7 @@ def fetch_public_tiktok_profile(source: str) -> IntegrationResult:
|
|
|
212
212
|
"Accept": "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8",
|
|
213
213
|
}
|
|
214
214
|
try:
|
|
215
|
-
response = requests.get(reference["url"], headers=headers, timeout=
|
|
215
|
+
response = requests.get(reference["url"], headers=headers, timeout=8, allow_redirects=True)
|
|
216
216
|
except requests.RequestException as exc:
|
|
217
217
|
return IntegrationResult(False, f"Não foi possível consultar o perfil público do TikTok: {exc}", reference)
|
|
218
218
|
if response.status_code in {401, 403, 429}:
|
package/package.json
CHANGED