@danhachuel/thunderbolt 0.6.60 → 0.6.61

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.
@@ -185,23 +185,45 @@ def _metric_text_value(value: Any) -> int | None:
185
185
  return int(digits) * multiplier
186
186
 
187
187
 
188
+ def _extract_bio_from_html(document: str) -> str:
189
+ """Extract only the JSON biography value; never use an SEO description as Bio."""
190
+ patterns = (
191
+ r'"biography"\s*:\s*"((?:\\.|[^"\\])*)"',
192
+ r"'biography'\s*:\s*'((?:\\.|[^'\\])*)'",
193
+ )
194
+ for pattern in patterns:
195
+ for match in re.finditer(pattern, document, flags=re.IGNORECASE | re.DOTALL):
196
+ raw_value = match.group(1)
197
+ try:
198
+ decoded = json.loads(f'"{raw_value}"')
199
+ except (TypeError, ValueError, json.JSONDecodeError):
200
+ decoded = bytes(raw_value, 'utf-8').decode('unicode_escape')
201
+ if str(decoded or '').strip():
202
+ return str(decoded).strip()
203
+ return ''
204
+
205
+
188
206
  def _embedded_profile_user(document: str, username: str) -> Mapping[str, Any]:
189
- wanted = username.casefold()
207
+ wanted = str(username or '').strip().lstrip('@').casefold()
190
208
  for payload in _embedded_json_documents(document):
191
209
  for node in _walk_json(payload):
192
210
  if not isinstance(node, Mapping):
193
211
  continue
194
- node_username = str(node.get('username') or node.get('handle') or '').lstrip('@').casefold()
195
- if node_username == wanted and any(key in node for key in ('biography', 'bio', 'edge_followed_by', 'edge_follow', 'followers', 'following')):
212
+ node_username = str(node.get('username') or node.get('handle') or '').strip().lstrip('@').casefold()
213
+ has_profile_fields = any(key in node for key in ('biography', 'bio', 'edge_followed_by', 'edge_follow', 'followers', 'following'))
214
+ if node_username == wanted and has_profile_fields:
196
215
  return node
197
216
  return {}
198
217
 
199
218
 
200
219
  def _extract_profile_user_from_html(document: str, username: str) -> dict[str, Any]:
201
220
  embedded_user = _embedded_profile_user(document, username)
221
+ regex_biography = _extract_bio_from_html(document)
202
222
  seo_description = _meta(document, 'og:description')
203
- biography = str(embedded_user.get('biography') or embedded_user.get('bio') or '').strip()
204
- description = biography or seo_description
223
+ biography = str(embedded_user.get('biography') or embedded_user.get('bio') or regex_biography or '').strip()
224
+ print(f'embedded_user for @{username}: {embedded_user}')
225
+ print(f'biography extracted for @{username}: {biography!r}')
226
+ description = biography
205
227
  title = str(embedded_user.get('full_name') or embedded_user.get('name') or _meta(document, 'og:title') or username).split('(')[0].strip() or username
206
228
  avatar_url = str(embedded_user.get('profile_pic_url_hd') or embedded_user.get('profile_pic_url') or _meta(document, 'og:image')).strip()
207
229
 
@@ -235,7 +257,7 @@ def _extract_profile_user_from_html(document: str, username: str) -> dict[str, A
235
257
  'profile_pic_url': avatar_url,
236
258
  '_html_posts': posts,
237
259
  }
238
- return user if any((description, followers, following, post_count, country, posts)) else {}
260
+ return user if any((biography, followers, following, post_count, country, posts)) else {}
239
261
 
240
262
 
241
263
  def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.60",
3
+ "version": "0.6.61",
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",