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.
This commit is contained in:
google-labs-jules[bot]
2025-05-26 14:11:01 +00:00
parent b4288ce51e
commit 1dffa2a6f9
16 changed files with 4525 additions and 52 deletions
+128
View File
@@ -0,0 +1,128 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
fromapprove.forms import Form, SubmitField, TextAreaField, TextField
fromapprove.translation import translate as _
if TYPE_CHECKING:
fromapprove.config import ConfigSectionProxy
fromapprove.sessions.atprotosocial.session import Session as ATProtoSocialSession # Adjusted
logger = logging.getLogger(__name__)
# This file is for defining forms and handling for ATProtoSocial-specific settings
# that might be more complex than simple key-value pairs handled by Session.get_settings_inputs.
# For ATProtoSocial, initial settings might be simple (handle, app password),
# but this structure allows for expansion.
class ATProtoSocialSettingsForm(Form):
"""
A settings form for ATProtoSocial sessions.
This would mirror the kind of settings found in Session.get_settings_inputs
but using the WTForms-like Form structure for more complex validation or layout.
"""
# Example fields - these should align with what ATProtoSocialSession.get_settings_inputs defines
# and what ATProtoSocialSession.get_configurable_values expects for its config.
# instance_url = TextField(
# _("Instance URL"),
# default="https://bsky.social", # Default PDS for Bluesky
# description=_("The base URL of your ATProtoSocial PDS instance (e.g., https://bsky.social)."),
# validators=[], # Add validators if needed, e.g., URL validator
# )
handle = TextField(
_("Bluesky Handle"),
description=_("Your Bluesky user handle (e.g., @username.bsky.social or username.bsky.social)."),
validators=[], # e.g., DataRequired()
)
app_password = TextField( # Consider PasswordField if sensitive and your Form class supports it
_("App Password"),
description=_("Your Bluesky App Password. Generate this in your Bluesky account settings."),
validators=[], # e.g., DataRequired()
)
# Add more fields as needed for ATProtoSocial configuration.
# For example, if there were specific notification settings, content filters, etc.
submit = SubmitField(_("Save ATProtoSocial Settings"))
async def get_settings_form(
user_id: str,
session: ATProtoSocialSession | None = None,
config: ConfigSectionProxy | None = None, # User-specific config for ATProtoSocial
) -> ATProtoSocialSettingsForm:
"""
Creates and pre-populates the ATProtoSocial settings form.
"""
form_data = {}
if session: # If a session exists, use its current config
# form_data["instance_url"] = session.config_get("api_base_url", "https://bsky.social")
form_data["handle"] = session.config_get("handle", "")
# App password should not be pre-filled for security.
form_data["app_password"] = ""
elif config: # Fallback to persisted config if no active session
# form_data["instance_url"] = config.api_base_url.get("https://bsky.social")
form_data["handle"] = config.handle.get("")
form_data["app_password"] = ""
form = ATProtoSocialSettingsForm(formdata=None, **form_data) # formdata=None for initial display
return form
async def process_settings_form(
form: ATProtoSocialSettingsForm,
user_id: str,
session: ATProtoSocialSession | None = None, # Pass if update should affect live session
config: ConfigSectionProxy | None = None, # User-specific config for ATProtoSocial
) -> bool:
"""
Processes the submitted ATProtoSocial settings form and updates configuration.
Returns True if successful, False otherwise.
"""
if not form.validate(): # Assuming form has a validate method
logger.warning(f"ATProtoSocial settings form validation failed for user {user_id}: {form.errors}")
return False
if not config and session: # Try to get config via session if not directly provided
# This depends on how ConfigSectionProxy is obtained.
# config = approve.config.config.sessions.atprotosocial[user_id] # Example path
pass # Needs actual way to get config proxy
if not config:
logger.error(f"Cannot process ATProtoSocial settings for user {user_id}: no config proxy available.")
return False
try:
# Update the configuration values
# await config.api_base_url.set(form.instance_url.data)
await config.handle.set(form.handle.data)
await config.app_password.set(form.app_password.data) # Ensure this is stored securely
logger.info(f"ATProtoSocial settings updated for user {user_id}.")
# If there's an active session, it might need to be reconfigured or restarted
if session:
logger.info(f"Requesting ATProtoSocial session re-initialization for user {user_id} due to settings change.")
# await session.stop() # Stop it
# # Update session instance with new values directly or rely on it re-reading config
# session.api_base_url = form.instance_url.data
# session.handle = form.handle.data
# # App password should be handled carefully, session might need to re-login
# await session.start() # Restart with new settings
# Or, more simply, the session might have a reconfigure method:
# await session.reconfigure(new_settings_dict)
pass # Placeholder for session reconfiguration logic
return True
except Exception as e:
logger.error(f"Error saving ATProtoSocial settings for user {user_id}: {e}", exc_info=True)
return False
# Any additional ATProtoSocial-specific settings views or handlers would go here.
# For instance, if ATProtoSocial had features like "Relays" or "Feed Generators"
# that needed UI configuration within Approve, those forms and handlers could be defined here.
logger.info("ATProtoSocial settings module loaded (placeholders).")