@danhachuel/thunderbolt 0.6.49 → 0.6.50

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.
@@ -216,8 +216,8 @@ def _render_instagram_posts(profile: Mapping[str, Any]) -> None:
216
216
  if load_clicked or refresh_clicked:
217
217
  target = max(10, len(posts)) if refresh_clicked else 10
218
218
  posts_ok, posts_message, loaded_posts = _load_instagram_posts(profile, limit=target)
219
- # Debug temporário removido após validação; manter para reactivar se necessário:
220
- # st.write({"posts_ok": posts_ok, "posts_message": posts_message, "posts_count": len(loaded_posts)})
219
+ # DEBUG WINDOWS INSTAGRAM: manter até a validação final do carregamento público.
220
+ st.write({"instagram_posts_ok": posts_ok, "instagram_posts_message": posts_message, "instagram_posts_count": len(loaded_posts)})
221
221
  if posts_ok:
222
222
  posts = loaded_posts
223
223
  _persist_instagram_posts(profile, posts)
@@ -132,18 +132,8 @@ class IntegrationResult:
132
132
 
133
133
 
134
134
  def normalize_instagram_bio(value: Any) -> str:
135
- """Keep the profile biography, never Instagram's metrics summary."""
135
+ """Keep the complete public biography exactly as returned by Instagram."""
136
136
  bio = str(value or '').strip()
137
- if not bio:
138
- return ''
139
- metric_words = r'followers|following|seguidores|seguindo|posts|publicações|publications'
140
- metric_hits = re.findall(
141
- rf'(?:\d[\d.,]*\s*(?:{metric_words})\b|\b(?:{metric_words})\s*\d[\d.,]*)',
142
- bio,
143
- flags=re.IGNORECASE,
144
- )
145
- if len(metric_hits) >= 2:
146
- return ''
147
137
  return bio
148
138
 
149
139
 
@@ -249,6 +239,7 @@ def _extract_profile_user(payload: Any) -> Mapping[str, Any] | None:
249
239
 
250
240
 
251
241
  def _fetch_profile_with_playwright(username: str, headers: Mapping[str, str], cookies: Mapping[str, str]) -> dict[str, Any] | None:
242
+ # CORREÇÃO WINDOWS: este helper é o único caminho de rede usado pelo perfil no Windows.
252
243
  endpoints = (
253
244
  f'https://www.instagram.com/api/v1/users/web_profile_info/?username={username}',
254
245
  f'https://i.instagram.com/api/v1/users/web_profile_info/?username={username}',
@@ -429,7 +420,9 @@ def _country_from_bio_fallback(bio: str) -> str:
429
420
 
430
421
  def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]) -> dict[str, Any]:
431
422
  followers = _profile_metric(user, 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
432
- following = _profile_metric(user, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
423
+ following_node = user.get('edge_follow')
424
+ following = normalize_instagram_metric(following_node) if isinstance(following_node, Mapping) else None
425
+ following = following if following is not None else _profile_metric(user, 'follows', 'following', 'following_count', 'followingCount')
433
426
  followers = followers if followers is not None else _structured_metric_from_json(json.dumps(user), 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
434
427
  following = following if following is not None else _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
435
428
  media = ((user.get('edge_owner_to_timeline_media') or {}).get('count') if isinstance(user.get('edge_owner_to_timeline_media'), dict) else None)
@@ -437,7 +430,7 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
437
430
  biography = str(user.get('biography') or user.get('bio') or user.get('description') or '').strip()
438
431
  country = extract_public_instagram_country(user) or _fetch_instagram_about_country(user.get('id') or user.get('pk'))
439
432
  country = country or _country_from_bio_fallback(biography)
440
- LOGGER.debug('Instagram profile extracted bio=%s followers=%s following=%s posts=%s country=%s', bool(biography), followers, following, media, country or '—')
433
+ LOGGER.debug('Instagram profile extracted keys=%s edge_follow=%r bio=%s followers=%s following=%s posts=%s country=%s', sorted(str(key) for key in user.keys()), following_node, bool(biography), followers, following, media, country or '—')
441
434
  return {
442
435
  'id': f"instagram_{reference['username']}",
443
436
  **reference,
@@ -617,6 +610,7 @@ def _post_from_node(node: dict[str, Any]) -> dict[str, Any] | None:
617
610
 
618
611
 
619
612
  def _fetch_posts_with_playwright(reference: Mapping[str, str], limit: int, cookies: Mapping[str, str]) -> list[dict[str, Any]]:
613
+ # CORREÇÃO WINDOWS: posts são extraídos exclusivamente do JSON/HTML obtido pelo Playwright.
620
614
  posts: list[dict[str, Any]] = []
621
615
  seen: set[str] = set()
622
616
  try:
@@ -635,6 +629,7 @@ def _fetch_posts_with_playwright(reference: Mapping[str, str], limit: int, cooki
635
629
  if response is None or response.status >= 400:
636
630
  return []
637
631
  browser_document = page.content()
632
+ LOGGER.debug('Instagram Playwright posts document_bytes=%s', len(browser_document))
638
633
  for document in _embedded_json_documents(browser_document):
639
634
  for node in _walk_json(document):
640
635
  post = _post_from_node(node)
@@ -642,6 +637,7 @@ def _fetch_posts_with_playwright(reference: Mapping[str, str], limit: int, cooki
642
637
  continue
643
638
  seen.add(post['id'])
644
639
  posts.append(post)
640
+ LOGGER.debug('Instagram Playwright post extracted id=%s', post['id'])
645
641
  if len(posts) >= max(1, int(limit)):
646
642
  return posts
647
643
  finally:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.49",
3
+ "version": "0.6.50",
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",