feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import logging
|
2026-01-11 20:13:56 +01:00
|
|
|
import wx
|
2026-02-01 13:01:32 +01:00
|
|
|
import asyncio
|
2026-01-11 20:13:56 +01:00
|
|
|
import output
|
2026-02-01 13:01:32 +01:00
|
|
|
from mysc.thread_utils import call_threaded
|
2026-01-11 20:13:56 +01:00
|
|
|
from wxUI.dialogs.blueski.showUserProfile import ShowUserProfileDialog
|
2025-08-30 22:48:00 +02:00
|
|
|
from typing import Any
|
|
|
|
|
import languageHandler # Ensure _() injection
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
2025-08-30 22:48:00 +02:00
|
|
|
class Handler:
|
|
|
|
|
"""Handler for Bluesky integration: creates minimal buffers."""
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
|
2025-08-30 22:48:00 +02:00
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.menus = dict(
|
|
|
|
|
compose="&Post",
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
)
|
2025-08-30 22:48:00 +02:00
|
|
|
self.item_menu = "&Post"
|
|
|
|
|
|
|
|
|
|
def create_buffers(self, session, createAccounts=True, controller=None):
|
|
|
|
|
name = session.get_name()
|
|
|
|
|
if createAccounts:
|
|
|
|
|
from pubsub import pub
|
2026-01-11 20:13:56 +01:00
|
|
|
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)
|
|
|
|
|
|
2025-08-30 22:48:00 +02:00
|
|
|
root_position = controller.view.search(name, name)
|
2026-01-10 19:46:53 +01:00
|
|
|
# Discover/home timeline
|
2025-08-30 22:48:00 +02:00
|
|
|
from pubsub import pub
|
|
|
|
|
pub.sendMessage(
|
|
|
|
|
"createBuffer",
|
|
|
|
|
buffer_type="home_timeline",
|
2026-01-10 19:46:53 +01:00
|
|
|
session_type="blueski",
|
|
|
|
|
buffer_title=_("Discover"),
|
2025-08-30 22:48:00 +02:00
|
|
|
parent_tab=root_position,
|
|
|
|
|
start=True,
|
|
|
|
|
kwargs=dict(parent=controller.view.nb, name="home_timeline", session=session)
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
)
|
2026-02-01 10:42:05 +01:00
|
|
|
# Home (Following-only timeline - reverse-chronological)
|
2025-08-30 22:48:00 +02:00
|
|
|
pub.sendMessage(
|
|
|
|
|
"createBuffer",
|
|
|
|
|
buffer_type="following_timeline",
|
2026-01-10 19:46:53 +01:00
|
|
|
session_type="blueski",
|
2026-02-01 10:42:05 +01:00
|
|
|
buffer_title=_("Home"),
|
2025-08-30 22:48:00 +02:00
|
|
|
parent_tab=root_position,
|
|
|
|
|
start=False,
|
|
|
|
|
kwargs=dict(parent=controller.view.nb, name="following_timeline", session=session)
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
)
|
2026-02-01 10:42:05 +01:00
|
|
|
# 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)
|
|
|
|
|
)
|
2026-01-11 20:13:56 +01:00
|
|
|
# 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)
|
|
|
|
|
)
|
2026-02-01 10:42:05 +01:00
|
|
|
# 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)
|
|
|
|
|
)
|
2026-01-11 20:13:56 +01:00
|
|
|
# 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)
|
|
|
|
|
)
|
2026-02-01 10:42:05 +01:00
|
|
|
# Followings (Users you follow)
|
2026-01-11 20:13:56 +01:00
|
|
|
pub.sendMessage(
|
|
|
|
|
"createBuffer",
|
|
|
|
|
buffer_type="FollowingBuffer",
|
|
|
|
|
session_type="blueski",
|
2026-02-01 10:42:05 +01:00
|
|
|
buffer_title=_("Followings"),
|
2026-01-11 20:13:56 +01:00
|
|
|
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)
|
|
|
|
|
)
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
|
2026-02-01 13:07:16 +01:00
|
|
|
# Timelines container
|
|
|
|
|
pub.sendMessage(
|
|
|
|
|
"createBuffer",
|
|
|
|
|
buffer_type="EmptyBuffer",
|
|
|
|
|
session_type="base",
|
|
|
|
|
buffer_title=_("Timelines"),
|
|
|
|
|
parent_tab=root_position,
|
|
|
|
|
start=False,
|
|
|
|
|
kwargs=dict(parent=controller.view.nb, name="timelines", account=name)
|
|
|
|
|
)
|
|
|
|
|
timelines_position = controller.view.search("timelines", name)
|
|
|
|
|
|
2026-02-01 13:01:32 +01:00
|
|
|
# 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,
|
2026-02-01 13:07:16 +01:00
|
|
|
parent_tab=timelines_position,
|
2026-02-01 13:01:32 +01:00
|
|
|
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")
|
|
|
|
|
|
2026-02-01 10:42:05 +01:00
|
|
|
# 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)
|
|
|
|
|
|
2025-08-30 22:48:00 +02:00
|
|
|
def start_buffer(self, controller, buffer):
|
|
|
|
|
"""Start a newly created Bluesky buffer."""
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
try:
|
2025-08-30 22:48:00 +02:00
|
|
|
if hasattr(buffer, "start_stream"):
|
|
|
|
|
buffer.start_stream(mandatory=True, play_sound=False)
|
|
|
|
|
# Enable periodic auto-refresh to simulate real-time updates
|
|
|
|
|
if hasattr(buffer, "enable_auto_refresh"):
|
|
|
|
|
buffer.enable_auto_refresh()
|
|
|
|
|
finally:
|
|
|
|
|
# Ensure we won't try to start it again
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
try:
|
2025-08-30 22:48:00 +02:00
|
|
|
buffer.needs_init = False
|
|
|
|
|
except Exception:
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
pass
|
|
|
|
|
|
2025-11-07 09:24:02 +01:00
|
|
|
def account_settings(self, buffer, controller):
|
|
|
|
|
"""Open a minimal account settings dialog for Bluesky."""
|
|
|
|
|
try:
|
|
|
|
|
current_mode = None
|
|
|
|
|
try:
|
|
|
|
|
current_mode = buffer.session.settings["general"].get("boost_mode")
|
|
|
|
|
except Exception:
|
|
|
|
|
current_mode = None
|
|
|
|
|
ask_default = True if current_mode in (None, "ask") else False
|
|
|
|
|
|
2026-01-10 19:46:53 +01:00
|
|
|
from wxUI.dialogs.blueski.configuration import AccountSettingsDialog
|
2025-11-07 09:24:02 +01:00
|
|
|
dlg = AccountSettingsDialog(controller.view, ask_before_boost=ask_default)
|
|
|
|
|
resp = dlg.ShowModal()
|
|
|
|
|
if resp == wx.ID_OK:
|
|
|
|
|
vals = dlg.get_values()
|
|
|
|
|
boost_mode = "ask" if vals.get("ask_before_boost") else "direct"
|
|
|
|
|
try:
|
|
|
|
|
buffer.session.settings["general"]["boost_mode"] = boost_mode
|
|
|
|
|
buffer.session.settings.write()
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.exception("Failed to persist Bluesky boost_mode setting")
|
|
|
|
|
dlg.Destroy()
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.exception("Error opening Bluesky account settings dialog")
|
|
|
|
|
|
2026-01-11 20:13:56 +01:00
|
|
|
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()
|
|
|
|
|
|
2025-08-30 22:48:00 +02:00
|
|
|
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
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
|
2025-08-30 22:48:00 +02:00
|
|
|
async def handle_message_command(self, command: str, user_id: str, message_id: str, payload: dict[str, Any]) -> dict[str, Any] | None:
|
|
|
|
|
logger.debug("handle_message_command stub: %s %s %s %s", command, user_id, message_id, payload)
|
|
|
|
|
return None
|
feat: Initial integration of ATProtoSocial (Bluesky) protocol
This commit introduces the initial implementation for supporting the ATProtoSocial (Bluesky) protocol within your application.
Key changes and features I implemented:
1. **Core Protocol Structure:**
* I added new directories `src/sessions/atprotosocial` and `src/controller/atprotosocial`.
* I populated these with foundational files (`session.py`, `utils.py`, `handler.py`, `compose.py`, etc.), mirroring the Mastodon implementation structure but adapted for ATProtoSocial.
2. **Authentication:**
* I implemented login and authorization using Bluesky SDK (handle and app password) in `sessions/atprotosocial/session.py`.
* I integrated this into your session management UI (`sessionManagerDialog.py`) to allow adding ATProtoSocial accounts.
3. **Posting Capabilities:**
* I implemented sending text posts, posts with images, replies, and quoting posts in `sessions/atprotosocial/session.py` and `utils.py`.
* I updated `compose.py` to reflect ATProtoSocial's panel configuration (character limits, media support, quoting).
4. **Notifications:**
* I implemented fetching and processing of notifications (likes, reposts, follows, mentions, replies, quotes) in `sessions/atprotosocial/session.py`.
* Notifications are formatted for display.
5. **Timelines:**
* I implemented fetching and processing for home timeline and user-specific timelines in `sessions/atprotosocial/session.py`.
* This includes handling of posts, reposts, and replies within your application's buffer and message cache system.
6. **User Actions:**
* I implemented core user actions: follow, unfollow, mute, unmute, block, unblock in `sessions/atprotosocial/utils.py`.
* I integrated these actions into the controller layer (`controller/atprotosocial/handler.py`) and exposed them via `session.get_user_actions()`.
7. **User Management & Profile:**
* I implemented fetching user profiles, follower lists, following lists, and user search in `sessions/atprotosocial/utils.py` and `controller/atprotosocial/userList.py`.
8. **UI Integration (Initial Pass):**
* I adapted your session management UI for ATProtoSocial account creation.
* I updated main controller logic to load the ATProtoSocial handler and create basic buffers (Home, Notifications).
* I modified menu item labels based on the active session type (e.g., "Post" vs "Toot", "Like" vs "Favorite").
* I integrated core actions like reposting and liking into existing UI flows.
* I added basic integration for timeline refresh and loading more items.
* I added placeholder integration for viewing user profiles and user-specific timelines.
**Current Status & Next Steps:**
This represents a significant portion of the ATProtoSocial integration. The backend logic for most core features is in place. The immediate next steps, which were part of the original plan but not yet completed, would be:
* **Refining UI elements:** Fully implementing dedicated dialogs (compose, user profile), custom panels for new buffer types, and ensuring accurate rendering of ATProtoSocial posts and notifications.
* **Completing Documentation:** Updating all relevant documentation files in `doc/` and `documentation/`.
* **Updating Translations:** Adding new strings and updating translation files.
* **Adding Tests:** Creating unit and integration tests for the new protocol.
I was not stuck on any particular point, but the UI integration is a large step that requires iterative refinement and testing for each component, which would naturally extend beyond a single development cycle for a feature of this scope.
2025-05-26 14:11:01 +00:00
|
|
|
|
2025-08-30 22:48:00 +02:00
|
|
|
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
|
2026-01-11 20:13:56 +01:00
|
|
|
|
|
|
|
|
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
|
2026-02-01 12:39:50 +01:00
|
|
|
handle = None
|
|
|
|
|
display_name = None
|
|
|
|
|
if hasattr(buffer, "get_selected_item_author_details"):
|
|
|
|
|
details = buffer.get_selected_item_author_details()
|
|
|
|
|
if details:
|
|
|
|
|
handle = details.get("handle")
|
|
|
|
|
if not handle:
|
|
|
|
|
def g(obj, key, default=None):
|
|
|
|
|
if isinstance(obj, dict):
|
|
|
|
|
return obj.get(key, default)
|
|
|
|
|
return getattr(obj, key, default)
|
|
|
|
|
author = g(item, "author") or g(g(item, "post"), "author")
|
|
|
|
|
if author:
|
|
|
|
|
handle = g(author, "handle")
|
|
|
|
|
display_name = g(author, "displayName") or g(author, "display_name")
|
|
|
|
|
label = handle or display_name or _("Unknown")
|
|
|
|
|
title = _("Conversation with {0}").format(label)
|
2026-01-11 20:13:56 +01:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
)
|
|
|
|
|
|
2026-02-01 13:01:32 +01:00
|
|
|
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)
|
|
|
|
|
|
2026-01-11 20:13:56 +01:00
|
|
|
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")
|
|
|
|
|
|
2026-02-01 13:01:32 +01:00
|
|
|
def open_user_timeline(self, main_controller, session, user_payload=None):
|
2026-02-01 12:49:33 +01:00
|
|
|
"""Open posts timeline for a user (Alt+Win+I)."""
|
|
|
|
|
actor, handle = self._resolve_actor(session, user_payload)
|
|
|
|
|
if not actor:
|
|
|
|
|
output.speak(_("No user selected."), True)
|
|
|
|
|
return
|
|
|
|
|
|
2026-02-01 13:01:32 +01:00
|
|
|
# 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
|
|
|
|
|
|
2026-02-01 12:49:33 +01:00
|
|
|
account_name = session.get_name()
|
|
|
|
|
list_name = f"{handle}-timeline"
|
|
|
|
|
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 = _("Timeline for {user}").format(user=handle)
|
|
|
|
|
from pubsub import pub
|
|
|
|
|
pub.sendMessage(
|
|
|
|
|
"createBuffer",
|
|
|
|
|
buffer_type="UserTimeline",
|
|
|
|
|
session_type="blueski",
|
|
|
|
|
buffer_title=title,
|
2026-02-01 13:07:16 +01:00
|
|
|
parent_tab=main_controller.view.search("timelines", account_name),
|
2026-02-01 12:49:33 +01:00
|
|
|
start=True,
|
2026-02-01 13:01:32 +01:00
|
|
|
kwargs=dict(parent=main_controller.view.nb, name=list_name, session=session, actor=actor, handle=handle)
|
2026-02-01 12:49:33 +01:00
|
|
|
)
|
2026-02-01 13:01:32 +01:00
|
|
|
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
|
2026-02-01 13:07:16 +01:00
|
|
|
if key in timelines:
|
|
|
|
|
from wxUI import commonMessageDialogs
|
|
|
|
|
commonMessageDialogs.timeline_exist()
|
|
|
|
|
return
|
|
|
|
|
if key:
|
2026-02-01 13:01:32 +01:00
|
|
|
timelines.append(key)
|
|
|
|
|
session.settings["other_buffers"]["timelines"] = timelines
|
|
|
|
|
session.settings.write()
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.exception("Failed to persist Bluesky timeline buffer")
|
2026-02-01 12:49:33 +01:00
|
|
|
|
2026-01-11 20:13:56 +01:00
|
|
|
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")
|
2026-02-01 13:01:32 +01:00
|
|
|
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:]
|
2026-01-11 20:13:56 +01:00
|
|
|
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
|
2026-02-01 10:42:05 +01:00
|
|
|
|
2026-01-11 20:13:56 +01:00
|
|
|
uri = getattr(item, "uri", None) or (item.get("post", {}).get("uri") if isinstance(item, dict) else None)
|
|
|
|
|
if not uri: return
|
2026-02-01 10:42:05 +01:00
|
|
|
|
2026-01-11 20:13:56 +01:00
|
|
|
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."))
|
2026-02-01 10:42:05 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
)
|