@danhachuel/thunderbolt 0.6.34 → 0.6.35
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.
|
@@ -591,6 +591,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
|
|
|
591
591
|
data = st.session_state.get("social_instagram_result", {}) if st.session_state.get("social_instagram_ok") else {}
|
|
592
592
|
if data:
|
|
593
593
|
characters, _ = _characters(settings)
|
|
594
|
+
result_widget_id = _instagram_profile_storage_id(data)
|
|
594
595
|
with st.container(border=True):
|
|
595
596
|
st.subheader("Conta Instagram encontrada")
|
|
596
597
|
preview_cols = st.columns([0.8, 2.2, 1.2, 1.2, 1.2])
|
|
@@ -607,20 +608,20 @@ def render_social_networks(settings: dict[str, Any]) -> None:
|
|
|
607
608
|
st.metric("Seguidores", _metric(_profile_metric(data, "subscriber_count", "followers_count", "follower_count", "followers", "edge_followed_by")))
|
|
608
609
|
with preview_cols[4]:
|
|
609
610
|
st.metric("seguindo", _metric(_profile_metric(data, "following_count", "following", "follows", "followingCount", "edge_follow")))
|
|
610
|
-
with st.form("
|
|
611
|
+
with st.form(f"social_instagram_save_public_profile_{result_widget_id}"):
|
|
611
612
|
form_cols = st.columns(2)
|
|
612
613
|
with form_cols[0]:
|
|
613
|
-
name = st.text_input("Nome", value=_clean(data.get("name")), key="
|
|
614
|
-
bio = st.text_area("Bio", value=_profile_bio(data), key="
|
|
615
|
-
country = st.selectbox("País", _country_options(), index=_country_index(_profile_country(data)), format_func=_country_label, key="
|
|
616
|
-
language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(data.get("language")), format_func=language_label, key="
|
|
614
|
+
name = st.text_input("Nome", value=_clean(data.get("name")), key=f"social_instagram_result_name_{result_widget_id}")
|
|
615
|
+
bio = st.text_area("Bio", value=_profile_bio(data), key=f"social_instagram_result_bio_{result_widget_id}")
|
|
616
|
+
country = st.selectbox("País", _country_options(), index=_country_index(_profile_country(data)), format_func=_country_label, key=f"social_instagram_result_country_{result_widget_id}")
|
|
617
|
+
language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(data.get("language")), format_func=language_label, key=f"social_instagram_result_language_{result_widget_id}")
|
|
617
618
|
with form_cols[1]:
|
|
618
|
-
posts = st.number_input("posts", min_value=0, value=_metric_input(_profile_metric(data, "post_count", "posts", "media_count")), placeholder="Não encontrado", key="
|
|
619
|
-
followers = st.number_input("Seguidores", min_value=0, value=_metric_input(_profile_metric(data, "subscriber_count", "followers_count", "follower_count", "followers", "edge_followed_by")), placeholder="Não encontrado", key="
|
|
620
|
-
following = st.number_input("seguindo", min_value=0, value=_metric_input(_profile_metric(data, "following_count", "following", "follows", "followingCount", "edge_follow")), placeholder="Não encontrado", key="
|
|
619
|
+
posts = st.number_input("posts", min_value=0, value=_metric_input(_profile_metric(data, "post_count", "posts", "media_count")), placeholder="Não encontrado", key=f"social_instagram_result_posts_{result_widget_id}")
|
|
620
|
+
followers = st.number_input("Seguidores", min_value=0, value=_metric_input(_profile_metric(data, "subscriber_count", "followers_count", "follower_count", "followers", "edge_followed_by")), placeholder="Não encontrado", key=f"social_instagram_result_followers_{result_widget_id}")
|
|
621
|
+
following = st.number_input("seguindo", min_value=0, value=_metric_input(_profile_metric(data, "following_count", "following", "follows", "followingCount", "edge_follow")), placeholder="Não encontrado", key=f"social_instagram_result_following_{result_widget_id}")
|
|
621
622
|
character_options = [""] + [_clean(item.get("id")) for item in characters if _clean(item.get("id"))]
|
|
622
623
|
character_labels = {"": "Sem personagem associado"} | {_clean(item.get("id")): _clean(item.get("name")) or _clean(item.get("id")) for item in characters}
|
|
623
|
-
selected_character = st.selectbox("Personagem", character_options, format_func=lambda value: character_labels.get(value, value), key="
|
|
624
|
+
selected_character = st.selectbox("Personagem", character_options, format_func=lambda value: character_labels.get(value, value), key=f"social_instagram_result_character_{result_widget_id}")
|
|
624
625
|
save_profile = st.form_submit_button("Cadastrar conta Instagram", type="primary", use_container_width=True)
|
|
625
626
|
if save_profile:
|
|
626
627
|
profile_data = dict(data)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
5
6
|
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
from dataclasses import dataclass
|
|
@@ -13,6 +14,31 @@ from urllib.parse import urlparse
|
|
|
13
14
|
import requests
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
ABOUT_ACCOUNT_URL = 'https://i.instagram.com/api/v1/bloks/apps/com.instagram.interactions.about_this_account/'
|
|
18
|
+
ABOUT_ACCOUNT_BLOKS_VERSION = '8ca96ca267e30c02cf90888d91eeff09627f0e3fd2bd9df472278c9a6c022cbb'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _instagram_cookies() -> dict[str, str]:
|
|
22
|
+
cookies = {}
|
|
23
|
+
sessionid = os.getenv('INSTAGRAM_SESSIONID') or os.getenv('IG_SESSIONID')
|
|
24
|
+
if sessionid:
|
|
25
|
+
cookies['sessionid'] = sessionid.strip()
|
|
26
|
+
for name in ('csrftoken', 'ds_user_id', 'mid', 'ig_did'):
|
|
27
|
+
value = os.getenv(f'INSTAGRAM_{name.upper()}') or os.getenv(f'IG_{name.upper()}')
|
|
28
|
+
if value:
|
|
29
|
+
cookies[name] = value.strip()
|
|
30
|
+
return cookies
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _instagram_headers() -> dict[str, str]:
|
|
34
|
+
return {
|
|
35
|
+
'x-ig-app-id': '936619743392459',
|
|
36
|
+
'User-Agent': 'Instagram 390.0.0.0.50 Android',
|
|
37
|
+
'Accept': 'application/json, text/plain, */*',
|
|
38
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
16
42
|
@dataclass
|
|
17
43
|
class IntegrationResult:
|
|
18
44
|
ok: bool
|
|
@@ -104,12 +130,8 @@ def _structured_metric_from_json(document: str, *keys: str) -> int | None:
|
|
|
104
130
|
|
|
105
131
|
|
|
106
132
|
def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
107
|
-
headers =
|
|
108
|
-
|
|
109
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0 Safari/537.36',
|
|
110
|
-
'Accept': 'application/json, text/plain, */*',
|
|
111
|
-
'Accept-Language': 'en-US,en;q=0.9',
|
|
112
|
-
}
|
|
133
|
+
headers = _instagram_headers()
|
|
134
|
+
headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0 Safari/537.36'
|
|
113
135
|
# The www host is the current public web endpoint. The legacy i host is
|
|
114
136
|
# retained as a fallback because Instagram rate-limits the two hosts
|
|
115
137
|
# independently and their availability can vary by region.
|
|
@@ -150,6 +172,60 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
|
150
172
|
return None
|
|
151
173
|
|
|
152
174
|
|
|
175
|
+
def _country_from_bloks(value: Any) -> str:
|
|
176
|
+
if isinstance(value, Mapping):
|
|
177
|
+
data_key = str(value.get('key') or '').casefold()
|
|
178
|
+
if data_key in PUBLIC_COUNTRY_KEYS:
|
|
179
|
+
serialized = value.get('initial_lispy') or value.get('value')
|
|
180
|
+
if isinstance(serialized, str) and 'bk.action.array.Make' in serialized:
|
|
181
|
+
country = _country_value(serialized.rsplit('bk.action.array.Make,', 1)[-1].split(')', 1)[0].replace('\\"', '"').replace('"', '').strip())
|
|
182
|
+
else:
|
|
183
|
+
country = _country_value(serialized)
|
|
184
|
+
if country:
|
|
185
|
+
return country
|
|
186
|
+
for key, candidate in value.items():
|
|
187
|
+
if str(key).casefold() in PUBLIC_COUNTRY_KEYS:
|
|
188
|
+
if isinstance(candidate, str) and 'bk.action.array.Make' in candidate:
|
|
189
|
+
country = _country_value(candidate.rsplit('bk.action.array.Make,', 1)[-1].split(')', 1)[0].replace('\\"', '"').replace('"', '').strip())
|
|
190
|
+
else:
|
|
191
|
+
country = _country_value(candidate)
|
|
192
|
+
if country:
|
|
193
|
+
return country
|
|
194
|
+
country = _country_from_bloks(candidate)
|
|
195
|
+
if country:
|
|
196
|
+
return country
|
|
197
|
+
elif isinstance(value, list):
|
|
198
|
+
for candidate in value:
|
|
199
|
+
country = _country_from_bloks(candidate)
|
|
200
|
+
if country:
|
|
201
|
+
return country
|
|
202
|
+
elif isinstance(value, str) and 'about_this_account_country' in value:
|
|
203
|
+
marker = 'bk.action.array.Make,'
|
|
204
|
+
if marker in value:
|
|
205
|
+
country = value.rsplit(marker, 1)[-1].split(')', 1)[0].replace('\\"', '"').replace('"', '').strip()
|
|
206
|
+
return _country_value(country)
|
|
207
|
+
return ''
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _fetch_instagram_about_country(user_id: Any) -> str:
|
|
211
|
+
cookies = _instagram_cookies()
|
|
212
|
+
if not cookies or not str(user_id or '').strip():
|
|
213
|
+
return ''
|
|
214
|
+
payload = {
|
|
215
|
+
'referer_type': 'ProfileUsername',
|
|
216
|
+
'target_user_id': str(user_id),
|
|
217
|
+
'bk_client_context': json.dumps({'bloks_version': ABOUT_ACCOUNT_BLOKS_VERSION, 'style_id': 'instagram'}, separators=(',', ':')),
|
|
218
|
+
'bloks_versioning_id': ABOUT_ACCOUNT_BLOKS_VERSION,
|
|
219
|
+
}
|
|
220
|
+
try:
|
|
221
|
+
response = requests.post(ABOUT_ACCOUNT_URL, data=payload, headers=_instagram_headers(), cookies=cookies, timeout=20)
|
|
222
|
+
if response.status_code >= 400:
|
|
223
|
+
return ''
|
|
224
|
+
return _country_from_bloks(response.json())
|
|
225
|
+
except (requests.RequestException, ValueError, TypeError):
|
|
226
|
+
return ''
|
|
227
|
+
|
|
228
|
+
|
|
153
229
|
def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]) -> dict[str, Any]:
|
|
154
230
|
followers = _profile_metric(user, 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
|
|
155
231
|
following = _profile_metric(user, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
|
|
@@ -157,7 +233,7 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
|
|
|
157
233
|
following = following if following is not None else _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
|
|
158
234
|
media = ((user.get('edge_owner_to_timeline_media') or {}).get('count') if isinstance(user.get('edge_owner_to_timeline_media'), dict) else None)
|
|
159
235
|
biography = str(user.get('biography') or '').strip()
|
|
160
|
-
country = extract_public_instagram_country(user)
|
|
236
|
+
country = extract_public_instagram_country(user) or _fetch_instagram_about_country(user.get('id') or user.get('pk'))
|
|
161
237
|
return {
|
|
162
238
|
'id': f"instagram_{reference['username']}",
|
|
163
239
|
**reference,
|
|
@@ -188,7 +264,7 @@ def normalize_instagram_metric(value: Any) -> int | None:
|
|
|
188
264
|
|
|
189
265
|
|
|
190
266
|
PUBLIC_COUNTRY_KEYS = {
|
|
191
|
-
'account_based_in', 'account_country', 'country_name',
|
|
267
|
+
'about_this_account_country', 'account_based_in', 'account_country', 'country_name',
|
|
192
268
|
'country_of_origin', 'country_of_registration', 'location_country', 'transparency_country',
|
|
193
269
|
}
|
|
194
270
|
PUBLIC_TRANSPARENCY_KEYS = {'about', 'about_account', 'account_transparency', 'transparency', 'account_info'}
|
package/package.json
CHANGED