@danhachuel/thunderbolt 0.6.15 → 0.6.17

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.
@@ -8,6 +8,7 @@ import streamlit as st
8
8
  from hermes_ui.domain import create_channel, update_channel
9
9
  from hermes_ui.influencers import InfluencerBackendError, STANDALONE_CONTENT_INFLUENCER_ID, get_repository
10
10
  from hermes_ui.storage import read_json, write_json
11
+ from hermes_ui.languages import LANGUAGE_CODES, language_code, language_label
11
12
  from integrations.instagram_public import fetch_public_instagram_profile
12
13
  from integrations.meta_social import test_facebook_pages_api_card, test_instagram_api_card
13
14
 
@@ -25,6 +26,11 @@ def _metric(value: Any) -> str:
25
26
  return _clean(value) or "—"
26
27
 
27
28
 
29
+ def _language_index(value: Any) -> int:
30
+ code = language_code(value)
31
+ return list(LANGUAGE_CODES).index(code) if code in LANGUAGE_CODES else list(LANGUAGE_CODES).index("pt")
32
+
33
+
28
34
  def _api_card_defaults(kind: str) -> tuple[str, list[str], Callable[[Mapping[str, Any]], dict[str, Any]]]:
29
35
  if kind == "instagram":
30
36
  return "instagram_api_cards", ["id", "label", "account_id", "access_token"], test_instagram_api_card
@@ -152,7 +158,16 @@ def render_meta_api_cards(settings: dict[str, Any], kind: str) -> None:
152
158
 
153
159
  def _instagram_profiles() -> list[dict[str, Any]]:
154
160
  records = read_json("channels.json", [])
155
- return [item for item in records if isinstance(item, dict) and _clean(item.get("platform")).lower() == "instagram"]
161
+ profiles: list[dict[str, Any]] = []
162
+ for item in records:
163
+ if not isinstance(item, dict):
164
+ continue
165
+ platform = _clean(item.get("platform")).lower()
166
+ network = _clean(item.get("social_network")).lower()
167
+ url = _clean(item.get("url")).lower()
168
+ if platform == "instagram" or network == "instagram" or "instagram.com/" in url:
169
+ profiles.append(item)
170
+ return profiles
156
171
 
157
172
 
158
173
  def _characters(settings: Mapping[str, Any]) -> tuple[list[dict[str, Any]], Any | None]:
@@ -231,14 +246,14 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
231
246
  name = st.text_input("Nome", value=_clean(profile.get("name")), key=f"instagram_edit_name_{profile_id}")
232
247
  handle = st.text_input("handler", value=_clean(profile.get("handle")), key=f"instagram_edit_handle_{profile_id}")
233
248
  country = st.text_input("País", value=_clean(profile.get("country")), key=f"instagram_edit_country_{profile_id}")
234
- language = st.text_input("Idioma", value=_clean(profile.get("language")), key=f"instagram_edit_language_{profile_id}")
249
+ language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(profile.get("language")), format_func=language_label, key=f"instagram_edit_language_{profile_id}")
235
250
  with edit_cols[1]:
236
251
  posts = st.number_input("posts", min_value=0, value=int(profile.get("post_count") or 0), key=f"instagram_edit_posts_{profile_id}")
237
252
  followers = st.number_input("Seguidores", min_value=0, value=int(profile.get("subscriber_count") or 0), key=f"instagram_edit_followers_{profile_id}")
238
253
  following = st.number_input("seguindo", min_value=0, value=int(profile.get("following_count") or 0), key=f"instagram_edit_following_{profile_id}")
239
254
  save_edit = st.form_submit_button("Guardar alterações", type="primary", use_container_width=True)
240
255
  if save_edit:
241
- update_channel(profile_id, {"name": name.strip(), "handle": handle.strip(), "country": country.strip(), "language": language.strip(), "post_count": int(posts), "subscriber_count": int(followers), "following_count": int(following)})
256
+ update_channel(profile_id, {"name": name.strip(), "handle": handle.strip(), "country": country.strip(), "language": language, "post_count": int(posts), "subscriber_count": int(followers), "following_count": int(following)})
242
257
  st.session_state.pop(edit_key, None)
243
258
  st.success("Conta Instagram actualizada.")
244
259
  st.rerun()
@@ -308,7 +323,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
308
323
  with form_cols[0]:
309
324
  name = st.text_input("Nome", value=_clean(data.get("name")), key="social_instagram_result_name")
310
325
  country = st.text_input("País", value=_clean(data.get("country")), key="social_instagram_result_country")
311
- language = st.text_input("Idioma", value=_clean(data.get("language")), key="social_instagram_result_language")
326
+ language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(data.get("language")), format_func=language_label, key="social_instagram_result_language")
312
327
  with form_cols[1]:
313
328
  posts = st.number_input("posts", min_value=0, value=int(data.get("post_count") or 0), key="social_instagram_result_posts")
314
329
  followers = st.number_input("Seguidores", min_value=0, value=int(data.get("subscriber_count") or 0), key="social_instagram_result_followers")
@@ -33,6 +33,8 @@ def create_channel(name: str, url: str = "", metadata: dict[str, Any] | None = N
33
33
  "google_account_email": "",
34
34
  "name": name.strip(),
35
35
  "url": url.strip(),
36
+ "platform": "youtube",
37
+ "social_network": "YouTube",
36
38
  "handle": "",
37
39
  "description": "",
38
40
  "description_source": "",
@@ -41,9 +43,15 @@ def create_channel(name: str, url: str = "", metadata: dict[str, Any] | None = N
41
43
  "thumbnail_url": "",
42
44
  "avatar_url": "",
43
45
  "subscriber_count": None,
46
+ "post_count": None,
47
+ "following_count": None,
44
48
  "video_count": None,
45
49
  "view_count": None,
46
50
  "metrics_source": "manual",
51
+ "public_lookup": False,
52
+ "last_public_lookup_at": None,
53
+ "country": "",
54
+ "character_id": "",
47
55
  "last_youtube_sync": None,
48
56
  "language": "Português",
49
57
  "blueprint_id": "",
@@ -48,6 +48,24 @@ def _meta(document: str, name: str) -> str:
48
48
  return ''
49
49
 
50
50
 
51
+ def _structured_metric(document: str, *keys: str) -> int | None:
52
+ """Read numeric Instagram counters embedded in the public HTML payload."""
53
+ document = unescape(document)
54
+ for key in keys:
55
+ patterns = (
56
+ rf'\\?["\']{re.escape(key)}\\?["\']\s*:\s*\{{[^}}]{{0,240}}?\\?["\']count\\?["\']\s*:\s*(\d+)',
57
+ rf'\\?["\']{re.escape(key)}\\?["\']\s*:\s*(\d+)',
58
+ )
59
+ for pattern in patterns:
60
+ found = re.search(pattern, document, flags=re.IGNORECASE)
61
+ if found:
62
+ try:
63
+ return int(found.group(1))
64
+ except ValueError:
65
+ continue
66
+ return None
67
+
68
+
51
69
  def fetch_public_instagram_profile(source: str) -> IntegrationResult:
52
70
  try:
53
71
  reference = normalize_instagram_reference(source)
@@ -73,9 +91,9 @@ def fetch_public_instagram_profile(source: str) -> IntegrationResult:
73
91
  continue
74
92
  return None
75
93
 
76
- followers = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:followers|seguidores)',))
77
- following = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:following|seguindo)',))
78
- posts = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:posts|publicações|publications)',))
94
+ followers = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:followers|seguidores)',)) or _structured_metric(response.text, 'edge_followed_by', 'followers', 'follower_count')
95
+ following = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:following|seguindo)',)) or _structured_metric(response.text, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
96
+ posts = metric((r'([\d,.]+)\s*(?:mil\s+)?(?:posts|publicações|publications)',)) or _structured_metric(response.text, 'edge_owner_to_timeline_media', 'posts', 'post_count')
79
97
  data = {'id': f"instagram_{reference['username']}", **reference, 'name': title.split('(')[0].strip() or reference['username'], 'bio': description, 'avatar_url': avatar_url, 'subscriber_count': followers, 'following_count': following, 'post_count': posts, 'public_lookup': True, 'metrics_source': 'instagram_public_page', 'last_public_lookup_at': datetime.now(timezone.utc).isoformat()}
80
98
  return IntegrationResult(True, 'Perfil Instagram encontrado publicamente.', data)
81
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.15",
3
+ "version": "0.6.17",
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",