@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.
@@ -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
- try:
100
- response = requests.get(
101
- f'https://i.instagram.com/api/v1/users/web_profile_info/?username={username}',
102
- headers={
103
- 'x-ig-app-id': '936619743392459',
104
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0 Safari/537.36',
105
- 'Accept': '*/*',
106
- 'Accept-Language': 'en-US,en;q=0.9',
107
- },
108
- timeout=15,
109
- )
110
- if response.status_code >= 400:
111
- return None
112
- payload = response.json()
113
- except (requests.RequestException, ValueError, AttributeError):
114
- return None
115
- user = ((payload.get('data') or {}).get('user') if isinstance(payload, dict) else None)
116
- return user if isinstance(user, dict) else None
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.24",
3
+ "version": "0.6.25",
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",