diff --git a/CAMBIOS.md b/CAMBIOS.md index edc00094..8bd16f21 100644 --- a/CAMBIOS.md +++ b/CAMBIOS.md @@ -220,6 +220,40 @@ Para integrar Bluesky en TWBlue, también se modificaron: 4. Introducir App Password (desde Configuración > App Passwords en bsky.app) 5. Los buffers se crearán automáticamente +## Correcciones Recientes + +### Orden de Conversaciones +- Las conversaciones ahora se muestran en orden cronológico correcto (más antiguo arriba, más reciente abajo) +- Se añadió método `_add_items_chronological()` en `timeline.py` para manejar el orden específico de conversaciones + +### Detección de Imágenes y Sonido Indicador +- Corregido el evento de foco en paneles: cambiado de `wx.EVT_SET_FOCUS` a `wx.EVT_LIST_ITEM_FOCUSED` +- `EVT_SET_FOCUS` solo se disparaba al entrar al control, no al moverse entre elementos +- `EVT_LIST_ITEM_FOCUSED` se dispara correctamente al navegar entre posts con las flechas +- Simplificada función `is_image()` para detectar imágenes sin requerir URLs válidas + +### Funcionalidad OCR +- Implementada función `ocr_image()` en `base.py` para extraer texto de imágenes +- Usa `extra.ocr.OCRSpace.OCRSpaceAPI()` para el reconocimiento +- Soporta selección de imagen cuando hay múltiples imágenes en un post +- Creada clase `text` en `controller/blueski/messages.py` como visor de resultados OCR +- Creado diálogo `viewText` en `wxUI/dialogs/blueski/postDialogs.py` +- **Independiente de Mastodon**: no hay dependencias entre módulos blueski y mastodon + +### Reproducción Multimedia +- Implementada función `audio()` en `base.py` para reproducir vídeos y audio +- Usa `sound.URLPlayer` para la reproducción +- Soporta selección de URL cuando hay múltiples medios +- Función `get_media_urls()` en utils.py extrae URLs de vídeos, streams HLS y enlaces externos (YouTube, Vimeo, etc.) + +### Utilidades (`sessions/blueski/utils.py`) +- `is_image(post)`: Detecta si un post tiene imágenes (simplificado, no requiere URLs válidas) +- `is_audio_or_video(post)`: Detecta contenido de audio/vídeo +- `get_image_urls(post)`: Extrae URLs de imágenes para OCR +- `get_media_urls(post)`: Extrae URLs de medios para reproducción +- `find_urls(post)`: Encuentra URLs en el contenido del post +- `_extract_images_from_embed(embed)`: Helper compartido para extracción de imágenes + ## Limitaciones Conocidas - No hay streaming en tiempo real (se usa polling) diff --git a/src/controller/mainController.py b/src/controller/mainController.py index f3ab6244..b6fed3f9 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -254,10 +254,12 @@ class Controller(object): for i in sessions.sessions: log.debug("Working on session %s" % (i,)) if sessions.sessions[i].is_logged == False: - # Try auto-login for Blueski sessions if credentials exist + if sessions.sessions[i].session_id in config.app["sessions"]["ignored_sessions"]: + self.create_ignored_session_buffer(sessions.sessions[i]) + continue + # Try auto-login for sessions if credentials exist try: - if getattr(sessions.sessions[i], "type", None) == "blueski": - sessions.sessions[i].login() + sessions.sessions[i].login() except Exception: log.exception("Auto-login attempt failed for session %s", i) if sessions.sessions[i].is_logged == False: @@ -280,11 +282,8 @@ class Controller(object): """ Starts all buffer objects. Loads their items.""" for i in sessions.sessions: if sessions.sessions[i].is_logged == False: continue - self.start_buffers(sessions.sessions[i]) - self.set_buffer_positions(sessions.sessions[i]) - if hasattr(sessions.sessions[i], "start_streaming"): - sessions.sessions[i].start_streaming() - if config.app["app-settings"]["play_ready_sound"] == True: + call_threaded(self._start_session_buffers, sessions.sessions[i]) + if len(sessions.sessions) > 0 and config.app["app-settings"]["play_ready_sound"] == True: sessions.sessions[list(sessions.sessions.keys())[0]].sound.play("ready.ogg") if config.app["app-settings"]["speak_ready_msg"] == True: output.speak(_(u"Ready")) @@ -293,6 +292,16 @@ class Controller(object): b = self.get_first_buffer(self.accounts[0]) self.update_menus(handler=self.get_handler(b.session.type)) + def _start_session_buffers(self, session): + """Helper to start buffers for a session in a background thread.""" + try: + self.start_buffers(session) + self.set_buffer_positions(session) + if hasattr(session, "start_streaming"): + session.start_streaming() + except Exception: + log.exception("Error starting buffers for session %s", session.session_id) + def create_ignored_session_buffer(self, session): pub.sendMessage("core.create_account", name=session.get_name(), session_id=session.session_id) @@ -740,7 +749,13 @@ class Controller(object): def post_reply(self, *args, **kwargs): buffer = self.get_current_buffer() - if not buffer or not buffer.session: + if not buffer: + return + # Use buffer's own reply method if available (handles Mastodon and most cases) + if hasattr(buffer, "reply"): + return buffer.reply() + # Fallback for buffers without reply method + if not buffer.session: output.speak(_("No active session to reply."), True) return @@ -1052,6 +1067,8 @@ class Controller(object): def buffer_changed(self, *args, **kwargs): buffer = self.get_current_buffer() + if buffer is None: + return old_account = self.current_account new_account = buffer.account if new_account != old_account: @@ -1175,6 +1192,9 @@ class Controller(object): output.speak(msg, True) def next_account(self, *args, **kwargs): + if not self.accounts: + output.speak(_("No accounts available."), True) + return try: index = self.accounts.index(self.current_account) except ValueError: @@ -1203,6 +1223,9 @@ class Controller(object): output.speak(msg, True) def previous_account(self, *args, **kwargs): + if not self.accounts: + output.speak(_("No accounts available."), True) + return try: index = self.accounts.index(self.current_account) except ValueError: diff --git a/src/multiplatform_widgets/widgets.py b/src/multiplatform_widgets/widgets.py index 1a15e4ca..33b20bba 100644 --- a/src/multiplatform_widgets/widgets.py +++ b/src/multiplatform_widgets/widgets.py @@ -63,13 +63,17 @@ class list(object): def get_selected(self): if self.system == "Windows": - return self.list.GetFocusedItem() + item = self.list.GetFocusedItem() + if item == -1: + item = self.list.GetFirstSelected() + return item else: return self.list.GetSelection() def select_item(self, pos): if self.system == "Windows": self.list.Focus(pos) + self.list.Select(pos) else: self.list.SetSelection(pos) diff --git a/src/sessions/blueski/utils.py b/src/sessions/blueski/utils.py index 5a6ee4a9..834ece34 100644 --- a/src/sessions/blueski/utils.py +++ b/src/sessions/blueski/utils.py @@ -122,7 +122,27 @@ def is_image(post): """ actual_post = g(post, "post", post) embed = g(actual_post, "embed", None) - return len(_extract_images_from_embed(embed)) > 0 + if not embed: + return False + + etype = g(embed, "$type") or g(embed, "py_type") or "" + + # Direct images embed + if "images" in etype.lower(): + images = g(embed, "images", []) + if images and len(images) > 0: + return True + + # Check in recordWithMedia wrapper + if "recordwithmedia" in etype.lower(): + media = g(embed, "media", {}) + mtype = g(media, "$type") or g(media, "py_type") or "" + if "images" in mtype.lower(): + images = g(media, "images", []) + if images and len(images) > 0: + return True + + return False def get_image_urls(post): diff --git a/src/sessions/mastodon/session.py b/src/sessions/mastodon/session.py index 8a205862..b874955a 100644 --- a/src/sessions/mastodon/session.py +++ b/src/sessions/mastodon/session.py @@ -44,7 +44,7 @@ class Session(base.baseSession): if self.settings["mastodon"]["access_token"] != None and self.settings["mastodon"]["instance"] != None: try: log.debug("Logging in to Mastodon instance {}...".format(self.settings["mastodon"]["instance"])) - self.api = mastodon.Mastodon(access_token=self.settings["mastodon"]["access_token"], api_base_url=self.settings["mastodon"]["instance"], mastodon_version=MASTODON_VERSION, user_agent="TWBlue/{}".format(application.version), version_check_mode=self.version_check_mode) + self.api = mastodon.Mastodon(access_token=self.settings["mastodon"]["access_token"], api_base_url=self.settings["mastodon"]["instance"], mastodon_version=MASTODON_VERSION, user_agent="TWBlue/{}".format(application.version), version_check_mode=self.version_check_mode, request_timeout=30) if verify_credentials == True: credentials = self.api.account_verify_credentials() self.db["user_name"] = credentials["username"]