@danhachuel/thunderbolt 0.6.22 → 0.6.23

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,9 +8,10 @@ from typing import Any, Callable, Mapping
8
8
  import requests
9
9
  import streamlit as st
10
10
 
11
- from hermes_ui.domain import create_channel, update_channel
11
+ from hermes_ui.domain import create_channel, delete_channel, update_channel
12
12
  from hermes_ui.influencers import InfluencerBackendError, STANDALONE_CONTENT_INFLUENCER_ID, get_repository
13
13
  from hermes_ui.storage import read_json, write_json
14
+ from hermes_ui.countries import COUNTRY_OPTIONS
14
15
  from hermes_ui.languages import LANGUAGE_CODES, language_code, language_label
15
16
  from integrations.instagram_public import fetch_public_instagram_posts, fetch_public_instagram_profile, normalize_instagram_bio
16
17
  from integrations.meta_social import test_facebook_pages_api_card, test_instagram_api_card
@@ -42,6 +43,20 @@ def _language_index(value: Any) -> int:
42
43
  return list(LANGUAGE_CODES).index(code) if code in LANGUAGE_CODES else list(LANGUAGE_CODES).index("pt")
43
44
 
44
45
 
46
+ def _country_options() -> tuple[str, ...]:
47
+ return ("",) + COUNTRY_OPTIONS
48
+
49
+
50
+ def _country_index(value: Any) -> int:
51
+ options = _country_options()
52
+ value = _clean(value)
53
+ return options.index(value) if value in options else 0
54
+
55
+
56
+ def _country_label(value: str) -> str:
57
+ return value or "Não definido"
58
+
59
+
45
60
  def _instagram_posts_key(profile_id: str) -> str:
46
61
  return f"instagram_posts_{profile_id}"
47
62
 
@@ -326,17 +341,34 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
326
341
  if st.button("Editar", key=f"edit_instagram_button_{profile_id}", use_container_width=True):
327
342
  st.session_state[edit_key] = True
328
343
  st.rerun()
344
+ if st.button("Apagar", key=f"delete_instagram_button_{profile_id}", use_container_width=True):
345
+ st.session_state[f"confirm_delete_instagram_{profile_id}"] = True
346
+ st.rerun()
329
347
  url = _clean(profile.get("url"))
330
348
  if url:
331
349
  st.link_button("Abrir Instagram", url, use_container_width=True)
332
350
 
351
+ if st.session_state.get(f"confirm_delete_instagram_{profile_id}"):
352
+ st.warning("Apagar este card remove o cadastro da conta Instagram, mas não os ficheiros associados.")
353
+ confirm_cols = st.columns(2)
354
+ with confirm_cols[0]:
355
+ if st.button("Confirmar apagar", type="primary", key=f"confirm_delete_instagram_button_{profile_id}", use_container_width=True):
356
+ delete_channel(profile_id)
357
+ st.session_state.pop(f"confirm_delete_instagram_{profile_id}", None)
358
+ st.success("Conta Instagram apagada.")
359
+ st.rerun()
360
+ with confirm_cols[1]:
361
+ if st.button("Cancelar", key=f"cancel_delete_instagram_button_{profile_id}", use_container_width=True):
362
+ st.session_state.pop(f"confirm_delete_instagram_{profile_id}", None)
363
+ st.rerun()
364
+
333
365
  if st.session_state.get(edit_key):
334
366
  with st.form(f"edit_instagram_profile_form_{profile_id}"):
335
367
  edit_cols = st.columns(2)
336
368
  with edit_cols[0]:
337
369
  name = st.text_input("Nome", value=_clean(profile.get("name")), key=f"instagram_edit_name_{profile_id}")
338
370
  handle = st.text_input("handler", value=_clean(profile.get("handle")), key=f"instagram_edit_handle_{profile_id}")
339
- country = st.text_input("País", value=_clean(profile.get("country")), key=f"instagram_edit_country_{profile_id}")
371
+ country = st.selectbox("País", _country_options(), index=_country_index(profile.get("country")), format_func=_country_label, key=f"instagram_edit_country_{profile_id}")
340
372
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(profile.get("language")), format_func=language_label, key=f"instagram_edit_language_{profile_id}")
341
373
  with edit_cols[1]:
342
374
  posts = st.number_input("posts", min_value=0, value=int(profile.get("post_count") or 0), key=f"instagram_edit_posts_{profile_id}")
@@ -404,6 +436,9 @@ def render_social_networks(settings: dict[str, Any]) -> None:
404
436
  with preview_cols[1]:
405
437
  st.write(f"**{_clean(data.get('name')) or _clean(data.get('username'))}**")
406
438
  st.caption(_clean(data.get("handle")))
439
+ bio = normalize_instagram_bio(data.get("bio"))
440
+ if bio:
441
+ st.caption(f"Bio: {bio}")
407
442
  with preview_cols[2]:
408
443
  st.metric("posts", _metric(data.get("post_count")))
409
444
  with preview_cols[3]:
@@ -414,7 +449,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
414
449
  form_cols = st.columns(2)
415
450
  with form_cols[0]:
416
451
  name = st.text_input("Nome", value=_clean(data.get("name")), key="social_instagram_result_name")
417
- country = st.text_input("País", value=_clean(data.get("country")), key="social_instagram_result_country")
452
+ country = st.selectbox("País", _country_options(), index=_country_index(data.get("country")), format_func=_country_label, key="social_instagram_result_country")
418
453
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=_language_index(data.get("language")), format_func=language_label, key="social_instagram_result_language")
419
454
  with form_cols[1]:
420
455
  posts = st.number_input("posts", min_value=0, value=int(data.get("post_count") or 0), key="social_instagram_result_posts")
@@ -0,0 +1,43 @@
1
+ """Canonical country catalog for social-account metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ COUNTRY_OPTIONS: tuple[str, ...] = (
6
+ "Abecásia", "Afeganistão", "África do Sul", "Albânia", "Alemanha", "Andorra", "Angola",
7
+ "Antígua e Barbuda", "Arábia Saudita", "Argélia", "Argentina", "Armênia", "Austrália", "Áustria",
8
+ "Azerbaijão", "Bahamas", "Bahrein", "Bangladesh", "Barbados", "Bélgica", "Belize", "Benim",
9
+ "Bielorrússia", "Bolívia", "Bósnia e Herzegovina", "Botsuana", "Brasil", "Brunei", "Bulgária",
10
+ "Burquina Fasso", "Burundi", "Butão", "Cabo Verde", "Camarões", "Camboja", "Canadá", "Catar",
11
+ "Cazaquistão", "Chade", "Chile", "China", "Chipre", "Chipre do Norte", "Cingapura", "Colômbia",
12
+ "Comores", "Congo", "Coreia do Norte", "Coreia do Sul", "Costa do Marfim", "Costa Rica", "Croácia",
13
+ "Cuba", "Dinamarca", "Djibouti", "Dominica", "República Dominicana", "Egito", "El Salvador",
14
+ "Emirados Árabes Unidos", "Equador", "Eritreia", "Eslováquia", "Eslovênia", "Espanha",
15
+ "Estados Federados da Micronésia", "Estados Unidos", "Estônia", "Essuatíni (Suazilândia)", "Etiópia",
16
+ "Fiji", "Filipinas", "Finlândia", "França", "Gabão", "Gâmbia", "Gana", "Geórgia", "Granada",
17
+ "Grécia", "Guatemala", "Guiana", "Guiné", "Guiné-Bissau", "Guiné Equatorial", "Haiti", "Honduras",
18
+ "Hungria", "Iêmen", "Ilhas Cook", "Índia", "Indonésia", "Irã", "Irlanda", "Islândia", "Israel",
19
+ "Itália", "Jamaica", "Japão", "Jordânia", "Kiribati", "Kosovo", "Kuwait", "Laos", "Lesoto",
20
+ "Letônia", "Líbano", "Libéria", "Líbia", "Liechtenstein", "Lituânia", "Luxemburgo", "Macedônia do Norte",
21
+ "Madagascar", "Malásia", "Malawi", "Maldivas", "Mali", "Malta", "Marrocos", "Ilhas Marshall",
22
+ "Maurício", "Mauritânia", "México", "Mianmar", "Moçambique", "Moldávia", "Mônaco", "Mongólia",
23
+ "Montenegro", "Namíbia", "Nauru", "Nepal", "Nicarágua", "Níger", "Nigéria", "Niue", "Noruega",
24
+ "Nova Zelândia", "Omã", "Ossétia do Sul", "Países Baixos", "Palau", "Palestina", "Panamá",
25
+ "Papua-Nova Guiné", "Paquistão", "Paraguai", "Peru", "Polônia", "Portugal", "Quênia", "Quirguistão",
26
+ "Reino Unido", "República Centro-Africana", "República Checa", "República Democrática do Congo", "Romênia",
27
+ "Ruanda", "Rússia", "Saara Ocidental (República Árabe Saharaui Democrática)", "Samoa", "San Marino",
28
+ "Santa Lúcia", "São Cristóvão e Nevis", "São Tomé e Príncipe", "São Vicente e Granadinas", "Senegal",
29
+ "Serra Leoa", "Sérvia", "Seicheles", "Somália", "Somalilândia", "Sri Lanka", "Sudão", "Sudão do Sul",
30
+ "Suécia", "Suíça", "Suriname", "Síria", "Tajiquistão", "Tailândia", "Taiwan", "Tanzânia",
31
+ "Timor-Leste", "Togo", "Tonga", "Transnístria", "Trinidad e Tobago", "Tunísia", "Turcomenistão",
32
+ "Turquia", "Tuvalu", "Ucrânia", "Uganda", "Uruguai", "Uzbequistão", "Vanuatu", "Vaticano (Santa Sé)",
33
+ "Venezuela", "Vietnã", "Zâmbia", "Zimbábue",
34
+ )
35
+
36
+
37
+ def country_index(value: object) -> int:
38
+ """Return the selector index while keeping legacy/empty values usable."""
39
+ value = str(value or "").strip()
40
+ return COUNTRY_OPTIONS.index(value) if value in COUNTRY_OPTIONS else 0
41
+
42
+
43
+ __all__ = ["COUNTRY_OPTIONS", "country_index"]
@@ -117,8 +117,10 @@ def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
117
117
 
118
118
 
119
119
  def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]) -> dict[str, Any]:
120
- followers = _structured_metric_from_json(json.dumps(user), 'edge_followed_by', 'followers', 'follower_count')
121
- following = _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count')
120
+ followers = _direct_profile_metric(user, 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
121
+ following = _direct_profile_metric(user, 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
122
+ followers = followers if followers is not None else _structured_metric_from_json(json.dumps(user), 'edge_followed_by', 'followers', 'follower_count', 'followerCount')
123
+ following = following if following is not None else _structured_metric_from_json(json.dumps(user), 'edge_follow', 'follows', 'following', 'following_count', 'followingCount')
122
124
  media = ((user.get('edge_owner_to_timeline_media') or {}).get('count') if isinstance(user.get('edge_owner_to_timeline_media'), dict) else None)
123
125
  return {
124
126
  'id': f"instagram_{reference['username']}",
@@ -136,6 +138,21 @@ def _profile_data_from_api(user: Mapping[str, Any], reference: Mapping[str, str]
136
138
  }
137
139
 
138
140
 
141
+ def _direct_profile_metric(user: Mapping[str, Any], *keys: str) -> int | None:
142
+ """Read common profile counters before falling back to recursive JSON parsing."""
143
+ for key in keys:
144
+ value = user.get(key)
145
+ if isinstance(value, Mapping):
146
+ value = value.get('count') or value.get('value')
147
+ if value in (None, ''):
148
+ continue
149
+ try:
150
+ return int(value)
151
+ except (TypeError, ValueError):
152
+ continue
153
+ return None
154
+
155
+
139
156
  def fetch_public_instagram_profile(source: str) -> IntegrationResult:
140
157
  try:
141
158
  reference = normalize_instagram_reference(source)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.22",
3
+ "version": "0.6.23",
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",