@danhachuel/thunderbolt 0.6.40 → 0.6.42
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.
|
@@ -5,6 +5,7 @@ import re
|
|
|
5
5
|
import uuid
|
|
6
6
|
import zipfile
|
|
7
7
|
import unicodedata
|
|
8
|
+
import hashlib
|
|
8
9
|
from typing import Any, Callable, Mapping
|
|
9
10
|
|
|
10
11
|
import requests
|
|
@@ -124,6 +125,19 @@ def _instagram_profile_storage_id(profile: Mapping[str, Any]) -> str:
|
|
|
124
125
|
return re.sub(r"[^A-Za-z0-9_.-]+", "_", value) or "unknown"
|
|
125
126
|
|
|
126
127
|
|
|
128
|
+
def _clear_instagram_result_widget_state(profile: Mapping[str, Any]) -> None:
|
|
129
|
+
widget_id = _instagram_profile_storage_id(profile)
|
|
130
|
+
for field in ("name", "bio", "country", "language", "posts", "followers", "following", "character"):
|
|
131
|
+
st.session_state.pop(f"social_instagram_result_{field}_{widget_id}", None)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _instagram_result_widget_id(profile: Mapping[str, Any]) -> str:
|
|
135
|
+
base_id = _instagram_profile_storage_id(profile)
|
|
136
|
+
signature = "|".join(_clean(profile.get(key)) for key in ("bio", "bio_raw", "following_count", "subscriber_count", "post_count", "country"))
|
|
137
|
+
digest = hashlib.sha1(signature.encode("utf-8")).hexdigest()[:10]
|
|
138
|
+
return f"{base_id}_{digest}"
|
|
139
|
+
|
|
140
|
+
|
|
127
141
|
def _normalise_instagram_posts(value: Any) -> list[dict[str, Any]]:
|
|
128
142
|
if not isinstance(value, list):
|
|
129
143
|
return []
|
|
@@ -583,6 +597,8 @@ def render_social_networks(settings: dict[str, Any]) -> None:
|
|
|
583
597
|
if search_clicked:
|
|
584
598
|
result = fetch_public_instagram_profile(source)
|
|
585
599
|
result_data = dict(result.data) if isinstance(result.data, dict) else {}
|
|
600
|
+
if result_data:
|
|
601
|
+
_clear_instagram_result_widget_state(result_data)
|
|
586
602
|
st.session_state["social_instagram_result"] = result_data
|
|
587
603
|
st.session_state["social_instagram_ok"] = result.ok
|
|
588
604
|
st.session_state["social_instagram_message"] = result.message
|
|
@@ -591,7 +607,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
|
|
|
591
607
|
data = st.session_state.get("social_instagram_result", {}) if st.session_state.get("social_instagram_ok") else {}
|
|
592
608
|
if data:
|
|
593
609
|
characters, _ = _characters(settings)
|
|
594
|
-
result_widget_id =
|
|
610
|
+
result_widget_id = _instagram_result_widget_id(data)
|
|
595
611
|
with st.container(border=True):
|
|
596
612
|
st.subheader("Conta Instagram encontrada")
|
|
597
613
|
preview_cols = st.columns([0.8, 2.2, 1.2, 1.2, 1.2])
|
|
@@ -140,7 +140,14 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
|
140
140
|
f'https://i.instagram.com/api/v1/users/web_profile_info/?username={username}',
|
|
141
141
|
)
|
|
142
142
|
def profile_quality(user: Mapping[str, Any]) -> int:
|
|
143
|
-
|
|
143
|
+
groups = (
|
|
144
|
+
('biography', 'bio', 'description'),
|
|
145
|
+
('edge_follow', 'following', 'following_count', 'followingCount', 'follows'),
|
|
146
|
+
('edge_followed_by', 'followers', 'follower_count', 'followers_count', 'followerCount'),
|
|
147
|
+
('edge_owner_to_timeline_media', 'posts', 'post_count', 'media_count'),
|
|
148
|
+
('username', 'user_name', 'handle'),
|
|
149
|
+
)
|
|
150
|
+
return sum(any(user.get(key) not in (None, '', {}, []) for key in group) for group in groups)
|
|
144
151
|
|
|
145
152
|
def merge_profile(base: dict[str, Any] | None, candidate: Mapping[str, Any]) -> dict[str, Any]:
|
|
146
153
|
merged = dict(base or {})
|
|
@@ -300,12 +307,13 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
|
|
|
300
307
|
followers = followers if followers is not None else _structured_metric_from_json(json.dumps(user), 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
|
|
301
308
|
following = following if following is not None else _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
|
|
302
309
|
media = ((user.get('edge_owner_to_timeline_media') or {}).get('count') if isinstance(user.get('edge_owner_to_timeline_media'), dict) else None)
|
|
303
|
-
|
|
310
|
+
media = media if media is not None else _profile_metric(user, 'posts', 'post_count', 'media_count')
|
|
311
|
+
biography = str(user.get('biography') or user.get('bio') or user.get('description') or '').strip()
|
|
304
312
|
country = extract_public_instagram_country(user) or _fetch_instagram_about_country(user.get('id') or user.get('pk'))
|
|
305
313
|
return {
|
|
306
314
|
'id': f"instagram_{reference['username']}",
|
|
307
315
|
**reference,
|
|
308
|
-
'name': str(user.get('full_name') or user.get('username') or reference['username']).strip(),
|
|
316
|
+
'name': str(user.get('full_name') or user.get('name') or user.get('username') or reference['username']).strip(),
|
|
309
317
|
'bio': normalize_instagram_bio(biography),
|
|
310
318
|
'bio_raw': biography,
|
|
311
319
|
'country': country,
|
|
@@ -317,7 +325,7 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
|
|
|
317
325
|
'public_lookup': True,
|
|
318
326
|
'metrics_source': 'instagram_web_profile_info',
|
|
319
327
|
'last_public_lookup_at': datetime.now(timezone.utc).isoformat(),
|
|
320
|
-
'_api_posts': (((user.get('edge_owner_to_timeline_media') or {}).get('edges') or []) if isinstance(user.get('edge_owner_to_timeline_media'), dict) else []),
|
|
328
|
+
'_api_posts': (((user.get('edge_owner_to_timeline_media') or {}).get('edges') or []) if isinstance(user.get('edge_owner_to_timeline_media'), dict) else (user.get('posts') if isinstance(user.get('posts'), list) else [])),
|
|
321
329
|
}
|
|
322
330
|
|
|
323
331
|
|
|
@@ -512,6 +520,33 @@ def fetch_public_instagram_posts(source: str, limit: int = 10) -> IntegrationRes
|
|
|
512
520
|
break
|
|
513
521
|
if len(posts) >= max(1, int(limit)):
|
|
514
522
|
break
|
|
523
|
+
if not posts:
|
|
524
|
+
try:
|
|
525
|
+
from playwright.sync_api import sync_playwright
|
|
526
|
+
with sync_playwright() as playwright:
|
|
527
|
+
browser = playwright.chromium.launch(headless=True)
|
|
528
|
+
try:
|
|
529
|
+
page = browser.new_page(
|
|
530
|
+
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0 Safari/537.36',
|
|
531
|
+
extra_http_headers={'x-ig-app-id': '936619743392459', 'Accept-Language': 'en-US,en;q=0.9'},
|
|
532
|
+
)
|
|
533
|
+
page.goto(reference['url'], wait_until='domcontentloaded', timeout=30000)
|
|
534
|
+
browser_document = page.content()
|
|
535
|
+
for document in _embedded_json_documents(browser_document):
|
|
536
|
+
for node in _walk_json(document):
|
|
537
|
+
post = _post_from_node(node)
|
|
538
|
+
if not post or post['id'] in seen:
|
|
539
|
+
continue
|
|
540
|
+
seen.add(post['id'])
|
|
541
|
+
posts.append(post)
|
|
542
|
+
if len(posts) >= max(1, int(limit)):
|
|
543
|
+
break
|
|
544
|
+
if len(posts) >= max(1, int(limit)):
|
|
545
|
+
break
|
|
546
|
+
finally:
|
|
547
|
+
browser.close()
|
|
548
|
+
except (ImportError, OSError, RuntimeError, TimeoutError):
|
|
549
|
+
pass
|
|
515
550
|
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))]})
|
|
516
551
|
|
|
517
552
|
|
package/package.json
CHANGED