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