@danhachuel/thunderbolt 0.6.24 → 0.6.25
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.
- package/integrations/instagram_public.py +46 -21
- package/package.json +1 -1
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
5
7
|
from dataclasses import dataclass
|
|
6
8
|
from datetime import datetime, timezone
|
|
7
9
|
from html import unescape
|
|
@@ -96,24 +98,47 @@ def _structured_metric_from_json(document: str, *keys: str) -> int | None:
|
|
|
96
98
|
|
|
97
99
|
|
|
98
100
|
def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
headers = {
|
|
102
|
+
'x-ig-app-id': '936619743392459',
|
|
103
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0 Safari/537.36',
|
|
104
|
+
'Accept': 'application/json, text/plain, */*',
|
|
105
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
106
|
+
}
|
|
107
|
+
# The www host is the current public web endpoint. The legacy i host is
|
|
108
|
+
# retained as a fallback because Instagram rate-limits the two hosts
|
|
109
|
+
# independently and their availability can vary by region.
|
|
110
|
+
endpoints = (
|
|
111
|
+
f'https://www.instagram.com/api/v1/users/web_profile_info/?username={username}',
|
|
112
|
+
f'https://i.instagram.com/api/v1/users/web_profile_info/?username={username}',
|
|
113
|
+
)
|
|
114
|
+
for endpoint in endpoints:
|
|
115
|
+
try:
|
|
116
|
+
response = requests.get(endpoint, headers=headers, timeout=15)
|
|
117
|
+
if response.status_code >= 400:
|
|
118
|
+
continue
|
|
119
|
+
payload = response.json()
|
|
120
|
+
except (requests.RequestException, ValueError, AttributeError):
|
|
121
|
+
continue
|
|
122
|
+
user = ((payload.get('data') or {}).get('user') if isinstance(payload, dict) else None)
|
|
123
|
+
if isinstance(user, dict):
|
|
124
|
+
return user
|
|
125
|
+
curl = shutil.which('curl')
|
|
126
|
+
if curl:
|
|
127
|
+
try:
|
|
128
|
+
completed = subprocess.run(
|
|
129
|
+
[curl, '-L', '--max-time', '20', '-sS', '-A', headers['User-Agent'], '-H', f"x-ig-app-id: {headers['x-ig-app-id']}", endpoint],
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True,
|
|
132
|
+
timeout=25,
|
|
133
|
+
check=False,
|
|
134
|
+
)
|
|
135
|
+
payload = json.loads(completed.stdout) if completed.returncode == 0 and completed.stdout else None
|
|
136
|
+
user = ((payload.get('data') or {}).get('user') if isinstance(payload, dict) else None)
|
|
137
|
+
if isinstance(user, dict):
|
|
138
|
+
return user
|
|
139
|
+
except (OSError, subprocess.SubprocessError, ValueError):
|
|
140
|
+
pass
|
|
141
|
+
return None
|
|
117
142
|
|
|
118
143
|
|
|
119
144
|
def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]) -> dict[str, Any]:
|
|
@@ -161,15 +186,15 @@ def fetch_public_instagram_profile(source: str) -> IntegrationResult:
|
|
|
161
186
|
reference = normalize_instagram_reference(source)
|
|
162
187
|
except ValueError as exc:
|
|
163
188
|
return IntegrationResult(False, str(exc), {})
|
|
189
|
+
api_user = _fetch_web_profile_user(reference['username'])
|
|
190
|
+
if api_user:
|
|
191
|
+
return IntegrationResult(True, 'Perfil Instagram encontrado publicamente.', _profile_data_from_api(api_user, reference))
|
|
164
192
|
try:
|
|
165
193
|
response = requests.get(reference['url'], headers={'User-Agent': 'Mozilla/5.0', 'Accept-Language': 'pt-BR,pt;q=0.9,en;q=0.8'}, timeout=12)
|
|
166
194
|
except requests.RequestException as exc:
|
|
167
195
|
return IntegrationResult(False, f'Não foi possível consultar o perfil público do Instagram: {exc}', reference)
|
|
168
196
|
if response.status_code >= 400:
|
|
169
197
|
return IntegrationResult(False, f'O Instagram devolveu HTTP {response.status_code}. Confirme o @handle ou use o cadastro manual.', reference | {'status_code': response.status_code})
|
|
170
|
-
api_user = _fetch_web_profile_user(reference['username'])
|
|
171
|
-
if api_user:
|
|
172
|
-
return IntegrationResult(True, 'Perfil Instagram encontrado publicamente.', _profile_data_from_api(api_user, reference))
|
|
173
198
|
title = _meta(response.text, 'og:title') or reference['username']
|
|
174
199
|
description = _meta(response.text, 'og:description')
|
|
175
200
|
avatar_url = _meta(response.text, 'og:image')
|
package/package.json
CHANGED