mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2026-03-07 09:57:32 +01:00
Avance
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import wx
|
||||
import output
|
||||
from wxUI.dialogs.blueski.showUserProfile import ShowUserProfileDialog
|
||||
from typing import Any
|
||||
import languageHandler # Ensure _() injection
|
||||
|
||||
@@ -19,10 +22,16 @@ class Handler:
|
||||
|
||||
def create_buffers(self, session, createAccounts=True, controller=None):
|
||||
name = session.get_name()
|
||||
controller.accounts.append(name)
|
||||
if createAccounts:
|
||||
from pubsub import pub
|
||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=True)
|
||||
pub.sendMessage("core.create_account", name=name, session_id=session.session_id, logged=session.logged)
|
||||
|
||||
if not session.logged:
|
||||
logger.debug(f"Session {session.session_id} is not logged in, skipping timeline buffer creation.")
|
||||
return
|
||||
if name not in controller.accounts:
|
||||
controller.accounts.append(name)
|
||||
|
||||
root_position = controller.view.search(name, name)
|
||||
# Discover/home timeline
|
||||
from pubsub import pub
|
||||
@@ -45,6 +54,66 @@ class Handler:
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="following_timeline", session=session)
|
||||
)
|
||||
# Notifications
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="notifications",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Notifications"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="notifications", session=session)
|
||||
)
|
||||
# Likes
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="likes",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Likes"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="likes", session=session)
|
||||
)
|
||||
# Followers
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="FollowersBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Followers"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="followers", session=session)
|
||||
)
|
||||
# Following (Users)
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="FollowingBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Following (Users)"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="following", session=session)
|
||||
)
|
||||
# Blocks
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="BlocksBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Blocked Users"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="blocked", session=session)
|
||||
)
|
||||
# Chats
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="ConversationListBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=_("Chats"),
|
||||
parent_tab=root_position,
|
||||
start=False,
|
||||
kwargs=dict(parent=controller.view.nb, name="direct_messages", session=session)
|
||||
)
|
||||
|
||||
def start_buffer(self, controller, buffer):
|
||||
"""Start a newly created Bluesky buffer."""
|
||||
@@ -86,6 +155,45 @@ class Handler:
|
||||
except Exception:
|
||||
logger.exception("Error opening Bluesky account settings dialog")
|
||||
|
||||
def user_details(self, buffer):
|
||||
"""Show user profile dialog for the selected user/post."""
|
||||
session = getattr(buffer, "session", None)
|
||||
if not session:
|
||||
output.speak(_("No active session to view user details."), True)
|
||||
return
|
||||
|
||||
item = buffer.get_item() if hasattr(buffer, "get_item") else None
|
||||
if not item:
|
||||
output.speak(_("No user selected or identified to view details."), True)
|
||||
return
|
||||
|
||||
def g(obj, key, default=None):
|
||||
if isinstance(obj, dict):
|
||||
return obj.get(key, default)
|
||||
return getattr(obj, key, default)
|
||||
|
||||
user_ident = None
|
||||
|
||||
# If we're in a user list, the item itself is the user profile dict/model.
|
||||
if g(item, "did") or g(item, "handle"):
|
||||
user_ident = g(item, "did") or g(item, "handle")
|
||||
else:
|
||||
author = g(item, "author")
|
||||
if not author:
|
||||
post = g(item, "post") or g(item, "record")
|
||||
author = g(post, "author") if post else None
|
||||
if author:
|
||||
user_ident = g(author, "did") or g(author, "handle")
|
||||
|
||||
if not user_ident:
|
||||
output.speak(_("No user selected or identified to view details."), True)
|
||||
return
|
||||
|
||||
parent = getattr(buffer, "buffer", None) or wx.GetApp().GetTopWindow()
|
||||
dialog = ShowUserProfileDialog(parent, session, user_ident)
|
||||
dialog.ShowModal()
|
||||
dialog.Destroy()
|
||||
|
||||
async def handle_action(self, action_name: str, user_id: str, payload: dict[str, Any]) -> dict[str, Any] | None:
|
||||
logger.debug("handle_action stub: %s %s %s", action_name, user_id, payload)
|
||||
return None
|
||||
@@ -97,3 +205,156 @@ class Handler:
|
||||
async def handle_user_command(self, command: str, user_id: str, target_user_id: str, payload: dict[str, Any]) -> dict[str, Any] | None:
|
||||
logger.debug("handle_user_command stub: %s %s %s %s", command, user_id, target_user_id, payload)
|
||||
return None
|
||||
|
||||
def add_to_favourites(self, buffer):
|
||||
"""Standard action for Alt+Win+F"""
|
||||
if hasattr(buffer, "add_to_favorites"):
|
||||
buffer.add_to_favorites()
|
||||
elif hasattr(buffer, "on_like"):
|
||||
# Fallback
|
||||
buffer.on_like(None)
|
||||
|
||||
def remove_from_favourites(self, buffer):
|
||||
"""Standard action for Alt+Shift+Win+F"""
|
||||
if hasattr(buffer, "remove_from_favorites"):
|
||||
buffer.remove_from_favorites()
|
||||
elif hasattr(buffer, "on_like"):
|
||||
buffer.on_like(None)
|
||||
|
||||
def follow(self, buffer):
|
||||
"""Standard action for Ctrl+Win+S"""
|
||||
session = getattr(buffer, "session", None)
|
||||
if not session:
|
||||
output.speak(_("No active session."), True)
|
||||
return
|
||||
|
||||
def g(obj, key, default=None):
|
||||
if isinstance(obj, dict):
|
||||
return obj.get(key, default)
|
||||
return getattr(obj, key, default)
|
||||
|
||||
user_ident = None
|
||||
item = buffer.get_item() if hasattr(buffer, "get_item") else None
|
||||
if item:
|
||||
if g(item, "handle") or g(item, "did"):
|
||||
user_ident = g(item, "handle") or g(item, "did")
|
||||
else:
|
||||
author = g(item, "author")
|
||||
if not author:
|
||||
post = g(item, "post") or g(item, "record")
|
||||
author = g(post, "author") if post else None
|
||||
if author:
|
||||
user_ident = g(author, "handle") or g(author, "did")
|
||||
|
||||
users = [user_ident] if user_ident else []
|
||||
from controller.blueski import userActions as user_actions_controller
|
||||
user_actions_controller.userActions(session, users)
|
||||
|
||||
def open_conversation(self, controller, buffer):
|
||||
"""Standard action for Control+Win+C"""
|
||||
item = buffer.get_item()
|
||||
if not item:
|
||||
return
|
||||
|
||||
uri = None
|
||||
if hasattr(buffer, "get_selected_item_id"):
|
||||
uri = buffer.get_selected_item_id()
|
||||
if not uri:
|
||||
uri = getattr(item, "uri", None) or (item.get("post", {}).get("uri") if isinstance(item, dict) else None)
|
||||
if not uri: return
|
||||
|
||||
# Buffer Title
|
||||
author = getattr(item, "author", None) or (item.get("post", {}).get("author") if isinstance(item, dict) else None)
|
||||
handle = getattr(author, "handle", "unknown") if author else "unknown"
|
||||
title = _("Conversation with {0}").format(handle)
|
||||
|
||||
from pubsub import pub
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="conversation",
|
||||
session_type="blueski",
|
||||
buffer_title=title,
|
||||
parent_tab=controller.view.search(buffer.session.get_name(), buffer.session.get_name()) if hasattr(buffer.session, "get_name") else None,
|
||||
start=True,
|
||||
kwargs=dict(parent=controller.view.nb, name=title, session=buffer.session, uri=uri)
|
||||
)
|
||||
|
||||
def open_followers_timeline(self, main_controller, session, user_payload=None):
|
||||
actor, handle = self._resolve_actor(session, user_payload)
|
||||
if not actor:
|
||||
output.speak(_("No user selected."), True)
|
||||
return
|
||||
self._open_user_list(main_controller, session, actor, handle, list_type="followers")
|
||||
|
||||
def open_following_timeline(self, main_controller, session, user_payload=None):
|
||||
actor, handle = self._resolve_actor(session, user_payload)
|
||||
if not actor:
|
||||
output.speak(_("No user selected."), True)
|
||||
return
|
||||
self._open_user_list(main_controller, session, actor, handle, list_type="following")
|
||||
|
||||
def _resolve_actor(self, session, user_payload):
|
||||
def g(obj, key, default=None):
|
||||
if isinstance(obj, dict):
|
||||
return obj.get(key, default)
|
||||
return getattr(obj, key, default)
|
||||
|
||||
actor = None
|
||||
handle = None
|
||||
if user_payload:
|
||||
actor = g(user_payload, "did") or g(user_payload, "handle")
|
||||
handle = g(user_payload, "handle") or g(user_payload, "did")
|
||||
if not actor:
|
||||
actor = session.db.get("user_id") or session.db.get("user_name")
|
||||
handle = session.db.get("user_name") or actor
|
||||
return actor, handle
|
||||
|
||||
def _open_user_list(self, main_controller, session, actor, handle, list_type):
|
||||
account_name = session.get_name()
|
||||
own_actor = session.db.get("user_id") or session.db.get("user_name")
|
||||
own_handle = session.db.get("user_name")
|
||||
if actor == own_actor or (own_handle and actor == own_handle):
|
||||
name = "followers" if list_type == "followers" else "following"
|
||||
index = main_controller.view.search(name, account_name)
|
||||
if index is not None:
|
||||
main_controller.view.change_buffer(index)
|
||||
return
|
||||
|
||||
list_name = f"{handle}-{list_type}"
|
||||
if main_controller.search_buffer(list_name, account_name):
|
||||
index = main_controller.view.search(list_name, account_name)
|
||||
if index is not None:
|
||||
main_controller.view.change_buffer(index)
|
||||
return
|
||||
|
||||
title = _("Followers for {user}").format(user=handle) if list_type == "followers" else _("Following for {user}").format(user=handle)
|
||||
from pubsub import pub
|
||||
pub.sendMessage(
|
||||
"createBuffer",
|
||||
buffer_type="FollowersBuffer" if list_type == "followers" else "FollowingBuffer",
|
||||
session_type="blueski",
|
||||
buffer_title=title,
|
||||
parent_tab=main_controller.view.search(account_name, account_name),
|
||||
start=True,
|
||||
kwargs=dict(parent=main_controller.view.nb, name=list_name, session=session, actor=actor)
|
||||
)
|
||||
|
||||
def delete(self, buffer, controller):
|
||||
"""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):
|
||||
import output
|
||||
output.speak(_("Post deleted."))
|
||||
# Refresh buffer
|
||||
if hasattr(buffer, "start_stream"):
|
||||
buffer.start_stream(mandatory=True, play_sound=False)
|
||||
else:
|
||||
import output
|
||||
output.speak(_("Failed to delete post."))
|
||||
|
||||
@@ -1,75 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
import widgetUtils
|
||||
import output
|
||||
from wxUI.dialogs.blueski import userActions as userActionsDialog
|
||||
import languageHandler
|
||||
|
||||
fromapprove.translation import translate as _
|
||||
# fromapprove.controller.mastodon import userActions as mastodon_user_actions # If adapting
|
||||
|
||||
if TYPE_CHECKING:
|
||||
fromapprove.sessions.blueski.session import Session as BlueskiSession # Adjusted
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# This file defines user-specific actions that can be performed on Blueski entities,
|
||||
# typically represented as buttons or links in the UI, often on user profiles or posts.
|
||||
|
||||
# For Blueski, actions might include:
|
||||
# - Viewing a user's profile on Bluesky/Blueski instance.
|
||||
# - Following/Unfollowing a user.
|
||||
# - Muting/Blocking a user.
|
||||
# - Reporting a user.
|
||||
# - Fetching a user's latest posts.
|
||||
|
||||
# These actions are often presented in a context menu or as direct buttons.
|
||||
# The `get_user_actions` method in the BlueskiSession class would define these.
|
||||
# This file would contain the implementation or further handling logic if needed,
|
||||
# or if actions are too complex for simple lambda/method calls in the session class.
|
||||
|
||||
# Example structure for defining an action:
|
||||
# (This might be more detailed if actions require forms or multi-step processes)
|
||||
|
||||
# def view_profile_action(session: BlueskiSession, user_id: str) -> dict[str, Any]:
|
||||
# """
|
||||
# Generates data for a "View Profile on Blueski" action.
|
||||
# user_id here would be the Blueski DID or handle.
|
||||
# """
|
||||
# # profile_url = f"https://bsky.app/profile/{user_id}" # Example, construct from handle or DID
|
||||
# # This might involve resolving DID to handle or vice-versa if only one is known.
|
||||
# # handle = await session.util.get_username_from_user_id(user_id) or user_id
|
||||
# # profile_url = f"https://bsky.app/profile/{handle}"
|
||||
|
||||
# return {
|
||||
# "id": "blueski_view_profile",
|
||||
# "label": _("View Profile on Bluesky"),
|
||||
# "icon": "external-link-alt", # FontAwesome icon name
|
||||
# "action_type": "link", # "link", "modal", "api_call"
|
||||
# "url": profile_url, # For "link" type
|
||||
# # "api_endpoint": "/api/blueski/user_action", # For "api_call"
|
||||
# # "payload": {"action": "view_profile", "target_user_id": user_id},
|
||||
# "confirmation_required": False,
|
||||
# }
|
||||
log = logging.getLogger("controller.blueski.userActions")
|
||||
|
||||
|
||||
# async def follow_user_action_handler(session: BlueskiSession, target_user_id: str) -> dict[str, Any]:
|
||||
# """
|
||||
# Handles the 'follow_user' action for Blueski.
|
||||
# target_user_id should be the DID of the user to follow.
|
||||
# """
|
||||
# # success = await session.util.follow_user(target_user_id)
|
||||
# # if success:
|
||||
# # return {"status": "success", "message": _("User {target_user_id} followed.").format(target_user_id=target_user_id)}
|
||||
# # else:
|
||||
# # return {"status": "error", "message": _("Failed to follow user {target_user_id}.").format(target_user_id=target_user_id)}
|
||||
# return {"status": "pending", "message": "Follow action not implemented yet."}
|
||||
class BasicUserSelector(object):
|
||||
def __init__(self, session, users=None):
|
||||
super(BasicUserSelector, self).__init__()
|
||||
self.session = session
|
||||
self.create_dialog(users=users or [])
|
||||
|
||||
def create_dialog(self, users):
|
||||
pass
|
||||
|
||||
def resolve_profile(self, actor):
|
||||
try:
|
||||
return self.session.get_profile(actor)
|
||||
except Exception:
|
||||
log.exception("Error resolving Bluesky profile for %s.", actor)
|
||||
return None
|
||||
|
||||
|
||||
# The list of available actions is typically defined in the Session class,
|
||||
# e.g., BlueskiSession.get_user_actions(). That method would return a list
|
||||
# of dictionaries, and this file might provide handlers for more complex actions
|
||||
# if they aren't simple API calls defined directly in the session's util.
|
||||
class userActions(BasicUserSelector):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(userActions, self).__init__(*args, **kwargs)
|
||||
if self.dialog.get_response() == widgetUtils.OK:
|
||||
self.process_action()
|
||||
|
||||
# For now, this file can be a placeholder if most actions are simple enough
|
||||
# to be handled directly by the session.util methods or basic handler routes.
|
||||
def create_dialog(self, users):
|
||||
self.dialog = userActionsDialog.UserActionsDialog(users)
|
||||
|
||||
logger.info("Blueski userActions module loaded (placeholders).")
|
||||
def process_action(self):
|
||||
action = self.dialog.get_action()
|
||||
actor = self.dialog.get_user().strip()
|
||||
if not actor:
|
||||
output.speak(_("No user specified."), True)
|
||||
return
|
||||
|
||||
profile = self.resolve_profile(actor)
|
||||
if not profile:
|
||||
output.speak(_("User not found."), True)
|
||||
return
|
||||
|
||||
def g(obj, key, default=None):
|
||||
if isinstance(obj, dict):
|
||||
return obj.get(key, default)
|
||||
return getattr(obj, key, default)
|
||||
|
||||
did = g(profile, "did")
|
||||
viewer = g(profile, "viewer") or {}
|
||||
|
||||
if not did:
|
||||
output.speak(_("User identifier not available."), True)
|
||||
return
|
||||
|
||||
if action == "follow":
|
||||
if self.session.follow_user(did):
|
||||
output.speak(_("Followed."))
|
||||
else:
|
||||
output.speak(_("Failed to follow user."), True)
|
||||
elif action == "unfollow":
|
||||
follow_uri = g(viewer, "following")
|
||||
if not follow_uri:
|
||||
output.speak(_("Follow information not available."), True)
|
||||
return
|
||||
if self.session.unfollow_user(follow_uri):
|
||||
output.speak(_("Unfollowed."))
|
||||
else:
|
||||
output.speak(_("Failed to unfollow user."), True)
|
||||
elif action == "mute":
|
||||
if self.session.mute_user(did):
|
||||
output.speak(_("Muted."))
|
||||
else:
|
||||
output.speak(_("Failed to mute user."), True)
|
||||
elif action == "unmute":
|
||||
if self.session.unmute_user(did):
|
||||
output.speak(_("Unmuted."))
|
||||
else:
|
||||
output.speak(_("Failed to unmute user."), True)
|
||||
elif action == "block":
|
||||
if self.session.block_user(did):
|
||||
output.speak(_("Blocked."))
|
||||
else:
|
||||
output.speak(_("Failed to block user."), True)
|
||||
elif action == "unblock":
|
||||
block_uri = g(viewer, "blocking")
|
||||
if not block_uri:
|
||||
output.speak(_("Block information not available."), True)
|
||||
return
|
||||
if self.session.unblock_user(block_uri):
|
||||
output.speak(_("Unblocked."))
|
||||
else:
|
||||
output.speak(_("Failed to unblock user."), True)
|
||||
|
||||
Reference in New Issue
Block a user