From c275ed9cf8f00817930c48c5ebdb128743a71184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Pav=C3=B3n=20Abi=C3=A1n?= Date: Sun, 1 Feb 2026 19:15:31 +0100 Subject: [PATCH] Refactor --- src/controller/blueski/handler.py | 64 +-- src/controller/buffers/blueski/base.py | 26 +- src/controller/buffers/blueski/chat.py | 25 +- src/controller/buffers/blueski/timeline.py | 533 +++++++-------------- src/controller/buffers/blueski/user.py | 41 +- 5 files changed, 243 insertions(+), 446 deletions(-) diff --git a/src/controller/blueski/handler.py b/src/controller/blueski/handler.py index a38693c9..e6b31672 100644 --- a/src/controller/blueski/handler.py +++ b/src/controller/blueski/handler.py @@ -64,17 +64,7 @@ class Handler: controller.accounts.append(name) root_position = controller.view.search(name, name) - # Discover/home timeline from pubsub import pub - pub.sendMessage( - "createBuffer", - buffer_type="home_timeline", - session_type="blueski", - buffer_title=_("Discover"), - parent_tab=root_position, - start=True, - kwargs=dict(parent=controller.view.nb, name="home_timeline", session=session) - ) # Home (Following-only timeline - reverse-chronological) pub.sendMessage( "createBuffer", @@ -82,9 +72,19 @@ class Handler: session_type="blueski", buffer_title=_("Home"), parent_tab=root_position, - start=False, + start=True, kwargs=dict(parent=controller.view.nb, name="following_timeline", session=session) ) + # Discover timeline + pub.sendMessage( + "createBuffer", + buffer_type="home_timeline", + session_type="blueski", + buffer_title=_("Discover"), + parent_tab=root_position, + start=False, + kwargs=dict(parent=controller.view.nb, name="home_timeline", session=session) + ) # Mentions (replies, mentions, quotes) pub.sendMessage( "createBuffer", @@ -140,7 +140,7 @@ class Handler: "createBuffer", buffer_type="FollowingBuffer", session_type="blueski", - buffer_title=_("Followings"), + buffer_title=_("Following"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="following", session=session) @@ -209,8 +209,8 @@ class Handler: start=False, kwargs=dict(parent=controller.view.nb, name=buffer_name, session=session, query=query) ) - except Exception: - logger.exception("Failed to restore Bluesky search buffers") + except Exception as e: + logger.error("Failed to restore Bluesky search buffers: %s", e) # Saved user timelines try: @@ -242,8 +242,8 @@ class Handler: start=False, kwargs=dict(parent=controller.view.nb, name=f"{handle}-timeline", session=session, actor=actor, handle=handle) ) - except Exception: - logger.exception("Failed to restore Bluesky timeline buffers") + except Exception as e: + logger.error("Failed to restore Bluesky timeline buffers: %s", e) # Saved followers/following timelines try: @@ -279,8 +279,8 @@ class Handler: start=False, kwargs=dict(parent=controller.view.nb, name=f"{handle}-followers", session=session, actor=actor, handle=handle) ) - except Exception: - logger.exception("Failed to restore Bluesky followers buffers") + except Exception as e: + logger.error("Failed to restore Bluesky followers buffers: %s", e) try: following = session.settings["other_buffers"].get("following_timelines") @@ -315,14 +315,14 @@ class Handler: start=False, kwargs=dict(parent=controller.view.nb, name=f"{handle}-following", session=session, actor=actor, handle=handle) ) - except Exception: - logger.exception("Failed to restore Bluesky following buffers") + except Exception as e: + logger.error("Failed to restore Bluesky following buffers: %s", e) # Start the background poller for real-time-like updates try: session.start_streaming() - except Exception: - logger.exception("Failed to start Bluesky streaming for session %s", name) + except Exception as e: + logger.error("Failed to start Bluesky streaming for session %s: %s", name, e) def start_buffer(self, controller, buffer): """Start a newly created Bluesky buffer.""" @@ -358,11 +358,11 @@ class Handler: try: buffer.session.settings["general"]["boost_mode"] = boost_mode buffer.session.settings.write() - except Exception: - logger.exception("Failed to persist Bluesky boost_mode setting") + except Exception as e: + logger.error("Failed to persist Bluesky boost_mode setting: %s", e) dlg.Destroy() - except Exception: - logger.exception("Error opening Bluesky account settings dialog") + except Exception as e: + logger.error("Error opening Bluesky account settings dialog: %s", e) def user_details(self, buffer): """Show user profile dialog for the selected user/post.""" @@ -670,8 +670,8 @@ class Handler: timelines.append(key) session.settings["other_buffers"]["timelines"] = timelines session.settings.write() - except Exception: - logger.exception("Failed to persist Bluesky timeline buffer") + except Exception as e: + logger.error("Failed to persist Bluesky timeline buffer: %s", e) def _resolve_actor(self, session, user_payload): def g(obj, key, default=None): @@ -785,8 +785,8 @@ class Handler: stored.append(key) session.settings["other_buffers"][settings_key] = stored session.settings.write() - except Exception: - logger.exception("Failed to persist Bluesky %s buffer", list_type) + except Exception as e: + logger.error("Failed to persist Bluesky %s buffer: %s", list_type, e) def delete(self, buffer, controller): """Standard action for delete key / menu item""" @@ -870,5 +870,5 @@ class Handler: searches.append(query) session.settings["other_buffers"]["searches"] = searches session.settings.write() - except Exception: - logger.exception("Failed to save search to settings") + except Exception as e: + logger.error("Failed to save search to settings: %s", e) diff --git a/src/controller/buffers/blueski/base.py b/src/controller/buffers/blueski/base.py index 768fc520..5bbc62c7 100644 --- a/src/controller/buffers/blueski/base.py +++ b/src/controller/buffers/blueski/base.py @@ -40,9 +40,13 @@ class BaseBuffer(base.Buffer): # Initialize DB list if needed if self.name not in self.session.db: self.session.db[self.name] = [] - + self.bind_events() + def get_max_items(self): + """Get max items per call from settings.""" + return self.session.settings["general"]["max_posts_per_call"] + def create_buffer(self, parent, name): # Default to HomePanel, can be overridden self.buffer = BlueskiPanels.HomePanel(parent, name, account=self.account) @@ -115,8 +119,8 @@ class BaseBuffer(base.Buffer): original_date = arrow.get(indexed_at) ts = original_date.humanize(locale=languageHandler.curLang[:2]) self.buffer.list.list.SetItem(index, 2, ts) - except Exception: - log.exception("Error updating relative time on focus") + except Exception as e: + log.error("Error updating relative time on focus: %s", e) # Read long posts in GUI if config.app["app-settings"].get("read_long_posts_in_gui", False) and self.buffer.list.list.HasFocus(): @@ -325,8 +329,8 @@ class BaseBuffer(base.Buffer): self.buffer.list.list.SetItem(index, 1, post_data[1]) # Text self.buffer.list.list.SetItem(index, 2, post_data[2]) # Date # Note: compose_post returns 4 items but list only has 3 columns - except Exception: - log.exception("Error refreshing list item after like") + except Exception as e: + log.error("Error refreshing list item after like: %s", e) def add_to_favorites(self, *args, **kwargs): self.toggle_favorite(confirm=False) @@ -370,8 +374,8 @@ class BaseBuffer(base.Buffer): self.session.send_chat_message(convo_id, text) self.session.sound.play("dm_sent.ogg") output.speak(_("Message sent."), True) - except: - log.exception("Error sending Bluesky DM (invisible)") + except Exception as e: + log.error("Error sending Bluesky DM: %s", e) output.speak(_("Failed to send message."), True) dlg.Destroy() return @@ -392,8 +396,8 @@ class BaseBuffer(base.Buffer): return try: blueski_messages.viewPost(self.session, item) - except Exception: - log.exception("Error opening Bluesky post viewer") + except Exception as e: + log.error("Error opening Bluesky post viewer: %s", e) def url_(self, *args, **kwargs): self.url() @@ -596,8 +600,8 @@ class BaseBuffer(base.Buffer): except Exception: pass output.speak(_("Deleted.")) - except Exception: - log.exception("Error deleting Bluesky post") + except Exception as e: + log.error("Error deleting Bluesky post: %s", e) output.speak(_("Could not delete."), True) diff --git a/src/controller/buffers/blueski/chat.py b/src/controller/buffers/blueski/chat.py index fd36dbc1..b7fef7d5 100644 --- a/src/controller/buffers/blueski/chat.py +++ b/src/controller/buffers/blueski/chat.py @@ -20,19 +20,15 @@ class ConversationListBuffer(BaseBuffer): self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - count = self.session.settings["general"].get("max_posts_per_call", 50) + count = self.get_max_items() try: res = self.session.list_convos(limit=count) items = res.get("items", []) - - # Clear to avoid list weirdness on refreshes? - # Chat list usually replaces content on fetch self.session.db[self.name] = [] self.buffer.list.clear() - return self.process_items(items, play_sound) - except Exception: - log.exception("Error fetching conversations") + except Exception as e: + log.error("Error fetching conversations: %s", e) return 0 def url(self, *args, **kwargs): @@ -80,23 +76,18 @@ class ChatBuffer(BaseBuffer): self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - if not self.convo_id: return 0 - count = self.session.settings["general"].get("max_posts_per_call", 50) + if not self.convo_id: + return 0 + count = self.get_max_items() try: res = self.session.get_convo_messages(self.convo_id, limit=count) items = res.get("items", []) - # Message order in API is often Oldest...Newest or vice versa. - # We want them in order and only new ones. - # For chat, let's just clear and show last N messages for simplicity now. self.session.db[self.name] = [] self.buffer.list.clear() - - # API usually returns newest first. We want newest at bottom. items = list(reversed(items)) - return self.process_items(items, play_sound) - except Exception: - log.exception("Error fetching chat messages") + except Exception as e: + log.error("Error fetching chat messages: %s", e) return 0 def on_reply(self, evt): diff --git a/src/controller/buffers/blueski/timeline.py b/src/controller/buffers/blueski/timeline.py index de745698..4c776ef3 100644 --- a/src/controller/buffers/blueski/timeline.py +++ b/src/controller/buffers/blueski/timeline.py @@ -3,11 +3,13 @@ import logging import output from .base import BaseBuffer from wxUI.buffers.blueski import panels as BlueskiPanels -from pubsub import pub log = logging.getLogger("controller.buffers.blueski.timeline") + class HomeTimeline(BaseBuffer): + """Discover feed buffer.""" + def __init__(self, *args, **kwargs): super(HomeTimeline, self).__init__(*args, **kwargs) self.type = "home_timeline" @@ -16,90 +18,64 @@ class HomeTimeline(BaseBuffer): self.sound = "tweet_received.ogg" def create_buffer(self, parent, name): - # Override to use HomePanel self.buffer = BlueskiPanels.HomePanel(parent, name) self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: pass - + count = self.get_max_items() api = self.session._ensure_client() - - # Discover Logic if not self.feed_uri: self.feed_uri = self._resolve_discover_feed(api) - - items = [] try: - res = None if self.feed_uri: - # Fetch feed - res = api.app.bsky.feed.get_feed({"feed": self.feed_uri, "limit": count}) + res = api.app.bsky.feed.get_feed({"feed": self.feed_uri, "limit": count}) else: - # Fallback to standard timeline - res = api.app.bsky.feed.get_timeline({"limit": count}) - - feed = getattr(res, "feed", []) - items = list(feed) + res = api.app.bsky.feed.get_timeline({"limit": count}) + items = list(getattr(res, "feed", [])) self.next_cursor = getattr(res, "cursor", None) - - except Exception: - log.exception("Failed to fetch home timeline") + except Exception as e: + log.error("Error fetching home timeline: %s", e) return 0 - return self.process_items(items, play_sound) def get_more_items(self): if not self.next_cursor: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() try: if self.feed_uri: res = api.app.bsky.feed.get_feed({"feed": self.feed_uri, "limit": count, "cursor": self.next_cursor}) else: res = api.app.bsky.feed.get_timeline({"limit": count, "cursor": self.next_cursor}) - feed = getattr(res, "feed", []) - items = list(feed) + items = list(getattr(res, "feed", [])) self.next_cursor = getattr(res, "cursor", None) added = self.process_items(items, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more home timeline items") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more home timeline: %s", e) def _resolve_discover_feed(self, api): - # Reuse logic from panels.py + cached = self.session.db.get("discover_feed_uri") + if cached: + return cached try: - cached = self.session.db.get("discover_feed_uri") - if cached: return cached - - # Simple fallback: Suggested feeds - try: - res = api.app.bsky.feed.get_suggested_feeds({"limit": 50}) - feeds = getattr(res, "feeds", []) - for feed in feeds: - dn = getattr(feed, "displayName", "") or getattr(feed, "display_name", "") - if "discover" in dn.lower(): - uri = getattr(feed, "uri", "") - self.session.db["discover_feed_uri"] = uri - try: self.session.save_persistent_data() - except: pass - return uri - except: pass - - return None - except: - return None + res = api.app.bsky.feed.get_suggested_feeds({"limit": 50}) + for feed in getattr(res, "feeds", []): + dn = getattr(feed, "displayName", "") or getattr(feed, "display_name", "") + if "discover" in dn.lower(): + uri = getattr(feed, "uri", "") + self.session.db["discover_feed_uri"] = uri + return uri + except Exception: + pass + return None + class FollowingTimeline(BaseBuffer): + """Following-only timeline (reverse-chronological).""" + def __init__(self, *args, **kwargs): super(FollowingTimeline, self).__init__(*args, **kwargs) self.type = "following_timeline" @@ -107,50 +83,41 @@ class FollowingTimeline(BaseBuffer): self.sound = "tweet_received.ogg" def create_buffer(self, parent, name): - self.buffer = BlueskiPanels.HomePanel(parent, name) # Reuse HomePanel layout + self.buffer = BlueskiPanels.HomePanel(parent, name) self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - count = 50 - try: count = self.session.settings["general"].get("max_posts_per_call", 50) - except: pass - - api = self.session._ensure_client() - try: - # Force reverse-chronological - res = api.app.bsky.feed.get_timeline({"limit": count, "algorithm": "reverse-chronological"}) - feed = getattr(res, "feed", []) - items = list(feed) - self.next_cursor = getattr(res, "cursor", None) - except Exception: - log.exception("Error fetching following timeline") - return 0 - - return self.process_items(items, play_sound) + count = self.get_max_items() + api = self.session._ensure_client() + try: + res = api.app.bsky.feed.get_timeline({"limit": count, "algorithm": "reverse-chronological"}) + items = list(getattr(res, "feed", [])) + self.next_cursor = getattr(res, "cursor", None) + except Exception as e: + log.error("Error fetching following timeline: %s", e) + return 0 + return self.process_items(items, play_sound) def get_more_items(self): if not self.next_cursor: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() try: res = api.app.bsky.feed.get_timeline({"limit": count, "algorithm": "reverse-chronological", "cursor": self.next_cursor}) - feed = getattr(res, "feed", []) - items = list(feed) + items = list(getattr(res, "feed", [])) self.next_cursor = getattr(res, "cursor", None) added = self.process_items(items, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more following timeline items") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more following timeline: %s", e) + class NotificationBuffer(BaseBuffer): + """Notifications buffer.""" + def __init__(self, *args, **kwargs): - # Override compose_func before calling super().__init__ kwargs["compose_func"] = "compose_notification" super(NotificationBuffer, self).__init__(*args, **kwargs) self.type = "notifications" @@ -162,90 +129,70 @@ class NotificationBuffer(BaseBuffer): self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except Exception: - pass - + count = self.get_max_items() api = self.session._ensure_client() if not api: return 0 - try: res = api.app.bsky.notification.list_notifications({"limit": count}) - notifications = getattr(res, "notifications", []) + notifications = list(getattr(res, "notifications", [])) self.next_cursor = getattr(res, "cursor", None) if not notifications: return 0 - - # Process notifications using the notification compose function - return self.process_items(list(notifications), play_sound) - - except Exception: - log.exception("Error fetching Bluesky notifications") + return self.process_items(notifications, play_sound) + except Exception as e: + log.error("Error fetching notifications: %s", e) return 0 def get_more_items(self): if not self.next_cursor: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() if not api: return try: res = api.app.bsky.notification.list_notifications({"limit": count, "cursor": self.next_cursor}) - notifications = getattr(res, "notifications", []) + notifications = list(getattr(res, "notifications", [])) self.next_cursor = getattr(res, "cursor", None) - added = self.process_items(list(notifications), play_sound=False) + added = self.process_items(notifications, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more notifications") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more notifications: %s", e) def add_new_item(self, notification): - """Add a single new notification from streaming/polling.""" return self.process_items([notification], play_sound=True) + class Conversation(BaseBuffer): + """Thread/conversation view.""" + def __init__(self, *args, **kwargs): super(Conversation, self).__init__(*args, **kwargs) self.type = "conversation" - # We need the root URI or the URI of the post to show thread for self.root_uri = kwargs.get("uri") self.sound = "search_updated.ogg" - + def create_buffer(self, parent, name): self.buffer = BlueskiPanels.HomePanel(parent, name) self.buffer.session = self.session - + def start_stream(self, mandatory=False, play_sound=True): - if not self.root_uri: return 0 - + if not self.root_uri: + return 0 api = self.session._ensure_client() try: - params = {"uri": self.root_uri, "depth": 100, "parentHeight": 100} - try: - res = api.app.bsky.feed.get_post_thread(params) - except Exception: - res = api.app.bsky.feed.get_post_thread({"uri": self.root_uri}) - - def g(obj, key, default=None): - if isinstance(obj, dict): - return obj.get(key, default) - return getattr(obj, key, default) - - thread = getattr(res, "thread", None) or (res.get("thread") if isinstance(res, dict) else None) + res = api.app.bsky.feed.get_post_thread({"uri": self.root_uri, "depth": 100, "parentHeight": 100}) + thread = getattr(res, "thread", None) if not thread: return 0 - final_items = [] + def g(obj, key, default=None): + return obj.get(key, default) if isinstance(obj, dict) else getattr(obj, key, default) - # Add parent chain (oldest to newest) if available + final_items = [] + # Add ancestors ancestors = [] parent = g(thread, "parent") while parent: @@ -255,29 +202,28 @@ class Conversation(BaseBuffer): parent = g(parent, "parent") final_items.extend(ancestors) + # Traverse thread def traverse(node): if not node: return post = g(node, "post") if post: final_items.append(post) - replies = g(node, "replies") or [] - for r in replies: + for r in (g(node, "replies") or []): traverse(r) traverse(thread) - - # Clear existing items to avoid duplication when refreshing a thread view (which changes structure little) - self.session.db[self.name] = [] - self.buffer.list.clear() # Clear UI too - + self.session.db[self.name] = [] + self.buffer.list.clear() return self.process_items(final_items, play_sound) - - except Exception: - log.exception("Error fetching thread") + except Exception as e: + log.error("Error fetching thread: %s", e) return 0 + class LikesBuffer(BaseBuffer): + """User's liked posts.""" + def __init__(self, *args, **kwargs): super(LikesBuffer, self).__init__(*args, **kwargs) self.type = "likes" @@ -289,50 +235,39 @@ class LikesBuffer(BaseBuffer): self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except Exception: - pass - + count = self.get_max_items() api = self.session._ensure_client() try: res = api.app.bsky.feed.get_actor_likes({"actor": api.me.did, "limit": count}) - items = getattr(res, "feed", None) or getattr(res, "items", None) or [] + items = list(getattr(res, "feed", None) or getattr(res, "items", None) or []) self.next_cursor = getattr(res, "cursor", None) - except Exception: - log.exception("Error fetching likes") + except Exception as e: + log.error("Error fetching likes: %s", e) return 0 - - return self.process_items(list(items), play_sound) + return self.process_items(items, play_sound) def get_more_items(self): if not self.next_cursor: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() if not api: return try: res = api.app.bsky.feed.get_actor_likes({"actor": api.me.did, "limit": count, "cursor": self.next_cursor}) - items = getattr(res, "feed", None) or getattr(res, "items", None) or [] + items = list(getattr(res, "feed", None) or getattr(res, "items", None) or []) self.next_cursor = getattr(res, "cursor", None) - added = self.process_items(list(items), play_sound=False) + added = self.process_items(items, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more likes") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more likes: %s", e) class MentionsBuffer(BaseBuffer): - """Buffer for mentions and replies to the current user.""" + """Mentions, replies and quotes.""" def __init__(self, *args, **kwargs): - # Use notification compose function since mentions come from notifications kwargs["compose_func"] = "compose_notification" super(MentionsBuffer, self).__init__(*args, **kwargs) self.type = "mentions" @@ -344,46 +279,26 @@ class MentionsBuffer(BaseBuffer): self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except Exception: - pass - + count = self.get_max_items() api = self.session._ensure_client() if not api: return 0 - try: res = api.app.bsky.notification.list_notifications({"limit": count}) notifications = getattr(res, "notifications", []) self.next_cursor = getattr(res, "cursor", None) - if not notifications: - return 0 - - # Filter only mentions and replies - mentions = [ - n for n in notifications - if getattr(n, "reason", "") in ("mention", "reply", "quote") - ] - + mentions = [n for n in notifications if getattr(n, "reason", "") in ("mention", "reply", "quote")] if not mentions: return 0 - return self.process_items(mentions, play_sound) - - except Exception: - log.exception("Error fetching Bluesky mentions") + except Exception as e: + log.error("Error fetching mentions: %s", e) return 0 def get_more_items(self): if not self.next_cursor: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() if not api: return @@ -391,98 +306,68 @@ class MentionsBuffer(BaseBuffer): res = api.app.bsky.notification.list_notifications({"limit": count, "cursor": self.next_cursor}) notifications = getattr(res, "notifications", []) self.next_cursor = getattr(res, "cursor", None) - # Filter only mentions and replies - mentions = [ - n for n in notifications - if getattr(n, "reason", "") in ("mention", "reply", "quote") - ] + mentions = [n for n in notifications if getattr(n, "reason", "") in ("mention", "reply", "quote")] if mentions: added = self.process_items(mentions, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more mentions") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more mentions: %s", e) def add_new_item(self, notification): - """Add a single new mention from streaming/polling.""" - reason = getattr(notification, "reason", "") - if reason in ("mention", "reply", "quote"): + if getattr(notification, "reason", "") in ("mention", "reply", "quote"): return self.process_items([notification], play_sound=True) return 0 class SentBuffer(BaseBuffer): - """Buffer for posts sent by the current user.""" + """User's sent posts.""" def __init__(self, *args, **kwargs): super(SentBuffer, self).__init__(*args, **kwargs) self.type = "sent" self.next_cursor = None - # No sound for sent posts (user's own posts) def create_buffer(self, parent, name): self.buffer = BlueskiPanels.HomePanel(parent, name) self.buffer.session = self.session def start_stream(self, mandatory=False, play_sound=True): - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except Exception: - pass - + count = self.get_max_items() api = self.session._ensure_client() if not api or not api.me: return 0 - try: - # Get author's own posts (excluding replies) - res = api.app.bsky.feed.get_author_feed({ - "actor": api.me.did, - "limit": count, - "filter": "posts_no_replies" - }) - items = getattr(res, "feed", []) + res = api.app.bsky.feed.get_author_feed({"actor": api.me.did, "limit": count, "filter": "posts_no_replies"}) + items = list(getattr(res, "feed", [])) self.next_cursor = getattr(res, "cursor", None) - if not items: return 0 - - return self.process_items(list(items), play_sound) - - except Exception: - log.exception("Error fetching sent posts") + return self.process_items(items, play_sound) + except Exception as e: + log.error("Error fetching sent posts: %s", e) return 0 def get_more_items(self): if not self.next_cursor: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() if not api or not api.me: return try: - res = api.app.bsky.feed.get_author_feed({ - "actor": api.me.did, - "limit": count, - "filter": "posts_no_replies", - "cursor": self.next_cursor - }) - items = getattr(res, "feed", []) + res = api.app.bsky.feed.get_author_feed({"actor": api.me.did, "limit": count, "filter": "posts_no_replies", "cursor": self.next_cursor}) + items = list(getattr(res, "feed", [])) self.next_cursor = getattr(res, "cursor", None) - added = self.process_items(list(items), play_sound=False) + added = self.process_items(items, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more sent posts") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more sent posts: %s", e) class UserTimeline(BaseBuffer): - """Buffer for posts by a specific user.""" + """Timeline for a specific user.""" def __init__(self, *args, **kwargs): self.actor = kwargs.get("actor") @@ -500,106 +385,64 @@ class UserTimeline(BaseBuffer): def start_stream(self, mandatory=False, play_sound=True): if not self.actor: return 0 - - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except Exception: - pass - - actor = self.actor - if isinstance(actor, str): - actor = actor.strip() - if actor.startswith("@"): - actor = actor[1:] - + count = self.get_max_items() + actor = self.actor.strip().lstrip("@") if isinstance(self.actor, str) else self.actor api = self.session._ensure_client() if not api: return 0 - try: if isinstance(actor, str) and not actor.startswith("did:"): - try: - profile = self.session.get_profile(actor) - if profile: - def g(obj, key, default=None): - if isinstance(obj, dict): - return obj.get(key, default) - return getattr(obj, key, default) - did = g(profile, "did") - if did: - actor = did - except Exception: - pass + profile = self.session.get_profile(actor) + if profile: + did = profile.get("did") if isinstance(profile, dict) else getattr(profile, "did", None) + if did: + actor = did self._resolved_actor = actor - res = api.app.bsky.feed.get_author_feed({ - "actor": actor, - "limit": count, - }) - items = getattr(res, "feed", []) or [] + res = api.app.bsky.feed.get_author_feed({"actor": actor, "limit": count}) + items = list(getattr(res, "feed", []) or []) self.next_cursor = getattr(res, "cursor", None) - except Exception: - log.exception("Error fetching user timeline") + except Exception as e: + log.error("Error fetching user timeline: %s", e) return 0 - - return self.process_items(list(items), play_sound) + return self.process_items(items, play_sound) def get_more_items(self): if not self.next_cursor or not self._resolved_actor: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() if not api: return try: - res = api.app.bsky.feed.get_author_feed({ - "actor": self._resolved_actor, - "limit": count, - "cursor": self.next_cursor - }) - items = getattr(res, "feed", []) or [] + res = api.app.bsky.feed.get_author_feed({"actor": self._resolved_actor, "limit": count, "cursor": self.next_cursor}) + items = list(getattr(res, "feed", []) or []) self.next_cursor = getattr(res, "cursor", None) - added = self.process_items(list(items), play_sound=False) + added = self.process_items(items, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more user timeline items") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more user timeline: %s", e) def remove_buffer(self, force=False): if not force: from wxUI import commonMessageDialogs import widgetUtils - dlg = commonMessageDialogs.remove_buffer() - if dlg != widgetUtils.YES: + if commonMessageDialogs.remove_buffer() != widgetUtils.YES: return False - try: - self.session.db.pop(self.name, None) - except Exception: - pass - try: - timelines = self.session.settings["other_buffers"].get("timelines") - if timelines is None: - timelines = [] - if isinstance(timelines, str): - timelines = [t for t in timelines.split(",") if t] - actor = self.actor or "" - handle = self.handle or "" - for key in (actor, handle): - if key in timelines: - timelines.remove(key) - self.session.settings["other_buffers"]["timelines"] = timelines - self.session.settings.write() - except Exception: - log.exception("Error updating Bluesky timelines settings") + self.session.db.pop(self.name, None) + timelines = self.session.settings["other_buffers"].get("timelines") or [] + if isinstance(timelines, str): + timelines = [t for t in timelines.split(",") if t] + for key in (self.actor or "", self.handle or ""): + if key in timelines: + timelines.remove(key) + self.session.settings["other_buffers"]["timelines"] = timelines + self.session.settings.write() return True class SearchBuffer(BaseBuffer): - """Buffer for search results (posts).""" + """Search results buffer.""" def __init__(self, *args, **kwargs): self.search_query = kwargs.pop("query", "") @@ -615,86 +458,52 @@ class SearchBuffer(BaseBuffer): def start_stream(self, mandatory=False, play_sound=True): if not self.search_query: return 0 - - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except Exception: - pass - + count = self.get_max_items() api = self.session._ensure_client() if not api: return 0 - try: - # Search posts - res = api.app.bsky.feed.search_posts({ - "q": self.search_query, - "limit": count - }) - posts = getattr(res, "posts", []) + res = api.app.bsky.feed.search_posts({"q": self.search_query, "limit": count}) + posts = list(getattr(res, "posts", [])) self.next_cursor = getattr(res, "cursor", None) - if not posts: return 0 - - # Clear existing results for new search self.session.db[self.name] = [] self.buffer.list.clear() - - return self.process_items(list(posts), play_sound) - - except Exception: - log.exception("Error searching Bluesky posts") + return self.process_items(posts, play_sound) + except Exception as e: + log.error("Error searching posts: %s", e) return 0 def get_more_items(self): if not self.next_cursor or not self.search_query: return - count = 50 - try: - count = self.session.settings["general"].get("max_posts_per_call", 50) - except: - pass + count = self.get_max_items() api = self.session._ensure_client() if not api: return try: - res = api.app.bsky.feed.search_posts({ - "q": self.search_query, - "limit": count, - "cursor": self.next_cursor - }) - posts = getattr(res, "posts", []) + res = api.app.bsky.feed.search_posts({"q": self.search_query, "limit": count, "cursor": self.next_cursor}) + posts = list(getattr(res, "posts", [])) self.next_cursor = getattr(res, "cursor", None) - added = self.process_items(list(posts), play_sound=False) + added = self.process_items(posts, play_sound=False) if added: - output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more search results") + output.speak(_(u"%s items retrieved") % added, True) + except Exception as e: + log.error("Error fetching more search results: %s", e) def remove_buffer(self, force=False): - """Search buffers can always be removed.""" if not force: from wxUI import commonMessageDialogs import widgetUtils - dlg = commonMessageDialogs.remove_buffer() - if dlg != widgetUtils.YES: + if commonMessageDialogs.remove_buffer() != widgetUtils.YES: return False - try: - self.session.db.pop(self.name, None) - except Exception: - pass - # Also remove from saved searches - try: - searches = self.session.settings["other_buffers"].get("searches") - if searches: - if isinstance(searches, str): - searches = [s for s in searches.split(",") if s] - if self.search_query in searches: - searches.remove(self.search_query) - self.session.settings["other_buffers"]["searches"] = searches - self.session.settings.write() - except Exception: - log.exception("Error updating saved searches") + self.session.db.pop(self.name, None) + searches = self.session.settings["other_buffers"].get("searches") or [] + if isinstance(searches, str): + searches = [s for s in searches.split(",") if s] + if self.search_query in searches: + searches.remove(self.search_query) + self.session.settings["other_buffers"]["searches"] = searches + self.session.settings.write() return True diff --git a/src/controller/buffers/blueski/user.py b/src/controller/buffers/blueski/user.py index 37165c6e..a71d8dad 100644 --- a/src/controller/buffers/blueski/user.py +++ b/src/controller/buffers/blueski/user.py @@ -24,31 +24,24 @@ class UserBuffer(BaseBuffer): api_method = self.kwargs.get("api_method") if not api_method: return 0 - count = self.session.settings["general"].get("max_posts_per_call", 50) + count = self.get_max_items() actor = ( self.kwargs.get("actor") or self.kwargs.get("did") or self.kwargs.get("handle") or self.kwargs.get("id") ) - + try: - # We call the method in session. API methods return {"items": [...], "cursor": ...} if api_method in ("get_followers", "get_follows"): res = getattr(self.session, api_method)(actor=actor, limit=count) else: res = getattr(self.session, api_method)(limit=count) items = res.get("items", []) self.next_cursor = res.get("cursor") - - # Clear existing items for these lists to start fresh? - # Or append? Standard lists in TWBlue usually append. - # But followers/blocks are often full-sync or large jumps. - # For now, append like timelines. - return self.process_items(items, play_sound) - except Exception: - log.exception(f"Error fetching user list for {self.name}") + except Exception as e: + log.error("Error fetching user list for %s: %s", self.name, e) return 0 def get_more_items(self): @@ -56,7 +49,7 @@ class UserBuffer(BaseBuffer): if not api_method or not self.next_cursor: return - count = self.session.settings["general"].get("max_posts_per_call", 50) + count = self.get_max_items() actor = ( self.kwargs.get("actor") or self.kwargs.get("did") @@ -73,8 +66,8 @@ class UserBuffer(BaseBuffer): added = self.process_items(items, play_sound=False) if added: output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception(f"Error fetching more user list items for {self.name}") + except Exception as e: + log.error("Error fetching more user list items for %s: %s", self.name, e) class FollowersBuffer(UserBuffer): def __init__(self, *args, **kwargs): @@ -102,8 +95,8 @@ class FollowersBuffer(UserBuffer): timelines.remove(key) self.session.settings["other_buffers"]["followers_timelines"] = timelines self.session.settings.write() - except Exception: - log.exception("Error updating Bluesky followers timelines settings") + except Exception as e: + log.error("Error updating Bluesky followers timelines settings: %s", e) return True class FollowingBuffer(UserBuffer): @@ -132,8 +125,8 @@ class FollowingBuffer(UserBuffer): timelines.remove(key) self.session.settings["other_buffers"]["following_timelines"] = timelines self.session.settings.write() - except Exception: - log.exception("Error updating Bluesky following timelines settings") + except Exception as e: + log.error("Error updating Bluesky following timelines settings: %s", e) return True class BlocksBuffer(UserBuffer): @@ -152,20 +145,20 @@ class PostUserListBuffer(UserBuffer): def start_stream(self, mandatory=False, play_sound=True): if not self.api_method or not self.post_uri: return 0 - count = self.session.settings["general"].get("max_posts_per_call", 50) + count = self.get_max_items() try: res = getattr(self.session, self.api_method)(self.post_uri, limit=count) items = res.get("items", []) self.next_cursor = res.get("cursor") return self.process_items(items, play_sound) - except Exception: - log.exception("Error fetching post user list for %s", self.name) + except Exception as e: + log.error("Error fetching post user list for %s: %s", self.name, e) return 0 def get_more_items(self): if not self.api_method or not self.post_uri or not self.next_cursor: return - count = self.session.settings["general"].get("max_posts_per_call", 50) + count = self.get_max_items() try: res = getattr(self.session, self.api_method)(self.post_uri, limit=count, cursor=self.next_cursor) items = res.get("items", []) @@ -173,8 +166,8 @@ class PostUserListBuffer(UserBuffer): added = self.process_items(items, play_sound=False) if added: output.speak(_(u"%s items retrieved") % (str(added)), True) - except Exception: - log.exception("Error fetching more post user list items for %s", self.name) + except Exception as e: + log.error("Error fetching more post user list items for %s: %s", self.name, e) def remove_buffer(self, force=False): if not force: