@danhachuel/thunderbolt 0.6.39 → 0.6.41
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.
|
@@ -124,6 +124,12 @@ def _instagram_profile_storage_id(profile: Mapping[str, Any]) -> str:
|
|
|
124
124
|
return re.sub(r"[^A-Za-z0-9_.-]+", "_", value) or "unknown"
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
def _clear_instagram_result_widget_state(profile: Mapping[str, Any]) -> None:
|
|
128
|
+
widget_id = _instagram_profile_storage_id(profile)
|
|
129
|
+
for field in ("name", "bio", "country", "language", "posts", "followers", "following", "character"):
|
|
130
|
+
st.session_state.pop(f"social_instagram_result_{field}_{widget_id}", None)
|
|
131
|
+
|
|
132
|
+
|
|
127
133
|
def _normalise_instagram_posts(value: Any) -> list[dict[str, Any]]:
|
|
128
134
|
if not isinstance(value, list):
|
|
129
135
|
return []
|
|
@@ -583,6 +589,8 @@ def render_social_networks(settings: dict[str, Any]) -> None:
|
|
|
583
589
|
if search_clicked:
|
|
584
590
|
result = fetch_public_instagram_profile(source)
|
|
585
591
|
result_data = dict(result.data) if isinstance(result.data, dict) else {}
|
|
592
|
+
if result_data:
|
|
593
|
+
_clear_instagram_result_widget_state(result_data)
|
|
586
594
|
st.session_state["social_instagram_result"] = result_data
|
|
587
595
|
st.session_state["social_instagram_ok"] = result.ok
|
|
588
596
|
st.session_state["social_instagram_message"] = result.message
|
|
@@ -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 {})
|
|
@@ -152,6 +159,17 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
|
152
159
|
merged[key] = value
|
|
153
160
|
return merged
|
|
154
161
|
|
|
162
|
+
def extract_user(payload: Any) -> Mapping[str, Any] | None:
|
|
163
|
+
if not isinstance(payload, Mapping):
|
|
164
|
+
return None
|
|
165
|
+
candidates = [
|
|
166
|
+
payload.get('user'),
|
|
167
|
+
(payload.get('data') or {}).get('user') if isinstance(payload.get('data'), Mapping) else None,
|
|
168
|
+
(payload.get('graphql') or {}).get('user') if isinstance(payload.get('graphql'), Mapping) else None,
|
|
169
|
+
(payload.get('data') or {}).get('profile') if isinstance(payload.get('data'), Mapping) else None,
|
|
170
|
+
]
|
|
171
|
+
return next((candidate for candidate in candidates if isinstance(candidate, Mapping)), None)
|
|
172
|
+
|
|
155
173
|
request_headers = [
|
|
156
174
|
headers,
|
|
157
175
|
{**headers, 'User-Agent': 'Instagram 390.0.0.0.50 Android', 'Accept': 'application/json, text/plain, */*'},
|
|
@@ -169,7 +187,7 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
|
169
187
|
if best_user:
|
|
170
188
|
return best_user
|
|
171
189
|
continue
|
|
172
|
-
user = (
|
|
190
|
+
user = extract_user(payload)
|
|
173
191
|
if isinstance(user, dict):
|
|
174
192
|
best_user = merge_profile(best_user, user)
|
|
175
193
|
if profile_quality(best_user) >= 4:
|
|
@@ -189,7 +207,7 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
|
189
207
|
check=False,
|
|
190
208
|
)
|
|
191
209
|
payload = json.loads(completed.stdout) if completed.returncode == 0 and completed.stdout else None
|
|
192
|
-
user = (
|
|
210
|
+
user = extract_user(payload)
|
|
193
211
|
if isinstance(user, dict):
|
|
194
212
|
best_user = merge_profile(best_user, user)
|
|
195
213
|
if profile_quality(best_user) >= 4:
|
|
@@ -215,7 +233,7 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
|
215
233
|
if response is None or response.status >= 400:
|
|
216
234
|
continue
|
|
217
235
|
payload = json.loads(page.locator('body').inner_text(timeout=5000))
|
|
218
|
-
user = (
|
|
236
|
+
user = extract_user(payload)
|
|
219
237
|
if isinstance(user, dict):
|
|
220
238
|
best_user = merge_profile(best_user, user)
|
|
221
239
|
if profile_quality(best_user) >= 4:
|
|
@@ -289,12 +307,13 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
|
|
|
289
307
|
followers = followers if followers is not None else _structured_metric_from_json(json.dumps(user), 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
|
|
290
308
|
following = following if following is not None else _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
|
|
291
309
|
media = ((user.get('edge_owner_to_timeline_media') or {}).get('count') if isinstance(user.get('edge_owner_to_timeline_media'), dict) else None)
|
|
292
|
-
|
|
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()
|
|
293
312
|
country = extract_public_instagram_country(user) or _fetch_instagram_about_country(user.get('id') or user.get('pk'))
|
|
294
313
|
return {
|
|
295
314
|
'id': f"instagram_{reference['username']}",
|
|
296
315
|
**reference,
|
|
297
|
-
'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(),
|
|
298
317
|
'bio': normalize_instagram_bio(biography),
|
|
299
318
|
'bio_raw': biography,
|
|
300
319
|
'country': country,
|
|
@@ -306,7 +325,7 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
|
|
|
306
325
|
'public_lookup': True,
|
|
307
326
|
'metrics_source': 'instagram_web_profile_info',
|
|
308
327
|
'last_public_lookup_at': datetime.now(timezone.utc).isoformat(),
|
|
309
|
-
'_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 [])),
|
|
310
329
|
}
|
|
311
330
|
|
|
312
331
|
|
package/package.json
CHANGED