@danhachuel/thunderbolt 0.6.29 → 0.6.31

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.
@@ -48,6 +48,10 @@ def _render_instagram_bio(value: Any) -> None:
48
48
  st.text(bio)
49
49
 
50
50
 
51
+ def _profile_bio(profile: Mapping[str, Any]) -> str:
52
+ return normalize_instagram_bio(profile.get("bio")) or normalize_instagram_bio(profile.get("bio_raw"))
53
+
54
+
51
55
  def _language_index(value: Any) -> int:
52
56
  code = language_code(value)
53
57
  return list(LANGUAGE_CODES).index(code) if code in LANGUAGE_CODES else list(LANGUAGE_CODES).index("pt")
@@ -299,7 +303,8 @@ def _save_public_profile(data: Mapping[str, Any], *, country: str, language: str
299
303
  "name": name,
300
304
  "url": url,
301
305
  "handle": _clean(data.get("handle")),
302
- "bio": normalize_instagram_bio(data.get("bio")),
306
+ "bio": _profile_bio(data),
307
+ "bio_raw": _clean(data.get("bio_raw")),
303
308
  "country": selected_country,
304
309
  "language": language.strip(),
305
310
  "post_count": data.get("post_count"),
@@ -314,7 +319,7 @@ def _save_public_profile(data: Mapping[str, Any], *, country: str, language: str
314
319
 
315
320
  def _merge_instagram_refresh(existing: Mapping[str, Any], refreshed: Mapping[str, Any]) -> dict[str, Any]:
316
321
  merged = dict(refreshed)
317
- for field in ("subscriber_count", "following_count", "post_count", "bio", "avatar_url"):
322
+ for field in ("subscriber_count", "following_count", "post_count", "bio", "bio_raw", "avatar_url"):
318
323
  if merged.get(field) in (None, "") and existing.get(field) not in (None, ""):
319
324
  merged[field] = existing[field]
320
325
  return merged
@@ -348,7 +353,7 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
348
353
  with header_cols[1]:
349
354
  st.write(f"**{_clean(profile.get('name')) or 'Sem nome'}**")
350
355
  st.caption(f"{_clean(profile.get('handle')) or _clean(profile.get('url')) or 'sem handler'}")
351
- _render_instagram_bio(profile.get("bio"))
356
+ _render_instagram_bio(_profile_bio(profile))
352
357
  st.caption(f"{_clean(profile.get('country')) or 'País não definido'} · {_clean(profile.get('language')) or 'Idioma não definido'}")
353
358
  with header_cols[2]:
354
359
  st.metric("posts", _metric(profile.get("post_count")))
@@ -362,6 +367,11 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
362
367
  if st.button("↻", help="Actualizar posts, seguidores e seguindo", key=f"refresh_instagram_{profile_id}"):
363
368
  refreshed_ok, refreshed_message, refreshed = _refresh_instagram_profile(profile)
364
369
  if refreshed_ok:
370
+ posts_key = _instagram_posts_key(profile_id)
371
+ current_posts = st.session_state.get(posts_key, [])
372
+ posts_ok, _, refreshed_posts = _load_instagram_posts(refreshed, limit=max(10, len(current_posts)))
373
+ if posts_ok:
374
+ st.session_state[posts_key] = refreshed_posts
365
375
  update_channel(profile_id, {**refreshed, "platform": "instagram", "country": profile.get("country", ""), "language": profile.get("language", ""), "character_id": profile.get("character_id", "")})
366
376
  st.success("Métricas Instagram actualizadas.")
367
377
  st.rerun()
@@ -397,7 +407,7 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
397
407
  with edit_cols[0]:
398
408
  name = st.text_input("Nome", value=_clean(profile.get("name")), key=f"instagram_edit_name_{profile_id}")
399
409
  handle = st.text_input("handler", value=_clean(profile.get("handle")), key=f"instagram_edit_handle_{profile_id}")
400
- bio = st.text_area("Bio", value=normalize_instagram_bio(profile.get("bio")), key=f"instagram_edit_bio_{profile_id}")
410
+ bio = st.text_area("Bio", value=_profile_bio(profile), key=f"instagram_edit_bio_{profile_id}")
401
411
  country = st.selectbox("País", _country_options(), index=_country_index(profile.get("country")), format_func=_country_label, key=f"instagram_edit_country_{profile_id}")
402
412
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(profile.get("language")), format_func=language_label, key=f"instagram_edit_language_{profile_id}")
403
413
  with edit_cols[1]:
@@ -406,7 +416,7 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
406
416
  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}")
407
417
  save_edit = st.form_submit_button("Guardar alterações", type="primary", use_container_width=True)
408
418
  if save_edit:
409
- 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)})
419
+ update_channel(profile_id, {"name": name.strip(), "handle": handle.strip(), "bio": normalize_instagram_bio(bio), "bio_raw": bio, "country": country.strip(), "language": language, "post_count": _metric_input(posts), "subscriber_count": _metric_input(followers), "following_count": _metric_input(following)})
410
420
  st.session_state.pop(edit_key, None)
411
421
  st.success("Conta Instagram actualizada.")
412
422
  st.rerun()
@@ -466,7 +476,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
466
476
  with preview_cols[1]:
467
477
  st.write(f"**{_clean(data.get('name')) or _clean(data.get('username'))}**")
468
478
  st.caption(_clean(data.get("handle")))
469
- _render_instagram_bio(data.get("bio"))
479
+ _render_instagram_bio(_profile_bio(data))
470
480
  with preview_cols[2]:
471
481
  st.metric("posts", _metric(data.get("post_count")))
472
482
  with preview_cols[3]:
@@ -477,7 +487,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
477
487
  form_cols = st.columns(2)
478
488
  with form_cols[0]:
479
489
  name = st.text_input("Nome", value=_clean(data.get("name")), key="social_instagram_result_name")
480
- bio = st.text_area("Bio", value=normalize_instagram_bio(data.get("bio")), key="social_instagram_result_bio")
490
+ bio = st.text_area("Bio", value=_profile_bio(data), key="social_instagram_result_bio")
481
491
  country = st.selectbox("País", _country_options(), index=_country_index(data.get("country")), format_func=_country_label, key="social_instagram_result_country")
482
492
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(data.get("language")), format_func=language_label, key="social_instagram_result_language")
483
493
  with form_cols[1]:
@@ -490,7 +500,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
490
500
  save_profile = st.form_submit_button("Cadastrar conta Instagram", type="primary", use_container_width=True)
491
501
  if save_profile:
492
502
  profile_data = dict(data)
493
- 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)})
503
+ profile_data.update({"name": name, "bio": normalize_instagram_bio(bio), "bio_raw": bio, "post_count": _metric_input(posts), "subscriber_count": _metric_input(followers), "following_count": _metric_input(following)})
494
504
  _save_public_profile(profile_data, country=country, language=language, character_id=selected_character)
495
505
  st.success("Conta Instagram cadastrada em Redes Sociais.")
496
506
  for key in ("social_instagram_result", "social_instagram_ok", "social_instagram_message"):
@@ -37,6 +37,7 @@ def create_channel(name: str, url: str = "", metadata: dict[str, Any] | None = N
37
37
  "social_network": "YouTube",
38
38
  "handle": "",
39
39
  "bio": "",
40
+ "bio_raw": "",
40
41
  "description": "",
41
42
  "description_source": "",
42
43
  "niche": "",
@@ -125,6 +125,8 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
125
125
  [curl, '-L', '--max-time', '20', '-sS', '-A', headers['User-Agent'], '-H', f"x-ig-app-id: {headers['x-ig-app-id']}", '-H', 'Accept: application/json, text/plain, */*', endpoint],
126
126
  capture_output=True,
127
127
  text=True,
128
+ encoding='utf-8',
129
+ errors='replace',
128
130
  timeout=25,
129
131
  check=False,
130
132
  )
@@ -154,11 +156,13 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
154
156
  followers = followers if followers is not None else _structured_metric_from_json(json.dumps(user), 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
155
157
  following = following if following is not None else _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
156
158
  media = ((user.get('edge_owner_to_timeline_media') or {}).get('count') if isinstance(user.get('edge_owner_to_timeline_media'), dict) else None)
159
+ biography = str(user.get('biography') or '').strip()
157
160
  return {
158
161
  'id': f"instagram_{reference['username']}",
159
162
  **reference,
160
163
  'name': str(user.get('full_name') or user.get('username') or reference['username']).strip(),
161
- 'bio': normalize_instagram_bio(user.get('biography')),
164
+ 'bio': normalize_instagram_bio(biography),
165
+ 'bio_raw': biography,
162
166
  'avatar_url': str(user.get('profile_pic_url_hd') or user.get('profile_pic_url') or '').strip(),
163
167
  'subscriber_count': followers,
164
168
  'following_count': following,
@@ -220,7 +224,7 @@ def fetch_public_instagram_profile(source: str) -> IntegrationResult:
220
224
  followers = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:followers|seguidores)',)) or _structured_metric(response.text, 'edge_followed_by', 'followers', 'follower_count') or _structured_metric_from_json(response.text, 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
221
225
  following = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:following|seguindo)',)) or _structured_metric(response.text, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount') or _structured_metric_from_json(response.text, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
222
226
  posts = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:posts|publicações|publications)',)) or _structured_metric(response.text, 'edge_owner_to_timeline_media', 'posts', 'post_count')
223
- data = {'id': f"instagram_{reference['username']}", **reference, 'name': title.split('(')[0].strip() or reference['username'], 'bio': normalize_instagram_bio(description), 'avatar_url': avatar_url, 'subscriber_count': followers, 'following_count': following, 'post_count': posts, 'public_lookup': True, 'metrics_source': 'instagram_public_page', 'last_public_lookup_at': datetime.now(timezone.utc).isoformat()}
227
+ data = {'id': f"instagram_{reference['username']}", **reference, 'name': title.split('(')[0].strip() or reference['username'], 'bio': normalize_instagram_bio(description), 'bio_raw': description, 'avatar_url': avatar_url, 'subscriber_count': followers, 'following_count': following, 'post_count': posts, 'public_lookup': True, 'metrics_source': 'instagram_public_page', 'last_public_lookup_at': datetime.now(timezone.utc).isoformat()}
224
228
  return IntegrationResult(True, 'Perfil Instagram encontrado publicamente.', data)
225
229
 
226
230
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.29",
3
+ "version": "0.6.31",
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",