@danhachuel/thunderbolt 0.6.23 → 0.6.24

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.
@@ -13,7 +13,7 @@ from hermes_ui.influencers import InfluencerBackendError, STANDALONE_CONTENT_INF
13
13
  from hermes_ui.storage import read_json, write_json
14
14
  from hermes_ui.countries import COUNTRY_OPTIONS
15
15
  from hermes_ui.languages import LANGUAGE_CODES, language_code, language_label
16
- from integrations.instagram_public import fetch_public_instagram_posts, fetch_public_instagram_profile, normalize_instagram_bio
16
+ from integrations.instagram_public import fetch_public_instagram_posts, fetch_public_instagram_profile, normalize_instagram_bio, normalize_instagram_metric
17
17
  from integrations.meta_social import test_facebook_pages_api_card, test_instagram_api_card
18
18
 
19
19
 
@@ -38,6 +38,10 @@ def _profile_metric(profile: Mapping[str, Any], *keys: str) -> Any:
38
38
  return None
39
39
 
40
40
 
41
+ def _metric_input(value: Any) -> int | None:
42
+ return normalize_instagram_metric(value)
43
+
44
+
41
45
  def _language_index(value: Any) -> int:
42
46
  code = language_code(value)
43
47
  return list(LANGUAGE_CODES).index(code) if code in LANGUAGE_CODES else list(LANGUAGE_CODES).index("pt")
@@ -288,6 +292,7 @@ def _save_public_profile(data: Mapping[str, Any], *, country: str, language: str
288
292
  "name": name,
289
293
  "url": url,
290
294
  "handle": _clean(data.get("handle")),
295
+ "bio": normalize_instagram_bio(data.get("bio")),
291
296
  "country": country.strip(),
292
297
  "language": language.strip(),
293
298
  "post_count": data.get("post_count"),
@@ -300,6 +305,14 @@ def _save_public_profile(data: Mapping[str, Any], *, country: str, language: str
300
305
  return create_channel(name, url, metadata)
301
306
 
302
307
 
308
+ def _merge_instagram_refresh(existing: Mapping[str, Any], refreshed: Mapping[str, Any]) -> dict[str, Any]:
309
+ merged = dict(refreshed)
310
+ for field in ("subscriber_count", "following_count", "post_count", "bio", "avatar_url"):
311
+ if merged.get(field) in (None, "") and existing.get(field) not in (None, ""):
312
+ merged[field] = existing[field]
313
+ return merged
314
+
315
+
303
316
  def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, Any]], settings: Mapping[str, Any]) -> None:
304
317
  profile_id = _clean(profile.get("id"))
305
318
  edit_key = f"edit_instagram_profile_{profile_id}"
@@ -329,10 +342,7 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
329
342
  if st.button("↻", help="Actualizar posts, seguidores e seguindo", key=f"refresh_instagram_{profile_id}"):
330
343
  result = fetch_public_instagram_profile(_clean(profile.get("url")) or _clean(profile.get("handle")))
331
344
  if result.ok:
332
- refreshed = dict(result.data)
333
- for field in ("subscriber_count", "following_count", "post_count", "bio", "avatar_url"):
334
- if refreshed.get(field) in (None, "") and profile.get(field) not in (None, ""):
335
- refreshed[field] = profile[field]
345
+ refreshed = _merge_instagram_refresh(profile, result.data)
336
346
  update_channel(profile_id, {**refreshed, "platform": "instagram", "country": profile.get("country", ""), "language": profile.get("language", ""), "character_id": profile.get("character_id", "")})
337
347
  st.success("Métricas Instagram actualizadas.")
338
348
  st.rerun()
@@ -368,15 +378,16 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
368
378
  with edit_cols[0]:
369
379
  name = st.text_input("Nome", value=_clean(profile.get("name")), key=f"instagram_edit_name_{profile_id}")
370
380
  handle = st.text_input("handler", value=_clean(profile.get("handle")), key=f"instagram_edit_handle_{profile_id}")
381
+ bio = st.text_area("Bio", value=normalize_instagram_bio(profile.get("bio")), key=f"instagram_edit_bio_{profile_id}")
371
382
  country = st.selectbox("País", _country_options(), index=_country_index(profile.get("country")), format_func=_country_label, key=f"instagram_edit_country_{profile_id}")
372
383
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(profile.get("language")), format_func=language_label, key=f"instagram_edit_language_{profile_id}")
373
384
  with edit_cols[1]:
374
- posts = st.number_input("posts", min_value=0, value=int(profile.get("post_count") or 0), key=f"instagram_edit_posts_{profile_id}")
375
- followers = st.number_input("Seguidores", min_value=0, value=int(profile.get("subscriber_count") or 0), key=f"instagram_edit_followers_{profile_id}")
376
- following = st.number_input("seguindo", min_value=0, value=int(profile.get("following_count") or 0), key=f"instagram_edit_following_{profile_id}")
385
+ posts = st.number_input("posts", min_value=0, value=_metric_input(profile.get("post_count")), placeholder="Não encontrado", key=f"instagram_edit_posts_{profile_id}")
386
+ followers = st.number_input("Seguidores", min_value=0, value=_metric_input(profile.get("subscriber_count")), placeholder="Não encontrado", key=f"instagram_edit_followers_{profile_id}")
387
+ following = st.number_input("seguindo", min_value=0, value=_metric_input(profile.get("following_count")), placeholder="Não encontrado", key=f"instagram_edit_following_{profile_id}")
377
388
  save_edit = st.form_submit_button("Guardar alterações", type="primary", use_container_width=True)
378
389
  if save_edit:
379
- update_channel(profile_id, {"name": name.strip(), "handle": handle.strip(), "country": country.strip(), "language": language, "post_count": int(posts), "subscriber_count": int(followers), "following_count": int(following)})
390
+ update_channel(profile_id, {"name": name.strip(), "handle": handle.strip(), "bio": normalize_instagram_bio(bio), "country": country.strip(), "language": language, "post_count": _metric_input(posts), "subscriber_count": _metric_input(followers), "following_count": _metric_input(following)})
380
391
  st.session_state.pop(edit_key, None)
381
392
  st.success("Conta Instagram actualizada.")
382
393
  st.rerun()
@@ -449,19 +460,20 @@ def render_social_networks(settings: dict[str, Any]) -> None:
449
460
  form_cols = st.columns(2)
450
461
  with form_cols[0]:
451
462
  name = st.text_input("Nome", value=_clean(data.get("name")), key="social_instagram_result_name")
463
+ bio = st.text_area("Bio", value=normalize_instagram_bio(data.get("bio")), key="social_instagram_result_bio")
452
464
  country = st.selectbox("País", _country_options(), index=_country_index(data.get("country")), format_func=_country_label, key="social_instagram_result_country")
453
465
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(data.get("language")), format_func=language_label, key="social_instagram_result_language")
454
466
  with form_cols[1]:
455
- posts = st.number_input("posts", min_value=0, value=int(data.get("post_count") or 0), key="social_instagram_result_posts")
456
- followers = st.number_input("Seguidores", min_value=0, value=int(data.get("subscriber_count") or 0), key="social_instagram_result_followers")
457
- following = st.number_input("seguindo", min_value=0, value=int(data.get("following_count") or 0), key="social_instagram_result_following")
467
+ posts = st.number_input("posts", min_value=0, value=_metric_input(data.get("post_count")), placeholder="Não encontrado", key="social_instagram_result_posts")
468
+ followers = st.number_input("Seguidores", min_value=0, value=_metric_input(data.get("subscriber_count")), placeholder="Não encontrado", key="social_instagram_result_followers")
469
+ following = st.number_input("seguindo", min_value=0, value=_metric_input(data.get("following_count")), placeholder="Não encontrado", key="social_instagram_result_following")
458
470
  character_options = [""] + [_clean(item.get("id")) for item in characters if _clean(item.get("id"))]
459
471
  character_labels = {"": "Sem personagem associado"} | {_clean(item.get("id")): _clean(item.get("name")) or _clean(item.get("id")) for item in characters}
460
472
  selected_character = st.selectbox("Personagem", character_options, format_func=lambda value: character_labels.get(value, value), key="social_instagram_result_character")
461
473
  save_profile = st.form_submit_button("Cadastrar conta Instagram", type="primary", use_container_width=True)
462
474
  if save_profile:
463
475
  profile_data = dict(data)
464
- profile_data.update({"name": name, "post_count": int(posts), "subscriber_count": int(followers), "following_count": int(following)})
476
+ profile_data.update({"name": name, "bio": normalize_instagram_bio(bio), "post_count": _metric_input(posts), "subscriber_count": _metric_input(followers), "following_count": _metric_input(following)})
465
477
  _save_public_profile(profile_data, country=country, language=language, character_id=selected_character)
466
478
  st.success("Conta Instagram cadastrada em Redes Sociais.")
467
479
  for key in ("social_instagram_result", "social_instagram_ok", "social_instagram_message"):
@@ -36,6 +36,7 @@ def create_channel(name: str, url: str = "", metadata: dict[str, Any] | None = N
36
36
  "platform": "youtube",
37
37
  "social_network": "YouTube",
38
38
  "handle": "",
39
+ "bio": "",
39
40
  "description": "",
40
41
  "description_source": "",
41
42
  "niche": "",
@@ -117,8 +117,8 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
117
117
 
118
118
 
119
119
  def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]) -> dict[str, Any]:
120
- followers = _direct_profile_metric(user, 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
121
- following = _direct_profile_metric(user, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
120
+ followers = _profile_metric(user, 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
121
+ following = _profile_metric(user, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
122
122
  followers = followers if followers is not None else _structured_metric_from_json(json.dumps(user), 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
123
123
  following = following if following is not None else _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
124
124
  media = ((user.get('edge_owner_to_timeline_media') or {}).get('count') if isinstance(user.get('edge_owner_to_timeline_media'), dict) else None)
@@ -138,18 +138,21 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
138
138
  }
139
139
 
140
140
 
141
- def _direct_profile_metric(user: Mapping[str, Any], *keys: str) -> int | None:
142
- """Read common profile counters before falling back to recursive JSON parsing."""
141
+ def normalize_instagram_metric(value: Any) -> int | None:
142
+ """Normalize an Instagram counter without turning an unknown value into zero."""
143
+ if isinstance(value, Mapping):
144
+ value = value.get('count') or value.get('value')
145
+ if value in (None, ''):
146
+ return None
147
+ digits = re.sub(r'[^0-9]', '', str(value))
148
+ return int(digits) if digits else None
149
+
150
+
151
+ def _profile_metric(user: Mapping[str, Any], *keys: str) -> int | None:
143
152
  for key in keys:
144
- value = user.get(key)
145
- if isinstance(value, Mapping):
146
- value = value.get('count') or value.get('value')
147
- if value in (None, ''):
148
- continue
149
- try:
150
- return int(value)
151
- except (TypeError, ValueError):
152
- continue
153
+ value = normalize_instagram_metric(user.get(key))
154
+ if value is not None:
155
+ return value
153
156
  return None
154
157
 
155
158
 
@@ -283,4 +286,4 @@ def fetch_public_instagram_posts(source: str, limit: int = 10) -> IntegrationRes
283
286
  return IntegrationResult(bool(posts), 'Posts públicos encontrados.' if posts else 'Não foi possível encontrar posts públicos nesta página do Instagram.', reference | {'posts': posts[:max(1, int(limit))]})
284
287
 
285
288
 
286
- __all__ = ['IntegrationResult', 'fetch_public_instagram_posts', 'fetch_public_instagram_profile', 'normalize_instagram_bio', 'normalize_instagram_reference']
289
+ __all__ = ['IntegrationResult', 'fetch_public_instagram_posts', 'fetch_public_instagram_profile', 'normalize_instagram_bio', 'normalize_instagram_metric', 'normalize_instagram_reference']
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.23",
3
+ "version": "0.6.24",
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",