mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2026-03-07 01:47:32 +01:00
Terminando integración
This commit is contained in:
@@ -44,16 +44,26 @@ class Handler:
|
||||
start=True,
|
||||
kwargs=dict(parent=controller.view.nb, name="home_timeline", session=session)
|
||||
)
|
||||
# Following-only timeline (reverse-chronological)
|
||||
# Home (Following-only timeline - reverse-chronological)
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="following_timeline",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Following (Chronological)"),
|
||||
buffer_title=_("Home"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="following_timeline", session=session)
|
||||
)
|
||||
# Mentions (replies, mentions, quotes)
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="MentionsBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Mentions"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="mentions", session=session)
|
||||
)
|
||||
# Notifications
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
@@ -64,6 +74,16 @@ class Handler:
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="notifications", session=session)
|
||||
)
|
||||
# Sent posts
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="SentBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Sent"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="sent", session=session)
|
||||
)
|
||||
# Likes
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
@@ -84,12 +104,12 @@ class Handler:
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="followers", session=session)
|
||||
)
|
||||
# Following (Users)
|
||||
# Followings (Users you follow)
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="FollowingBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Following (Users)"),
|
||||
buffer_title=_("Followings"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="following", session=session)
|
||||
@@ -115,6 +135,12 @@ class Handler:
|
||||
kwargs=dict(parent=controller.view.nb, name="direct_messages", session=session)
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
||||
def start_buffer(self, controller, buffer):
|
||||
"""Start a newly created Bluesky buffer."""
|
||||
try:
|
||||
@@ -343,10 +369,10 @@ class Handler:
|
||||
"""Standard action for delete key / menu item"""
|
||||
item = buffer.get_item()
|
||||
if not item: return
|
||||
|
||||
|
||||
uri = getattr(item, "uri", None) or (item.get("post", {}).get("uri") if isinstance(item, dict) else None)
|
||||
if not uri: return
|
||||
|
||||
|
||||
import wx
|
||||
if wx.MessageBox(_("Are you sure you want to delete this post?"), _("Delete post"), wx.YES_NO | wx.ICON_QUESTION) == wx.YES:
|
||||
if buffer.session.delete_post(uri):
|
||||
@@ -358,3 +384,54 @@ class Handler:
|
||||
else:
|
||||
import output
|
||||
output.speak(_("Failed to delete post."))
|
||||
|
||||
def search(self, controller, session):
|
||||
"""Open search dialog and create search buffer for results."""
|
||||
dlg = wx.TextEntryDialog(
|
||||
controller.view,
|
||||
_("Enter search term:"),
|
||||
_("Search Bluesky")
|
||||
)
|
||||
if dlg.ShowModal() != wx.ID_OK:
|
||||
dlg.Destroy()
|
||||
return
|
||||
|
||||
query = dlg.GetValue().strip()
|
||||
dlg.Destroy()
|
||||
|
||||
if not query:
|
||||
return
|
||||
|
||||
# Create unique buffer name for this search
|
||||
buffer_name = f"search_{query[:20]}"
|
||||
account_name = session.get_name()
|
||||
|
||||
# Check if buffer already exists
|
||||
existing = controller.search_buffer(buffer_name, account_name)
|
||||
if existing:
|
||||
# Navigate to existing buffer
|
||||
index = controller.view.search(buffer_name, account_name)
|
||||
if index is not None:
|
||||
controller.view.change_buffer(index)
|
||||
# Refresh search
|
||||
existing.search_query = query
|
||||
existing.start_stream(mandatory=True, play_sound=False)
|
||||
return
|
||||
|
||||
# Create new search buffer
|
||||
title = _("Search: {query}").format(query=query)
|
||||
from pubsub import pub
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="SearchBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=title,
|
||||
parent_tab=controller.view.search(account_name, account_name),
|
||||
start=True,
|
||||
kwargs=dict(
|
||||
parent=controller.view.nb,
|
||||
name=buffer_name,
|
||||
session=session,
|
||||
query=query
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .timeline import HomeTimeline, FollowingTimeline, NotificationBuffer, Conversation
|
||||
from .timeline import (
|
||||
HomeTimeline,
|
||||
FollowingTimeline,
|
||||
NotificationBuffer,
|
||||
Conversation,
|
||||
LikesBuffer,
|
||||
MentionsBuffer,
|
||||
SentBuffer,
|
||||
SearchBuffer,
|
||||
)
|
||||
from .user import FollowersBuffer, FollowingBuffer, BlocksBuffer
|
||||
from .chat import ConversationListBuffer, ChatBuffer as ChatMessageBuffer
|
||||
|
||||
@@ -123,20 +123,84 @@ class BaseBuffer(base.Buffer):
|
||||
output.speak(_("Reposted."))
|
||||
|
||||
def on_like(self, evt):
|
||||
self.toggle_favorite(confirm=True)
|
||||
self.toggle_favorite(confirm=False)
|
||||
|
||||
def toggle_favorite(self, confirm=False, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
if not item: return
|
||||
uri = item.get("uri") if isinstance(item, dict) else getattr(item, "uri", None)
|
||||
if not item:
|
||||
output.speak(_("No item to like."), True)
|
||||
return
|
||||
|
||||
def g(obj, key, default=None):
|
||||
if isinstance(obj, dict):
|
||||
return obj.get(key, default)
|
||||
return getattr(obj, key, default)
|
||||
|
||||
uri = g(item, "uri")
|
||||
if not uri:
|
||||
post = g(item, "post") or g(item, "record")
|
||||
uri = g(post, "uri") if post else None
|
||||
|
||||
if not uri:
|
||||
output.speak(_("Could not find post identifier."), True)
|
||||
return
|
||||
|
||||
if confirm:
|
||||
if wx.MessageBox(_("Like this post?"), _("Confirm"), wx.YES_NO | wx.ICON_QUESTION) != wx.YES:
|
||||
return
|
||||
|
||||
self.session.like(uri)
|
||||
# Check if already liked
|
||||
viewer = g(item, "viewer")
|
||||
already_liked = g(viewer, "like") if viewer else None
|
||||
|
||||
if already_liked:
|
||||
output.speak(_("Already liked."), True)
|
||||
return
|
||||
|
||||
# Perform the like
|
||||
like_uri = self.session.like(uri)
|
||||
if not like_uri:
|
||||
output.speak(_("Failed to like post."), True)
|
||||
return
|
||||
|
||||
output.speak(_("Liked."))
|
||||
|
||||
# Update the viewer state in the item
|
||||
if isinstance(item, dict):
|
||||
if "viewer" not in item:
|
||||
item["viewer"] = {}
|
||||
item["viewer"]["like"] = like_uri
|
||||
else:
|
||||
# For SDK models, create or update viewer
|
||||
if not hasattr(item, "viewer") or item.viewer is None:
|
||||
# Create a simple object to hold the like state
|
||||
class Viewer:
|
||||
def __init__(self):
|
||||
self.like = None
|
||||
item.viewer = Viewer()
|
||||
item.viewer.like = like_uri
|
||||
|
||||
# Refresh the displayed item in the list
|
||||
try:
|
||||
index = self.buffer.list.get_selected()
|
||||
if index > -1:
|
||||
# Recompose and update the list item
|
||||
safe = True
|
||||
relative_times = self.session.settings["general"].get("relative_times", False)
|
||||
show_screen_names = self.session.settings["general"].get("show_screen_names", False)
|
||||
post_data = self.compose_function(item, self.session.db, self.session.settings,
|
||||
relative_times=relative_times,
|
||||
show_screen_names=show_screen_names,
|
||||
safe=safe)
|
||||
|
||||
# Update the item in place (only 3 columns: Author, Post, Date)
|
||||
self.buffer.list.list.SetItem(index, 0, post_data[0]) # Author
|
||||
self.buffer.list.list.SetItem(index, 1, post_data[1]) # Text (with ♥ indicator)
|
||||
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")
|
||||
|
||||
def add_to_favorites(self, *args, **kwargs):
|
||||
self.toggle_favorite(confirm=False)
|
||||
|
||||
@@ -172,8 +236,9 @@ class BaseBuffer(base.Buffer):
|
||||
if text:
|
||||
try:
|
||||
api = self.session._ensure_client()
|
||||
dm_client = api.with_bsky_chat_proxy()
|
||||
# Get or create conversation
|
||||
res = api.chat.bsky.convo.get_convo_for_members({"members": [did]})
|
||||
res = dm_client.chat.bsky.convo.get_convo_for_members({"members": [did]})
|
||||
convo_id = res.convo.id
|
||||
self.session.send_chat_message(convo_id, text)
|
||||
output.speak(_("Message sent."), True)
|
||||
@@ -186,6 +251,46 @@ class BaseBuffer(base.Buffer):
|
||||
# If showing, we'll just open the chat buffer for now as it's more structured
|
||||
self.view_chat_with_user(did, handle)
|
||||
|
||||
def url(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
if not item: return
|
||||
|
||||
import webbrowser
|
||||
|
||||
def g(obj, key, default=None):
|
||||
if isinstance(obj, dict):
|
||||
return obj.get(key, default)
|
||||
return getattr(obj, key, default)
|
||||
|
||||
uri = g(item, "uri")
|
||||
author = g(item, "author") or g(g(item, "post"), "author")
|
||||
handle = g(author, "handle")
|
||||
|
||||
if uri and handle:
|
||||
# URI format: at://did:plc:xxx/app.bsky.feed.post/rkey
|
||||
if "app.bsky.feed.post" in uri:
|
||||
rkey = uri.split("/")[-1]
|
||||
url = f"https://bsky.app/profile/{handle}/post/{rkey}"
|
||||
webbrowser.open(url)
|
||||
return
|
||||
elif "app.bsky.feed.like" in uri:
|
||||
# It's a like notification, try to get the subject
|
||||
subject = g(item, "subject")
|
||||
subject_uri = g(subject, "uri") if subject else None
|
||||
if subject_uri:
|
||||
rkey = subject_uri.split("/")[-1]
|
||||
# We might not have the handle of the post author here easily if it's not in the notification
|
||||
# But let's try...
|
||||
# Actually, notification items usually have enough info or we can't deep direct link easily without fetching.
|
||||
# For now, let's just open the profile of the liker
|
||||
pass
|
||||
|
||||
# Fallback to profile
|
||||
if handle:
|
||||
url = f"https://bsky.app/profile/{handle}"
|
||||
webbrowser.open(url)
|
||||
return
|
||||
|
||||
def user_actions(self, *args, **kwargs):
|
||||
pub.sendMessage("execute-action", action="follow")
|
||||
|
||||
|
||||
@@ -100,28 +100,43 @@ class FollowingTimeline(BaseBuffer):
|
||||
|
||||
class NotificationBuffer(BaseBuffer):
|
||||
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"
|
||||
|
||||
self.sound = "notification_received.ogg"
|
||||
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = BlueskiPanels.NotificationPanel(parent, name)
|
||||
self.buffer = BlueskiPanels.NotificationPanel(parent, name)
|
||||
self.buffer.session = self.session
|
||||
|
||||
def start_stream(self, mandatory=False, play_sound=True):
|
||||
count = 50
|
||||
api = self.session._ensure_client()
|
||||
try:
|
||||
res = api.app.bsky.notification.list_notifications({"limit": count})
|
||||
notifs = getattr(res, "notifications", [])
|
||||
items = []
|
||||
# Notifications are not FeedViewPost. They have different structure.
|
||||
# self.compose_function expects FeedViewPost-like structure (post, author, etc).
|
||||
# We need to map them or have a different compose function.
|
||||
# For now, let's skip items to avoid crash
|
||||
# Or attempt to map.
|
||||
except:
|
||||
return 0
|
||||
return 0
|
||||
count = 50
|
||||
try:
|
||||
count = self.session.settings["general"].get("max_posts_per_call", 50)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
api = self.session._ensure_client()
|
||||
if not api:
|
||||
return 0
|
||||
|
||||
try:
|
||||
res = api.app.bsky.notification.list_notifications({"limit": count})
|
||||
notifications = getattr(res, "notifications", [])
|
||||
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 0
|
||||
|
||||
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):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -207,3 +222,154 @@ class LikesBuffer(BaseBuffer):
|
||||
return 0
|
||||
|
||||
return self.process_items(list(items), play_sound)
|
||||
|
||||
|
||||
class MentionsBuffer(BaseBuffer):
|
||||
"""Buffer for mentions and replies to the current user."""
|
||||
|
||||
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"
|
||||
self.sound = "mention_received.ogg"
|
||||
|
||||
def create_buffer(self, parent, name):
|
||||
self.buffer = BlueskiPanels.NotificationPanel(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
|
||||
|
||||
api = self.session._ensure_client()
|
||||
if not api:
|
||||
return 0
|
||||
|
||||
try:
|
||||
res = api.app.bsky.notification.list_notifications({"limit": count})
|
||||
notifications = getattr(res, "notifications", [])
|
||||
if not notifications:
|
||||
return 0
|
||||
|
||||
# Filter only mentions and replies
|
||||
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")
|
||||
return 0
|
||||
|
||||
def add_new_item(self, notification):
|
||||
"""Add a single new mention from streaming/polling."""
|
||||
reason = getattr(notification, "reason", "")
|
||||
if 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."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SentBuffer, self).__init__(*args, **kwargs)
|
||||
self.type = "sent"
|
||||
|
||||
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
|
||||
|
||||
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", [])
|
||||
|
||||
if not items:
|
||||
return 0
|
||||
|
||||
return self.process_items(list(items), play_sound)
|
||||
|
||||
except Exception:
|
||||
log.exception("Error fetching sent posts")
|
||||
return 0
|
||||
|
||||
|
||||
class SearchBuffer(BaseBuffer):
|
||||
"""Buffer for search results (posts)."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.search_query = kwargs.pop("query", "")
|
||||
super(SearchBuffer, self).__init__(*args, **kwargs)
|
||||
self.type = "search"
|
||||
|
||||
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.search_query:
|
||||
return 0
|
||||
|
||||
count = 50
|
||||
try:
|
||||
count = self.session.settings["general"].get("max_posts_per_call", 50)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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", [])
|
||||
|
||||
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 0
|
||||
|
||||
def remove_buffer(self, force=False):
|
||||
"""Search buffers can always be removed."""
|
||||
try:
|
||||
self.session.db.pop(self.name, None)
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
|
||||
@@ -128,6 +128,9 @@ class Controller(object):
|
||||
pub.subscribe(self.mastodon_new_conversation, "mastodon.conversation_received")
|
||||
pub.subscribe(self.mastodon_error_post, "mastodon.error_post")
|
||||
|
||||
# Bluesky specific events.
|
||||
pub.subscribe(self.blueski_new_item, "blueski.new_item")
|
||||
|
||||
# connect application events to GUI
|
||||
widgetUtils.connect_event(self.view, widgetUtils.CLOSE_EVENT, self.exit_)
|
||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.show_hide, menuitem=self.view.show_hide)
|
||||
@@ -388,6 +391,12 @@ class Controller(object):
|
||||
"notifications": BlueskiTimelines.NotificationBuffer,
|
||||
"conversation": BlueskiTimelines.Conversation,
|
||||
"likes": BlueskiTimelines.LikesBuffer,
|
||||
"MentionsBuffer": BlueskiTimelines.MentionsBuffer,
|
||||
"mentions": BlueskiTimelines.MentionsBuffer,
|
||||
"SentBuffer": BlueskiTimelines.SentBuffer,
|
||||
"sent": BlueskiTimelines.SentBuffer,
|
||||
"SearchBuffer": BlueskiTimelines.SearchBuffer,
|
||||
"search": BlueskiTimelines.SearchBuffer,
|
||||
"UserBuffer": BlueskiUsers.UserBuffer,
|
||||
"FollowersBuffer": BlueskiUsers.FollowersBuffer,
|
||||
"FollowingBuffer": BlueskiUsers.FollowingBuffer,
|
||||
@@ -757,10 +766,10 @@ class Controller(object):
|
||||
dlg.Destroy()
|
||||
if not text:
|
||||
return
|
||||
try:
|
||||
uri = session.send_message(text, reply_to=selected_item_uri, reply_to_cid=selected_item_cid)
|
||||
if uri:
|
||||
output.speak(_("Reply sent."), True)
|
||||
try:
|
||||
uri = session.send_message(text, reply_to=selected_item_uri, reply_to_cid=selected_item_cid)
|
||||
if uri:
|
||||
output.speak(_("Reply sent."), True)
|
||||
else:
|
||||
output.speak(_("Failed to send reply."), True)
|
||||
except Exception:
|
||||
@@ -902,29 +911,13 @@ class Controller(object):
|
||||
buffer = self.get_current_buffer()
|
||||
if hasattr(buffer, "add_to_favorites"): # Generic buffer method
|
||||
return buffer.add_to_favorites()
|
||||
elif hasattr(buffer, "toggle_favorite"):
|
||||
return buffer.toggle_favorite()
|
||||
elif buffer.session and buffer.session.KIND == "blueski":
|
||||
item_uri = buffer.get_selected_item_id()
|
||||
if not item_uri:
|
||||
output.speak(_("No item selected to like."), True)
|
||||
return
|
||||
social_handler = self.get_handler(buffer.session.KIND)
|
||||
async def _like():
|
||||
result = await social_handler.like_item(buffer.session, item_uri)
|
||||
wx.CallAfter(output.speak, result["message"], True) # Ensure UI updates on main thread
|
||||
if result.get("status") == "success" and result.get("like_uri"):
|
||||
if hasattr(buffer, "store_item_viewer_state"):
|
||||
# Ensure store_item_viewer_state is called on main thread if it modifies UI/shared data
|
||||
wx.CallAfter(buffer.store_item_viewer_state, item_uri, "like_uri", result["like_uri"])
|
||||
# Also update the item in message_cache to reflect the like
|
||||
if buffer.session and hasattr(buffer.session, "message_cache") and item_uri in buffer.session.message_cache:
|
||||
cached_post = buffer.session.message_cache[item_uri]
|
||||
if isinstance(cached_post, dict) and isinstance(cached_post.get("viewer"), dict):
|
||||
cached_post["viewer"]["like"] = result["like_uri"]
|
||||
elif hasattr(cached_post, "viewer") and cached_post.viewer: # SDK model
|
||||
cached_post.viewer.like = result["like_uri"]
|
||||
# No need to call buffer.update_item here unless it re-renders from scratch
|
||||
# The visual feedback might come from a list refresh or specific item update later
|
||||
asyncio.create_task(_like()) # wx.CallAfter for the task itself if _like might interact with UI before await
|
||||
# Fallback if buffer doesn't have the method but session is blueski (e.g. ChatBuffer)
|
||||
# Chat messages can't be liked yet in this implementation, or handled by specific buffer
|
||||
output.speak(_("This item cannot be liked."), True)
|
||||
return
|
||||
|
||||
|
||||
def remove_from_favourites(self, *args, **kwargs):
|
||||
@@ -1475,7 +1468,12 @@ class Controller(object):
|
||||
output.speak(_(u"Updating buffer..."), True)
|
||||
session = bf.session
|
||||
|
||||
async def do_update():
|
||||
output.speak(_(u"Updating buffer..."), True)
|
||||
session = bf.session
|
||||
|
||||
import threading
|
||||
|
||||
def do_update_sync():
|
||||
new_ids = []
|
||||
try:
|
||||
if session.KIND == "blueski":
|
||||
@@ -1483,26 +1481,34 @@ class Controller(object):
|
||||
count = bf.start_stream(mandatory=True)
|
||||
if count: new_ids = [str(x) for x in range(count)]
|
||||
else:
|
||||
output.speak(_(u"This buffer type cannot be updated."), True)
|
||||
wx.CallAfter(output.speak, _(u"This buffer type cannot be updated."), True)
|
||||
return
|
||||
else: # Generic fallback for other sessions
|
||||
# If they are async, this might be tricky in a thread without a loop
|
||||
# But most old sessions in TWBlue are sync (using threads)
|
||||
if hasattr(bf, "start_stream"):
|
||||
count = bf.start_stream(mandatory=True, avoid_autoreading=True)
|
||||
if count: new_ids = [str(x) for x in range(count)]
|
||||
else:
|
||||
output.speak(_(u"Unable to update this buffer."), True)
|
||||
wx.CallAfter(output.speak, _(u"Unable to update this buffer."), True)
|
||||
return
|
||||
|
||||
# Generic feedback
|
||||
if bf.type in ["home_timeline", "user_timeline"]:
|
||||
output.speak(_("{0} posts retrieved").format(len(new_ids)), True)
|
||||
elif bf.type == "notifications":
|
||||
output.speak(_("Notifications updated."), True)
|
||||
if bf.type in ["home_timeline", "user_timeline", "notifications", "mentions"]:
|
||||
wx.CallAfter(output.speak, _("{0} new items.").format(len(new_ids)), True)
|
||||
|
||||
except Exception as e:
|
||||
log.exception("Error updating buffer %s", bf.name)
|
||||
output.speak(_("An error occurred while updating the buffer."), True)
|
||||
|
||||
wx.CallAfter(asyncio.create_task, do_update())
|
||||
wx.CallAfter(output.speak, _("An error occurred while updating the buffer."), True)
|
||||
|
||||
if session.KIND == "blueski":
|
||||
threading.Thread(target=do_update_sync).start()
|
||||
else:
|
||||
# Original async logic for others if needed, but likely they are sync too.
|
||||
# Assuming TWBlue architecture is mostly thread-based for legacy sessions.
|
||||
# If we have an async loop running, we could use it for async-capable sessions.
|
||||
# For safety, let's use the thread approach generally if we are not sure about the loop state.
|
||||
threading.Thread(target=do_update_sync).start()
|
||||
|
||||
|
||||
def get_more_items(self, *args, **kwargs):
|
||||
@@ -1637,6 +1643,33 @@ class Controller(object):
|
||||
# if "direct_messages" not in buffer.session.settings["other_buffers"]["muted_buffers"]:
|
||||
# self.notify(buffer.session, sound_to_play)
|
||||
|
||||
def blueski_new_item(self, item, session_name, _buffers):
|
||||
"""Handle new items from Bluesky polling."""
|
||||
sound_to_play = None
|
||||
for buff in _buffers:
|
||||
buffer = self.search_buffer(buff, session_name)
|
||||
if buffer is None or buffer.session.get_name() != session_name:
|
||||
continue
|
||||
if hasattr(buffer, "add_new_item"):
|
||||
buffer.add_new_item(item)
|
||||
# Determine sound to play
|
||||
if buff == "notifications":
|
||||
sound_to_play = "notification_received.ogg"
|
||||
elif buff == "home_timeline":
|
||||
sound_to_play = "tweet_received.ogg"
|
||||
elif "timeline" in buff:
|
||||
sound_to_play = "tweet_timeline.ogg"
|
||||
else:
|
||||
sound_to_play = None
|
||||
# Play sound if buffer is not muted
|
||||
if sound_to_play is not None:
|
||||
try:
|
||||
muted = buffer.session.settings["other_buffers"].get("muted_buffers", [])
|
||||
if buff not in muted:
|
||||
self.notify(buffer.session, sound_to_play)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def mastodon_error_post(self, name, reply_to, visibility, posts, language):
|
||||
home = self.search_buffer("home_timeline", name)
|
||||
if home != None:
|
||||
|
||||
@@ -21,7 +21,7 @@ def character_count(post_text, post_cw, character_limit=500):
|
||||
# We will use text for counting character limit only.
|
||||
full_text = post_text+post_cw
|
||||
# find remote users as Mastodon doesn't count the domain in char limit.
|
||||
users = re.findall("@[\w\.-]+@[\w\.-]+", full_text)
|
||||
users = re.findall(r"@[\w\.-]+@[\w\.-]+", full_text)
|
||||
for user in users:
|
||||
domain = user.split("@")[-1]
|
||||
full_text = full_text.replace("@"+domain, "")
|
||||
|
||||
@@ -20,7 +20,7 @@ class EditTemplate(object):
|
||||
self.template: str = template
|
||||
|
||||
def validate_template(self, template: str) -> bool:
|
||||
used_variables: List[str] = re.findall("\$\w+", template)
|
||||
used_variables: List[str] = re.findall(r"\$\w+", template)
|
||||
validated: bool = True
|
||||
for var in used_variables:
|
||||
if var[1:] not in self.variables:
|
||||
|
||||
Reference in New Issue
Block a user