@danhachuel/thunderbolt 0.7.6 → 0.7.8
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 +22 -6
- package/integrations/platforms.py +13 -2
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -2625,17 +2625,19 @@ def _channel_average_video_time(channel: dict[str, Any], *, prompt_master: str =
|
|
|
2625
2625
|
|
|
2626
2626
|
|
|
2627
2627
|
def _refresh_youtube_channel_metrics(channel: dict[str, Any], youtube: YouTubeAdapter) -> tuple[bool, str]:
|
|
2628
|
-
|
|
2628
|
+
channel_ref = str(channel.get("youtube_channel_id") or channel.get("youtube_id") or channel.get("url") or channel.get("handle") or "")
|
|
2629
|
+
result = youtube.fetch_channel(channel_ref) if youtube.api_key else youtube.fetch_channel_public(channel_ref)
|
|
2629
2630
|
if not result.ok or not isinstance(result.data, dict):
|
|
2630
2631
|
return False, result.message
|
|
2631
2632
|
data = result.data
|
|
2632
|
-
|
|
2633
|
-
"subscriber_count": data.get("subscriber_count"),
|
|
2634
|
-
"video_count": data.get("video_count"),
|
|
2635
|
-
"view_count": data.get("view_count"),
|
|
2633
|
+
updates = {
|
|
2636
2634
|
"metrics_source": data.get("metrics_source", "youtube_public_page"),
|
|
2637
2635
|
"last_public_lookup_at": data.get("last_public_lookup_at", now()),
|
|
2638
|
-
}
|
|
2636
|
+
}
|
|
2637
|
+
for field in ("youtube_id", "name", "handle", "url", "thumbnail_url", "subscriber_count", "video_count", "view_count"):
|
|
2638
|
+
if data.get(field) is not None and data.get(field) != "":
|
|
2639
|
+
updates[field] = data[field]
|
|
2640
|
+
update_channel(str(channel["id"]), updates)
|
|
2639
2641
|
return True, "Métricas YouTube actualizadas."
|
|
2640
2642
|
|
|
2641
2643
|
|
|
@@ -3086,6 +3088,18 @@ def render_channels():
|
|
|
3086
3088
|
edit_key = f"edit_channel_{channel_id}"
|
|
3087
3089
|
with st.container(border=True):
|
|
3088
3090
|
header_cols = st.columns([0.7, 2.75, 1.1, 1.0, 1.35, 1.35])
|
|
3091
|
+
channel_url = str(channel.get("url") or "").strip()
|
|
3092
|
+
if not channel_url:
|
|
3093
|
+
channel_id_or_handle = str(channel.get("youtube_channel_id") or channel.get("youtube_id") or channel.get("handle") or "").strip()
|
|
3094
|
+
if channel_id_or_handle:
|
|
3095
|
+
if channel_id_or_handle.startswith("http"):
|
|
3096
|
+
channel_url = channel_id_or_handle
|
|
3097
|
+
elif channel_id_or_handle.startswith("UC"):
|
|
3098
|
+
channel_url = f"https://www.youtube.com/channel/{channel_id_or_handle}"
|
|
3099
|
+
elif channel_id_or_handle.startswith(("@", "channel/")):
|
|
3100
|
+
channel_url = f"https://www.youtube.com/{channel_id_or_handle}"
|
|
3101
|
+
else:
|
|
3102
|
+
channel_url = f"https://www.youtube.com/@{channel_id_or_handle}"
|
|
3089
3103
|
with header_cols[0]:
|
|
3090
3104
|
if channel.get("thumbnail_url"):
|
|
3091
3105
|
st.image(channel["thumbnail_url"], width=64)
|
|
@@ -3102,6 +3116,8 @@ def render_channels():
|
|
|
3102
3116
|
with header_cols[4]:
|
|
3103
3117
|
st.metric("Visualizações", _format_channel_count(channel.get("view_count")))
|
|
3104
3118
|
with header_cols[5]:
|
|
3119
|
+
if channel_url:
|
|
3120
|
+
st.link_button("Abrir canal", channel_url, type="primary", width="content")
|
|
3105
3121
|
if st.button("↻", key=f"refresh_youtube_metrics_{channel_id}", help="Actualizar Inscritos, Vídeos e Visualizações", width="stretch"):
|
|
3106
3122
|
with st.spinner("A actualizar métricas YouTube…"):
|
|
3107
3123
|
refreshed, message = _refresh_youtube_channel_metrics(channel, youtube)
|
|
@@ -81,7 +81,7 @@ def _parse_public_count(value: Any) -> int | None:
|
|
|
81
81
|
text = _text_from_node(value).lower().replace(" ", "")
|
|
82
82
|
if not text:
|
|
83
83
|
return None
|
|
84
|
-
match = re.search(r"([0-9][0-9.,]*)(mil|
|
|
84
|
+
match = re.search(r"([0-9][0-9.,]*)(milhões|mil|mi|bi|[kmb])?", text)
|
|
85
85
|
if not match:
|
|
86
86
|
return None
|
|
87
87
|
number = match.group(1)
|
|
@@ -89,6 +89,16 @@ def _parse_public_count(value: Any) -> int | None:
|
|
|
89
89
|
if suffix == "milhões":
|
|
90
90
|
suffix = "m"
|
|
91
91
|
try:
|
|
92
|
+
if suffix:
|
|
93
|
+
# YouTube pode devolver "193 mil", "193,4 mil" ou "1.234 mil".
|
|
94
|
+
# Um ponto seguido de três algarismos é separador de milhares;
|
|
95
|
+
# vírgula/ponto com 1–2 algarismos é a parte decimal.
|
|
96
|
+
if "." in number and "," in number:
|
|
97
|
+
number = number.replace(".", "").replace(",", ".")
|
|
98
|
+
elif "," in number:
|
|
99
|
+
number = number.replace(",", ".")
|
|
100
|
+
elif re.fullmatch(r"\d+\.\d{3}", number):
|
|
101
|
+
number = number.replace(".", "")
|
|
92
102
|
if suffix in {"k", "mil"}:
|
|
93
103
|
return int(float(number.replace(",", ".")) * 1_000)
|
|
94
104
|
if suffix in {"m", "mi"}:
|
|
@@ -412,6 +422,7 @@ class YouTubeAdapter:
|
|
|
412
422
|
break
|
|
413
423
|
subscriber_value = _find_first_key(initial_data, "subscriberCountText")
|
|
414
424
|
video_value = _find_first_key(initial_data, "videoCountText")
|
|
425
|
+
view_value = _find_first_key(initial_data, "viewCountText")
|
|
415
426
|
data = {
|
|
416
427
|
"youtube_id": youtube_id or feed.get("youtube_id", ""),
|
|
417
428
|
"name": title,
|
|
@@ -421,7 +432,7 @@ class YouTubeAdapter:
|
|
|
421
432
|
"thumbnail_url": thumbnail_url,
|
|
422
433
|
"subscriber_count": _parse_public_count(subscriber_value),
|
|
423
434
|
"video_count": _parse_public_count(video_value) or feed.get("video_count"),
|
|
424
|
-
"view_count":
|
|
435
|
+
"view_count": _parse_public_count(view_value),
|
|
425
436
|
"metrics_source": "youtube_public_page" if (page_title or page_description or page_thumbnail) else ("youtube_public_feed" if feed.get("name") else "youtube_public_page"),
|
|
426
437
|
"public_lookup": True,
|
|
427
438
|
}
|
package/package.json
CHANGED