- **Soundpack Manager**: Full-featured package discovery, download, and installation system - Repository management with HTTPS enforcement and validation - Directory listing support (auto-discovers .zip files) + soundpacknames.txt fallback - Secure download with size limits, timeout protection, and path sanitization - Zip bomb protection: file count, individual size, and total extraction limits - Audio file validation using magic numbers (not just extensions) - Accessible UI with keyboard navigation and screen reader optimization - **Auto-refresh system**: Smart timeline updates respecting user activity - 300-second default interval + 10-second idle buffer - Keyboard activity tracking prevents interrupting active users - True new content detection using post IDs instead of counts - Preserves cursor position during background refreshes - **Enhanced notifications**: Fixed spam issues and improved targeting - Timeline switching now silent (no notifications for actively viewed content) - Initial app load notifications disabled for 2 seconds - Generic "New content in timeline" messages instead of misleading post counts - Separate handling for auto-refresh vs manual refresh scenarios - **Load more posts improvements**: Better positioning and user experience - New posts load below cursor position instead of above - Cursor automatically focuses on first new post for natural reading flow - Fixed widget hierarchy issues preventing activation - **Accessibility enhancements**: Workarounds for screen reader quirks - Added list headers to fix Orca single-item list reading issues - Improved accessible names and descriptions throughout - Non-selectable header items with dynamic counts - Better error messages and status updates - **Settings integration**: Corrected soundpack configuration management - Fixed inconsistent config keys (now uses [audio] sound_pack consistently) - Added soundpack manager to File menu (Ctrl+Shift+P) - Repository persistence and validation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
"""
|
|
Desktop notification manager using plyer
|
|
"""
|
|
|
|
from typing import Optional
|
|
from plyer import notification
|
|
from config.settings import SettingsManager
|
|
|
|
|
|
class NotificationManager:
|
|
"""Manages desktop notifications for Bifrost"""
|
|
|
|
def __init__(self, settings: SettingsManager):
|
|
self.settings = settings
|
|
|
|
def is_enabled(self, notification_type: str = None) -> bool:
|
|
"""Check if notifications are enabled globally or for specific type"""
|
|
if not self.settings.get_bool('notifications', 'enabled', True):
|
|
return False
|
|
|
|
if notification_type:
|
|
return self.settings.get_bool('notifications', notification_type, True)
|
|
return True
|
|
|
|
def show_notification(self, title: str, message: str, notification_type: str = None):
|
|
"""Show a desktop notification"""
|
|
if not self.is_enabled(notification_type):
|
|
return
|
|
|
|
try:
|
|
notification.notify(
|
|
title=title,
|
|
message=message,
|
|
app_name="Bifrost",
|
|
timeout=5
|
|
)
|
|
except Exception as e:
|
|
print(f"Failed to show notification: {e}")
|
|
|
|
def notify_direct_message(self, sender: str, message_preview: str):
|
|
"""Show notification for direct message"""
|
|
if not self.is_enabled('direct_messages'):
|
|
return
|
|
|
|
self.show_notification(
|
|
title=f"Direct message from {sender}",
|
|
message=message_preview,
|
|
notification_type='direct_messages'
|
|
)
|
|
|
|
def notify_mention(self, sender: str, post_preview: str):
|
|
"""Show notification for mention"""
|
|
if not self.is_enabled('mentions'):
|
|
return
|
|
|
|
self.show_notification(
|
|
title=f"{sender} mentioned you",
|
|
message=post_preview,
|
|
notification_type='mentions'
|
|
)
|
|
|
|
def notify_boost(self, sender: str, post_preview: str):
|
|
"""Show notification for boost/reblog"""
|
|
if not self.is_enabled('boosts'):
|
|
return
|
|
|
|
self.show_notification(
|
|
title=f"{sender} boosted your post",
|
|
message=post_preview,
|
|
notification_type='boosts'
|
|
)
|
|
|
|
def notify_favorite(self, sender: str, post_preview: str):
|
|
"""Show notification for favorite"""
|
|
if not self.is_enabled('favorites'):
|
|
return
|
|
|
|
self.show_notification(
|
|
title=f"{sender} favorited your post",
|
|
message=post_preview,
|
|
notification_type='favorites'
|
|
)
|
|
|
|
def notify_follow(self, follower: str):
|
|
"""Show notification for new follower"""
|
|
if not self.is_enabled('follows'):
|
|
return
|
|
|
|
self.show_notification(
|
|
title="New follower",
|
|
message=f"{follower} started following you",
|
|
notification_type='follows'
|
|
)
|
|
|
|
def notify_timeline_update(self, count: int, timeline_type: str = "timeline"):
|
|
"""Show notification for timeline updates"""
|
|
if not self.is_enabled('timeline_updates'):
|
|
return
|
|
|
|
if count == 1:
|
|
message = f"1 new post in your {timeline_type}"
|
|
else:
|
|
message = f"{count} new posts in your {timeline_type}"
|
|
|
|
self.show_notification(
|
|
title="Timeline updated",
|
|
message=message,
|
|
notification_type='timeline_updates'
|
|
)
|
|
|
|
def notify_new_content(self, timeline_type: str = "timeline"):
|
|
"""Show notification for new content without counting posts"""
|
|
if not self.is_enabled('timeline_updates'):
|
|
return
|
|
|
|
self.show_notification(
|
|
title="Timeline updated",
|
|
message=f"New content in your {timeline_type}",
|
|
notification_type='timeline_updates'
|
|
) |