mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2026-03-06 09:27:33 +01:00
Hilos funcionan.
This commit is contained in:
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import wx
|
import wx
|
||||||
|
import asyncio
|
||||||
import output
|
import output
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
from wxUI.dialogs.blueski.showUserProfile import ShowUserProfileDialog
|
from wxUI.dialogs.blueski.showUserProfile import ShowUserProfileDialog
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import languageHandler # Ensure _() injection
|
import languageHandler # Ensure _() injection
|
||||||
@@ -135,6 +137,28 @@ class Handler:
|
|||||||
kwargs=dict(parent=controller.view.nb, name="direct_messages", session=session)
|
kwargs=dict(parent=controller.view.nb, name="direct_messages", session=session)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Saved user timelines
|
||||||
|
try:
|
||||||
|
timelines = session.settings["other_buffers"].get("timelines")
|
||||||
|
if timelines is None:
|
||||||
|
timelines = []
|
||||||
|
if isinstance(timelines, str):
|
||||||
|
timelines = [t for t in timelines.split(",") if t]
|
||||||
|
for actor in timelines:
|
||||||
|
handle = actor
|
||||||
|
title = _("Timeline for {user}").format(user=handle)
|
||||||
|
pub.sendMessage(
|
||||||
|
"createBuffer",
|
||||||
|
buffer_type="UserTimeline",
|
||||||
|
session_type="blueski",
|
||||||
|
buffer_title=title,
|
||||||
|
parent_tab=root_position,
|
||||||
|
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")
|
||||||
|
|
||||||
# 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()
|
||||||
@@ -319,6 +343,66 @@ class Handler:
|
|||||||
kwargs=dict(parent=controller.view.nb, name=title, session=buffer.session, uri=uri)
|
kwargs=dict(parent=controller.view.nb, name=title, session=buffer.session, uri=uri)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def open_timeline(self, controller, buffer, default="posts"):
|
||||||
|
if not hasattr(buffer, "get_item"):
|
||||||
|
return
|
||||||
|
item = buffer.get_item()
|
||||||
|
if not item:
|
||||||
|
output.speak(_("No user selected."), True)
|
||||||
|
return
|
||||||
|
|
||||||
|
def g(obj, key, default=None):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return obj.get(key, default)
|
||||||
|
return getattr(obj, key, default)
|
||||||
|
|
||||||
|
handle = None
|
||||||
|
if hasattr(buffer, "get_selected_item_author_details"):
|
||||||
|
details = buffer.get_selected_item_author_details()
|
||||||
|
if details:
|
||||||
|
handle = details.get("handle") or details.get("did")
|
||||||
|
if not handle:
|
||||||
|
if g(item, "handle") or g(item, "did"):
|
||||||
|
handle = g(item, "handle") or g(item, "did")
|
||||||
|
else:
|
||||||
|
author = g(item, "author") or g(g(item, "post"), "author")
|
||||||
|
if author:
|
||||||
|
handle = g(author, "handle") or g(author, "did")
|
||||||
|
|
||||||
|
if not handle:
|
||||||
|
output.speak(_("No user selected."), True)
|
||||||
|
return
|
||||||
|
|
||||||
|
from wxUI.dialogs.mastodon import userTimeline as userTimelineDialog
|
||||||
|
dlg = userTimelineDialog.UserTimeline(users=[handle], default=default)
|
||||||
|
try:
|
||||||
|
if hasattr(dlg, "autocompletion"):
|
||||||
|
dlg.autocompletion.Enable(False)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if dlg.ShowModal() != wx.ID_OK:
|
||||||
|
dlg.Destroy()
|
||||||
|
return
|
||||||
|
|
||||||
|
action = dlg.get_action()
|
||||||
|
user = dlg.get_user().strip() or handle
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
if user.startswith("@"):
|
||||||
|
user = user[1:]
|
||||||
|
user_payload = {"handle": user}
|
||||||
|
if action == "posts":
|
||||||
|
result = self.open_user_timeline(main_controller=controller, session=buffer.session, user_payload=user_payload)
|
||||||
|
elif action == "followers":
|
||||||
|
result = self.open_followers_timeline(main_controller=controller, session=buffer.session, user_payload=user_payload)
|
||||||
|
elif action == "following":
|
||||||
|
result = self.open_following_timeline(main_controller=controller, session=buffer.session, user_payload=user_payload)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if asyncio.iscoroutine(result):
|
||||||
|
call_threaded(asyncio.run, result)
|
||||||
|
|
||||||
def open_followers_timeline(self, main_controller, session, user_payload=None):
|
def open_followers_timeline(self, main_controller, session, user_payload=None):
|
||||||
actor, handle = self._resolve_actor(session, user_payload)
|
actor, handle = self._resolve_actor(session, user_payload)
|
||||||
if not actor:
|
if not actor:
|
||||||
@@ -333,13 +417,28 @@ class Handler:
|
|||||||
return
|
return
|
||||||
self._open_user_list(main_controller, session, actor, handle, list_type="following")
|
self._open_user_list(main_controller, session, actor, handle, list_type="following")
|
||||||
|
|
||||||
async def open_user_timeline(self, main_controller, session, user_payload=None):
|
def open_user_timeline(self, main_controller, session, user_payload=None):
|
||||||
"""Open posts timeline for a user (Alt+Win+I)."""
|
"""Open posts timeline for a user (Alt+Win+I)."""
|
||||||
actor, handle = self._resolve_actor(session, user_payload)
|
actor, handle = self._resolve_actor(session, user_payload)
|
||||||
if not actor:
|
if not actor:
|
||||||
output.speak(_("No user selected."), True)
|
output.speak(_("No user selected."), True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# If we only have a handle, try to resolve DID for reliability
|
||||||
|
try:
|
||||||
|
if isinstance(actor, str) and not actor.startswith("did:"):
|
||||||
|
profile = 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
|
||||||
|
|
||||||
account_name = session.get_name()
|
account_name = session.get_name()
|
||||||
list_name = f"{handle}-timeline"
|
list_name = f"{handle}-timeline"
|
||||||
if main_controller.search_buffer(list_name, account_name):
|
if main_controller.search_buffer(list_name, account_name):
|
||||||
@@ -357,8 +456,21 @@ class Handler:
|
|||||||
buffer_title=title,
|
buffer_title=title,
|
||||||
parent_tab=main_controller.view.search(account_name, account_name),
|
parent_tab=main_controller.view.search(account_name, account_name),
|
||||||
start=True,
|
start=True,
|
||||||
kwargs=dict(parent=main_controller.view.nb, name=list_name, session=session, actor=actor)
|
kwargs=dict(parent=main_controller.view.nb, name=list_name, session=session, actor=actor, handle=handle)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
timelines = session.settings["other_buffers"].get("timelines")
|
||||||
|
if timelines is None:
|
||||||
|
timelines = []
|
||||||
|
if isinstance(timelines, str):
|
||||||
|
timelines = [t for t in timelines.split(",") if t]
|
||||||
|
key = handle or actor
|
||||||
|
if key and key not in timelines:
|
||||||
|
timelines.append(key)
|
||||||
|
session.settings["other_buffers"]["timelines"] = timelines
|
||||||
|
session.settings.write()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to persist Bluesky timeline buffer")
|
||||||
|
|
||||||
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):
|
||||||
@@ -371,6 +483,14 @@ class Handler:
|
|||||||
if user_payload:
|
if user_payload:
|
||||||
actor = g(user_payload, "did") or g(user_payload, "handle")
|
actor = g(user_payload, "did") or g(user_payload, "handle")
|
||||||
handle = g(user_payload, "handle") or g(user_payload, "did")
|
handle = g(user_payload, "handle") or g(user_payload, "did")
|
||||||
|
if isinstance(actor, str):
|
||||||
|
actor = actor.strip()
|
||||||
|
if actor.startswith("@"):
|
||||||
|
actor = actor[1:]
|
||||||
|
if isinstance(handle, str):
|
||||||
|
handle = handle.strip()
|
||||||
|
if handle.startswith("@"):
|
||||||
|
handle = handle[1:]
|
||||||
if not actor:
|
if not actor:
|
||||||
actor = session.db.get("user_id") or session.db.get("user_name")
|
actor = session.db.get("user_id") or session.db.get("user_name")
|
||||||
handle = session.db.get("user_name") or actor
|
handle = session.db.get("user_name") or actor
|
||||||
|
|||||||
@@ -159,22 +159,28 @@ class Conversation(BaseBuffer):
|
|||||||
res = api.app.bsky.feed.get_post_thread(params)
|
res = api.app.bsky.feed.get_post_thread(params)
|
||||||
except Exception:
|
except Exception:
|
||||||
res = api.app.bsky.feed.get_post_thread({"uri": self.root_uri})
|
res = api.app.bsky.feed.get_post_thread({"uri": self.root_uri})
|
||||||
thread = getattr(res, "thread", None)
|
|
||||||
if not thread:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def g(obj, key, default=None):
|
def g(obj, key, default=None):
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
return obj.get(key, default)
|
return obj.get(key, default)
|
||||||
return getattr(obj, key, default)
|
return getattr(obj, key, default)
|
||||||
|
|
||||||
# Find the root of the thread tree
|
thread = getattr(res, "thread", None) or (res.get("thread") if isinstance(res, dict) else None)
|
||||||
curr = thread
|
if not thread:
|
||||||
while g(curr, "parent"):
|
return 0
|
||||||
curr = g(curr, "parent")
|
|
||||||
|
|
||||||
final_items = []
|
final_items = []
|
||||||
|
|
||||||
|
# Add parent chain (oldest to newest) if available
|
||||||
|
ancestors = []
|
||||||
|
parent = g(thread, "parent")
|
||||||
|
while parent:
|
||||||
|
ppost = g(parent, "post")
|
||||||
|
if ppost:
|
||||||
|
ancestors.insert(0, ppost)
|
||||||
|
parent = g(parent, "parent")
|
||||||
|
final_items.extend(ancestors)
|
||||||
|
|
||||||
def traverse(node):
|
def traverse(node):
|
||||||
if not node:
|
if not node:
|
||||||
return
|
return
|
||||||
@@ -185,7 +191,7 @@ class Conversation(BaseBuffer):
|
|||||||
for r in replies:
|
for r in replies:
|
||||||
traverse(r)
|
traverse(r)
|
||||||
|
|
||||||
traverse(curr)
|
traverse(thread)
|
||||||
|
|
||||||
# Clear existing items to avoid duplication when refreshing a thread view (which changes structure little)
|
# 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] = []
|
||||||
@@ -324,6 +330,7 @@ class UserTimeline(BaseBuffer):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.actor = kwargs.get("actor")
|
self.actor = kwargs.get("actor")
|
||||||
|
self.handle = kwargs.get("handle")
|
||||||
super(UserTimeline, self).__init__(*args, **kwargs)
|
super(UserTimeline, self).__init__(*args, **kwargs)
|
||||||
self.type = "user_timeline"
|
self.type = "user_timeline"
|
||||||
|
|
||||||
@@ -341,13 +348,19 @@ class UserTimeline(BaseBuffer):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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:
|
||||||
res = api.app.bsky.feed.get_author_feed({
|
res = api.app.bsky.feed.get_author_feed({
|
||||||
"actor": self.actor,
|
"actor": actor,
|
||||||
"limit": count,
|
"limit": count,
|
||||||
})
|
})
|
||||||
items = getattr(res, "feed", []) or []
|
items = getattr(res, "feed", []) or []
|
||||||
@@ -357,6 +370,34 @@ class UserTimeline(BaseBuffer):
|
|||||||
|
|
||||||
return self.process_items(list(items), play_sound)
|
return self.process_items(list(items), play_sound)
|
||||||
|
|
||||||
|
def remove_buffer(self, force=False):
|
||||||
|
if not force:
|
||||||
|
from wxUI import commonMessageDialogs
|
||||||
|
import widgetUtils
|
||||||
|
dlg = commonMessageDialogs.remove_buffer()
|
||||||
|
if dlg != 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")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class SearchBuffer(BaseBuffer):
|
class SearchBuffer(BaseBuffer):
|
||||||
"""Buffer for search results (posts)."""
|
"""Buffer for search results (posts)."""
|
||||||
|
|||||||
@@ -1739,10 +1739,9 @@ class Controller(object):
|
|||||||
if author_details:
|
if author_details:
|
||||||
user_payload = author_details
|
user_payload = author_details
|
||||||
|
|
||||||
async def _open_timeline():
|
result = handler.open_user_timeline(main_controller=self, session=session_to_use, user_payload=user_payload)
|
||||||
# Pass self (mainController) to the handler method so it can call self.add_buffer
|
if asyncio.iscoroutine(result):
|
||||||
await handler.open_user_timeline(main_controller=self, session=session_to_use, user_payload=user_payload)
|
call_threaded(asyncio.run, result)
|
||||||
wx.CallAfter(asyncio.create_task, _open_timeline())
|
|
||||||
|
|
||||||
elif hasattr(handler, 'openPostTimeline'): # Fallback for older handler structure
|
elif hasattr(handler, 'openPostTimeline'): # Fallback for older handler structure
|
||||||
# This path might not correctly pass main_controller if the old handler expects it differently
|
# This path might not correctly pass main_controller if the old handler expects it differently
|
||||||
|
|||||||
Reference in New Issue
Block a user