@danhachuel/thunderbolt 0.6.27 → 0.6.29

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
@@ -1721,7 +1721,7 @@ def render_growth_youtube():
1721
1721
 
1722
1722
  def render_dashboard():
1723
1723
  ui_language = current_ui_language()
1724
- st.title("Thunderbolt")
1724
+ st.title("Thunderbolt Dashboard")
1725
1725
  st.caption(ui_text("Interface local para operação e automação de conteúdo faceless", ui_language))
1726
1726
  summary = pipeline_summary()
1727
1727
  active_note = f'{summary["active_channels"]} {ui_text("activos", ui_language)}'
@@ -45,7 +45,7 @@ def _metric_input(value: Any) -> int | None:
45
45
  def _render_instagram_bio(value: Any) -> None:
46
46
  bio = normalize_instagram_bio(value)
47
47
  if bio:
48
- st.markdown(bio.replace("\n", " \n"))
48
+ st.text(bio)
49
49
 
50
50
 
51
51
  def _language_index(value: Any) -> int:
@@ -99,7 +99,6 @@ def _render_instagram_posts(profile: Mapping[str, Any]) -> None:
99
99
  profile_id = _clean(profile.get("id"))
100
100
  state_key = _instagram_posts_key(profile_id)
101
101
  posts = st.session_state.get(state_key, [])
102
- source = _clean(profile.get("url")) or _clean(profile.get("handle"))
103
102
  with st.expander("Últimos posts do Instagram", expanded=False):
104
103
  action_cols = st.columns(3)
105
104
  with action_cols[0]:
@@ -110,13 +109,14 @@ def _render_instagram_posts(profile: Mapping[str, Any]) -> None:
110
109
  download_clicked = bool(posts) and st.download_button("Baixar todos", data=_instagram_posts_archive(posts), file_name=f"instagram-{profile_id}-posts.zip", mime="application/zip", key=f"download_instagram_posts_{profile_id}", use_container_width=True)
111
110
  if load_clicked or refresh_clicked:
112
111
  target = max(10, len(posts)) if refresh_clicked else 10
113
- result = fetch_public_instagram_posts(source, limit=target)
114
- if result.ok:
115
- posts = result.data.get("posts", [])
112
+ posts_ok, posts_message, loaded_posts = _load_instagram_posts(profile, limit=target)
113
+ if posts_ok:
114
+ posts = loaded_posts
116
115
  st.session_state[state_key] = posts
117
116
  st.success(f"{len(posts)} posts carregados.")
117
+ st.rerun()
118
118
  else:
119
- st.warning(result.message)
119
+ st.warning(posts_message)
120
120
  posts = st.session_state.get(state_key, posts)
121
121
  if not posts:
122
122
  st.info("Clique em Carregar últimos 10 para consultar os posts públicos desta conta.")
@@ -133,11 +133,11 @@ def _render_instagram_posts(profile: Mapping[str, Any]) -> None:
133
133
  if _clean(post.get("caption")):
134
134
  st.caption(post["caption"])
135
135
  if st.button("Mostrar + 10", key=f"more_instagram_posts_{profile_id}", use_container_width=True):
136
- result = fetch_public_instagram_posts(source, limit=len(posts) + 10)
137
- if result.ok:
138
- st.session_state[state_key] = result.data.get("posts", posts)
136
+ posts_ok, posts_message, loaded_posts = _load_instagram_posts(profile, limit=len(posts) + 10)
137
+ if posts_ok:
138
+ st.session_state[state_key] = loaded_posts
139
139
  st.rerun()
140
- st.warning(result.message)
140
+ st.warning(posts_message)
141
141
 
142
142
 
143
143
  def _api_card_defaults(kind: str) -> tuple[str, list[str], Callable[[Mapping[str, Any]], dict[str, Any]]]:
@@ -291,6 +291,7 @@ def _characters(settings: Mapping[str, Any]) -> tuple[list[dict[str, Any]], Any
291
291
  def _save_public_profile(data: Mapping[str, Any], *, country: str, language: str, character_id: str = "") -> dict[str, Any]:
292
292
  name = _clean(data.get("name")) or _clean(data.get("username")) or "Conta Instagram"
293
293
  url = _clean(data.get("url"))
294
+ selected_country = _clean(country) if _clean(country) in COUNTRY_OPTIONS else ""
294
295
  metadata = {
295
296
  **dict(data),
296
297
  "platform": "instagram",
@@ -299,7 +300,7 @@ def _save_public_profile(data: Mapping[str, Any], *, country: str, language: str
299
300
  "url": url,
300
301
  "handle": _clean(data.get("handle")),
301
302
  "bio": normalize_instagram_bio(data.get("bio")),
302
- "country": country.strip(),
303
+ "country": selected_country,
303
304
  "language": language.strip(),
304
305
  "post_count": data.get("post_count"),
305
306
  "subscriber_count": data.get("subscriber_count"),
@@ -319,6 +320,21 @@ def _merge_instagram_refresh(existing: Mapping[str, Any], refreshed: Mapping[str
319
320
  return merged
320
321
 
321
322
 
323
+ def _refresh_instagram_profile(profile: Mapping[str, Any]) -> tuple[bool, str, dict[str, Any]]:
324
+ source = _clean(profile.get("url")) or _clean(profile.get("handle"))
325
+ result = fetch_public_instagram_profile(source)
326
+ if not result.ok:
327
+ return False, result.message, {}
328
+ return True, result.message, _merge_instagram_refresh(profile, result.data)
329
+
330
+
331
+ def _load_instagram_posts(profile: Mapping[str, Any], limit: int = 10) -> tuple[bool, str, list[dict[str, Any]]]:
332
+ source = _clean(profile.get("url")) or _clean(profile.get("handle"))
333
+ result = fetch_public_instagram_posts(source, limit=limit)
334
+ posts = result.data.get("posts", []) if isinstance(result.data, dict) else []
335
+ return result.ok, result.message, posts
336
+
337
+
322
338
  def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, Any]], settings: Mapping[str, Any]) -> None:
323
339
  profile_id = _clean(profile.get("id"))
324
340
  edit_key = f"edit_instagram_profile_{profile_id}"
@@ -344,13 +360,12 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
344
360
  refresh_col, edit_col = st.columns(2)
345
361
  with refresh_col:
346
362
  if st.button("↻", help="Actualizar posts, seguidores e seguindo", key=f"refresh_instagram_{profile_id}"):
347
- result = fetch_public_instagram_profile(_clean(profile.get("url")) or _clean(profile.get("handle")))
348
- if result.ok:
349
- refreshed = _merge_instagram_refresh(profile, result.data)
363
+ refreshed_ok, refreshed_message, refreshed = _refresh_instagram_profile(profile)
364
+ if refreshed_ok:
350
365
  update_channel(profile_id, {**refreshed, "platform": "instagram", "country": profile.get("country", ""), "language": profile.get("language", ""), "character_id": profile.get("character_id", "")})
351
366
  st.success("Métricas Instagram actualizadas.")
352
367
  st.rerun()
353
- st.warning(result.message)
368
+ st.warning(refreshed_message)
354
369
  with edit_col:
355
370
  if st.button("Editar", key=f"edit_instagram_button_{profile_id}", use_container_width=True):
356
371
  st.session_state[edit_key] = True
@@ -25,7 +25,13 @@ def normalize_instagram_bio(value: Any) -> str:
25
25
  bio = str(value or '').strip()
26
26
  if not bio:
27
27
  return ''
28
- if re.search(r'\b(?:followers|following|seguidores|seguindo|posts|publicações)\b', bio, flags=re.IGNORECASE) and re.search(r'\d', bio):
28
+ metric_words = r'followers|following|seguidores|seguindo|posts|publicações|publications'
29
+ metric_hits = re.findall(
30
+ rf'(?:\d[\d.,]*\s*(?:{metric_words})\b|\b(?:{metric_words})\s*\d[\d.,]*)',
31
+ bio,
32
+ flags=re.IGNORECASE,
33
+ )
34
+ if len(metric_hits) >= 2:
29
35
  return ''
30
36
  return bio
31
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.27",
3
+ "version": "0.6.29",
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",