From a4d5066156910cbd623c81e8b207a7de80db5121 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Thu, 2 Oct 2025 16:26:50 -0400 Subject: [PATCH 01/37] Added shortcuts to the database manager and some more to the menu bar. Also fixed shortcuts for seeking so they're not both s --- src/controller/mastodon/handler.py | 2 +- src/extra/autocompletionUsers/wx_manage.py | 4 ++-- src/wxUI/view.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/controller/mastodon/handler.py b/src/controller/mastodon/handler.py index d6335fab..a6e1e301 100644 --- a/src/controller/mastodon/handler.py +++ b/src/controller/mastodon/handler.py @@ -48,7 +48,7 @@ class Handler(object): addAlias=_("Add a&lias"), addToList=None, removeFromList=None, - details=_("Show user profile"), + details=_("S&how user profile"), favs=None, # In buffer Menu. community_timeline =_("Create c&ommunity timeline"), diff --git a/src/extra/autocompletionUsers/wx_manage.py b/src/extra/autocompletionUsers/wx_manage.py index 6f34a681..85455ab4 100644 --- a/src/extra/autocompletionUsers/wx_manage.py +++ b/src/extra/autocompletionUsers/wx_manage.py @@ -13,8 +13,8 @@ class autocompletionManageDialog(widgetUtils.BaseDialog): self.users = widgets.list(panel, _(u"Username"), _(u"Name"), style=wx.LC_REPORT) sizer.Add(label, 0, wx.ALL, 5) sizer.Add(self.users.list, 0, wx.ALL, 5) - self.add = wx.Button(panel, -1, _(u"Add user")) - self.remove = wx.Button(panel, -1, _(u"Remove user")) + self.add = wx.Button(panel, -1, _(u"&Add user")) + self.remove = wx.Button(panel, -1, _(u"&Remove user")) optionsBox = wx.BoxSizer(wx.HORIZONTAL) optionsBox.Add(self.add, 0, wx.ALL, 5) optionsBox.Add(self.remove, 0, wx.ALL, 5) diff --git a/src/wxUI/view.py b/src/wxUI/view.py index fd76c891..25daa1a2 100644 --- a/src/wxUI/view.py +++ b/src/wxUI/view.py @@ -19,7 +19,7 @@ class mainFrame(wx.Frame): self.menuitem_search = self.menubar_application.Append(wx.ID_ANY, _(u"&Search")) self.lists = self.menubar_application.Append(wx.ID_ANY, _(u"&Lists manager")) self.lists.Enable(False) - self.manageAliases = self.menubar_application.Append(wx.ID_ANY, _("Manage user aliases")) + self.manageAliases = self.menubar_application.Append(wx.ID_ANY, _("M&anage user aliases")) self.keystroke_editor = self.menubar_application.Append(wx.ID_ANY, _(u"&Edit keystrokes")) self.account_settings = self.menubar_application.Append(wx.ID_ANY, _(u"Account se&ttings")) self.prefs = self.menubar_application.Append(wx.ID_PREFERENCES, _(u"&Global settings")) @@ -56,7 +56,7 @@ class mainFrame(wx.Frame): self.trends = self.menubar_buffer.Append(wx.ID_ANY, _(u"New &trending topics buffer...")) self.filter = self.menubar_buffer.Append(wx.ID_ANY, _(u"Create a &filter")) self.manage_filters = self.menubar_buffer.Append(wx.ID_ANY, _(u"&Manage filters")) - self.find = self.menubar_buffer.Append(wx.ID_ANY, _(u"Find a string in the currently focused buffer...")) + self.find = self.menubar_buffer.Append(wx.ID_ANY, _(u"F&ind a string in the currently focused buffer...")) self.load_previous_items = self.menubar_buffer.Append(wx.ID_ANY, _(u"&Load previous items")) self.menubar_buffer.AppendSeparator() self.mute_buffer = self.menubar_buffer.AppendCheckItem(wx.ID_ANY, _(u"&Mute")) @@ -66,8 +66,8 @@ class mainFrame(wx.Frame): # audio menu self.menubar_audio = wx.Menu() - self.seekLeft = self.menubar_audio.Append(wx.ID_ANY, _(u"&Seek back 5 seconds")) - self.seekRight = self.menubar_audio.Append(wx.ID_ANY, _(u"&Seek forward 5 seconds")) + self.seekLeft = self.menubar_audio.Append(wx.ID_ANY, _(u"Seek &back 5 seconds")) + self.seekRight = self.menubar_audio.Append(wx.ID_ANY, _(u"Seek &forward 5 seconds")) # Help Menu self.menubar_help = wx.Menu() From b1bf2ea95ff31d0c1f2b1a4352de5a954662c793 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Mon, 20 Oct 2025 17:17:34 -0400 Subject: [PATCH 02/37] Updated shortcut for Bot account so it's not the same as bio. Also updated shortcut for posts --- src/wxUI/dialogs/mastodon/showUserProfile.py | 4 ++-- src/wxUI/dialogs/mastodon/updateProfile.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wxUI/dialogs/mastodon/showUserProfile.py b/src/wxUI/dialogs/mastodon/showUserProfile.py index c40f8035..b79887c8 100644 --- a/src/wxUI/dialogs/mastodon/showUserProfile.py +++ b/src/wxUI/dialogs/mastodon/showUserProfile.py @@ -141,7 +141,7 @@ class ShowUserProfile(wx.Dialog): mainSizer.Add(privateSizer, 0, wx.ALL | wx.CENTER) botSizer = wx.BoxSizer(wx.HORIZONTAL) - botLabel = wx.StaticText(self.panel, label=_("&Bot account: ")) + botLabel = wx.StaticText(self.panel, label=_("B&ot account: ")) botText = self.createTextCtrl(bullSwitch[user.bot], (30, 30)) botSizer.Add(botLabel, wx.SizerFlags().Center()) botSizer.Add(botText, wx.SizerFlags().Center()) @@ -154,7 +154,7 @@ class ShowUserProfile(wx.Dialog): discoverSizer.Add(discoverText, wx.SizerFlags().Center()) mainSizer.Add(discoverSizer, 0, wx.ALL | wx.CENTER) - posts = wx.Button(self.panel, label=_("{} p&osts. Click to open posts timeline").format(user.statuses_count)) + posts = wx.Button(self.panel, label=_("{} pos&ts. Click to open posts timeline").format(user.statuses_count)) # posts.SetToolTip(_("Click to open {}'s posts").format(user.display_name)) posts.Bind(wx.EVT_BUTTON, self.onPost) mainSizer.Add(posts, wx.SizerFlags().Center()) diff --git a/src/wxUI/dialogs/mastodon/updateProfile.py b/src/wxUI/dialogs/mastodon/updateProfile.py index 57852f63..9bbd46ef 100644 --- a/src/wxUI/dialogs/mastodon/updateProfile.py +++ b/src/wxUI/dialogs/mastodon/updateProfile.py @@ -119,7 +119,7 @@ class UpdateProfileDialog(wx.Dialog): self.locked = wx.CheckBox(panel, label=_("&Private account")) self.locked.SetValue(locked) - self.bot = wx.CheckBox(panel, label=_("&Bot account")) + self.bot = wx.CheckBox(panel, label=_("B&ot account")) self.bot.SetValue(bot) self.discoverable = wx.CheckBox(panel, label=_("&Discoverable account")) self.discoverable.SetValue(discoverable) From cbafb7da6910992d2f22ee0340d41936d0027a55 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Nov 2025 14:25:58 +0000 Subject: [PATCH 03/37] Add CLAUDE.md documentation for AI-assisted development This file provides guidance to Claude Code when working in this repository, including: - Development commands (run, build, test, translate) - High-level architecture overview (MVC pattern, sessions, buffers, controllers) - Key design patterns (compose functions, decorators, pub/sub events) - Important conventions and caveats for working with the codebase --- CLAUDE.md | 342 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..98c4b8e9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,342 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +TWBlue is an accessible desktop Mastodon client for Windows, built with Python 3.10 and wxPython. It provides two specialized interfaces optimized for screen reader users to interact with Mastodon instances. The application emphasizes accessibility-first design with keyboard navigation, audio feedback, and screen reader integration. + +## Development Commands + +### Running from Source +```bash +cd src +python main.py +``` + +For development from source, VLC dependencies are loaded from `../windows-dependencies/{arch}/` where arch is x86 or x64. + +### Installing Dependencies +```bash +# Install all Python dependencies +pip install -r requirements.txt + +# Initialize git submodules for Windows dependencies +git submodule init +git submodule update +``` + +### Building +```bash +# Build binary distribution (from src/ directory) +python setup.py build + +# Output will be in src/dist/ +``` + +### Testing +```bash +# Run tests using pytest +pytest + +# Tests are located in src/test/ +``` + +### Generating Documentation +```bash +cd doc +python documentation_importer.py +python generator.py +# Copy generated language folders to src/documentation/ +# Copy license.txt to src/documentation/ +``` + +### Translation Management +```bash +# Extract translation strings (from doc/ directory) +pybabel extract -o twblue.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "MCV software" --input-dirs ../src + +# Note: Translations managed via Weblate at https://weblate.mcvsoftware.com +``` + +## Architecture Overview + +TWBlue follows an MVC architecture with distinct separation between data access (Sessions), business logic (Controllers), and presentation (wxUI). + +### Core Components + +#### 1. Session Layer (`src/sessions/`) +Sessions represent authenticated connections to Mastodon instances. They manage API interactions, OAuth2 authentication, and persistent data storage. + +- **Base Session** (`sessions/base.py`): Abstract base class with configuration management, SQLiteDict persistence, and decorators for login/configuration checks +- **Mastodon Session** (`sessions/mastodon/session.py`): Implements Mastodon.py API wrapper, OAuth2 flow, and account credential management +- **Streaming** (`sessions/mastodon/streaming.py`): Real-time event listener that publishes to pub/sub system + +Key patterns: +- Sessions use `@_require_login` and `@_require_configuration` decorators +- Configuration files stored as INI format via configobj in `config/{session_id}/session.conf` +- Persistent data (caches, user lists) stored in `config/{session_id}/cache.db` using SQLiteDict +- Each session has its own sound system instance + +#### 2. Buffer System (`src/controller/buffers/`) +Buffers are the primary data structures for displaying social media content (timelines, mentions, notifications, conversations, etc.). + +**Base Buffer** (`controller/buffers/base/base.py`): +- Links buffer UI (wxPanel) with session (API access) and compose functions (data rendering) +- Handles keyboard events (F5/F6 for volume, Delete for item removal, Return for URLs) +- Manages periodic updates via `start_stream()` function +- Each buffer has a `compose_function` that formats API data for display + +**Mastodon Buffers** (`controller/buffers/mastodon/`): +- `base.py`: Mastodon-specific base buffer with timeline pagination +- `users.py`: Home timeline, mentions buffer +- `community.py`: Local/federated timelines +- `notifications.py`: System notifications +- `conversations.py`: Direct message threads +- `search.py`: Search results + +Buffer lifecycle: +1. Created by mainController when session initializes +2. Added to view (wx.Treebook) +3. Periodically updated via `start_stream()` or real-time via pub/sub events +4. Destroyed when session ends or buffer removed + +#### 3. Controller Layer (`src/controller/`) +Controllers orchestrate application logic and coordinate between sessions, buffers, and UI. + +**Main Controller** (`controller/mainController.py`): +- Manages all active buffers and sessions +- Binds keyboard shortcuts to actions +- Handles pub/sub event subscriptions +- Periodically calls `start_stream()` on visible buffers +- Provides buffer search methods: `search_buffer()`, `get_current_buffer()`, `get_best_buffer()` + +**Specialized Controllers**: +- `settings.py`: Settings dialog management +- `userAlias.py` / `userList.py`: User management features +- `mastodon/handler.py`: Mastodon-specific operations (filters, etc.) + +#### 4. GUI Layer (`src/wxUI/`) +wxPython-based interface with menu-driven navigation and list controls. + +- **Main Frame** (`wxUI/view.py`): Primary window with wx.Treebook for buffers, menu system, system tray integration +- **Buffer Panels** (`wxUI/buffers/`): Panel implementations for each buffer type +- **Dialogs** (`wxUI/dialogs/`): Post composition, settings, user profiles, filters + +#### 5. Pub/Sub Event System +Decoupled communication using PyPubSub 4.0.3. + +Key events: +- `mastodon.status_received`: New post received via streaming +- `mastodon.status_updated`: Post edited +- `mastodon.notification_received`: New notification +- `mastodon.conversation_received`: New DM + +Event flow: +1. Streaming listener receives API event +2. Publishes to topic via `pub.sendMessage()` +3. mainController subscribes to topics and routes to appropriate buffer +4. Buffer updates its display + +#### 6. Session Manager (`src/sessionmanager/`) +Manages session lifecycle (creation, configuration, activation, deletion). + +- `sessionManager.py`: UI for managing multiple accounts +- `manager.py`: Persists session list to global config +- Handles OAuth2 authorization flow for new accounts +- Loads saved sessions on startup + +#### 7. Configuration System (`src/config.py`, `src/config_utils.py`) +Hierarchical configuration with defaults and user overrides. + +- Global config: `config/app-configuration.conf` (defaults in `src/app-configuration.defaults`) +- Session configs: `config/{session_id}/session.conf` (defaults in `src/mastodon.defaults`) +- Keymaps in `src/keymaps/` +- Sound packs in `src/sounds/` + +**Path Management** (`src/paths.py`): +- Portable mode: Config/logs in application directory +- Installed mode: Config/logs in AppData +- Detects installation by presence of `Uninstall.exe` + +#### 8. Accessibility Features +Built for screen reader users from the ground up. + +- `accessible_output2`: Multi-screen reader support (NVDA, JAWS, SAPI, etc.) +- `sound_lib`: Accessible audio playback with spatial audio +- `platform_utils`: OS-specific accessibility hooks +- `output.py`: Unified interface for speech output +- `sound.py`: Sound system with volume control and sound pack management + +#### 9. Keyboard Handling (`src/keyboard_handler/`) +Cross-platform keyboard input with global hotkey support. + +- `wx_handler.py`: wxPython integration +- `global_handler.py`: System-wide hotkeys +- Platform implementations: `windows.py`, `osx.py`, `linux.py` +- `keystrokeEditor/`: UI for customizing shortcuts + +### Application Initialization Flow + +From `src/main.py`: +1. Setup logging to temp directory, then move to permanent location +2. Initialize language handler +3. Load global configuration +4. Setup sound system +5. Setup accessibility output +6. Initialize session manager +7. Load saved sessions or prompt for account creation +8. Create main controller +9. Start main event loop + +### Data Flow Patterns + +#### Real-time Update Flow +``` +Mastodon Streaming API + → sessions/mastodon/streaming.py (StreamListener) + → pub.sendMessage("mastodon.status_received", ...) + → controller/mainController.py (subscriber) + → buffer.add_new_item() + → compose_function(item) + → wxUI update +``` + +#### User Action Flow +``` +Keyboard input + → wx event handler + → buffer.get_event() + → buffer action method (e.g., open_status()) + → session.api_call() + → UI update or pub/sub event +``` + +#### Periodic Update Flow +``` +RepeatingTimer (every N seconds) + → mainController calls buffer.start_stream() + → session.get_timeline_data() + → buffer.put_items_on_list() + → compose_function for each item + → wxUI list control update +``` + +## Key Design Patterns and Conventions + +### Compose Functions +Buffers use compose functions to render API objects as user-readable strings. Located in `sessions/mastodon/compose.py`: + +```python +compose_function(item, db, relative_times, show_screen_names=False, session=None) +# Returns a string representation of the item for display +``` + +### Session Decorators +Sessions use decorators to enforce prerequisites: + +```python +@baseSession._require_login +def post_status(self, text): + # Only executes if self.logged == True + pass + +@baseSession._require_configuration +def get_timeline(self): + # Only executes if self.settings != None + pass +``` + +### Buffer Naming Convention +Buffers have both a `name` (internal identifier) and `account` (associated username): +- `name`: e.g., "home_timeline", "mentions", "notifications" +- `account`: e.g., "user@mastodon.social" +- Buffers are uniquely identified by (name, account) tuple + +### Configuration Hierarchy +1. Default values in `src/*.defaults` files +2. User overrides in `config/*.conf` files +3. Runtime modifications via settings dialogs +4. Written back to user config files on change + +## Important Caveats + +### Platform-Specific Code +- VLC paths must be set via environment variables when running from source (see `main.py`) +- Windows-specific: pywin32, win-inet-pton, winpaths dependencies +- Accessibility output works best on Windows with NVDA/JAWS + +### Threading and Event Handling +- API calls often wrapped in `call_threaded()` to avoid blocking UI +- Streaming runs in background thread and publishes to main thread via pub/sub +- wx events must be handled on main thread + +### Session Lifecycle +- Sessions must be logged in before buffer creation +- Buffers maintain references to sessions via `self.session` +- Destroying a session should destroy all associated buffers +- Session settings auto-save on write via `settings.write()` + +### Buffer Visibility +- Buffers have `invisible` flag for internal/system buffers +- Main controller distinguishes between visible buffers (shown in tree) and invisible buffers (used for data access) +- Empty buffers serve as account placeholders in tree structure + +### Logging and Debugging +- Logs written to temp directory on startup, then moved to permanent location +- Binary builds redirect stdout/stderr to `logs/` directory +- Source builds use console output +- Use `logging.getLogger("module.name")` pattern throughout + +## Build System Details + +### cx_Freeze Configuration (`src/setup.py`) +- Target: Win32GUI (suppresses console window) +- Includes: keymaps, locales, sounds, documentation, icon, config defaults +- Architecture-specific: Loads x86 or x64 dependencies from windows-dependencies submodule +- Special handling for enchant dictionaries, VLC plugins, VC++ redistributables + +### NSIS Installer (`scripts/twblue.nsi`) +- Expects binary distribution in `scripts/twblue64/` +- Creates Start Menu shortcuts, Desktop shortcut (optional) +- Registers uninstaller +- Checks for running instances before install/uninstall + +### CI/CD (`.github/workflows/release.yml`) +- Triggers on version tags (v20*) +- Builds on Windows-latest with Python 3.10 +- Creates both installer (EXE) and portable (ZIP) distributions +- Uploads to GitHub releases + +## Mastodon API Integration + +### Authentication +OAuth2 flow implemented in `sessions/mastodon/session.py`: +1. Create application credentials for instance +2. Request OAuth authorization URL +3. User authorizes in browser +4. Exchange code for access token +5. Store credentials in session config + +### API Client +Uses Mastodon.py 2.1.4 library: +- Instance created with base URL and access token +- Methods: `status_post()`, `timeline()`, `account()`, etc. +- Rate limiting handled by library +- Supports multiple instances simultaneously + +### Streaming API +Real-time updates via `sessions/mastodon/streaming.py`: +- Inherits from `Mastodon.StreamListener` +- Connects to user, public, or hashtag streams +- Runs in background thread +- Events published to main thread via pub/sub + +## Localization + +TWBlue supports 23 languages: +- Translation files in `src/locales/{lang}/LC_MESSAGES/twblue.mo` +- Uses gettext with `_()` function throughout codebase +- Language selection in settings, stored in global config +- Babel for extraction and compilation +- Weblate for translation management From 977de1332a5d031c91cd39910363fdb99064827a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Nov 2025 14:37:12 +0000 Subject: [PATCH 04/37] Implement post editing functionality for Mastodon Add ability to edit posts in Mastodon with full support for: - Editing post text and content warnings - Re-uploading or keeping existing media attachments - Editing poll options (for posts with polls) - Modifying visibility and language settings - All features available through web interface Changes: - Add edit_post() method in Mastodon session to handle API calls - Create editPost dialog class that loads existing post data - Add edit_status() method to buffer controllers - Add Edit menu item to base and notification menus - Register edit_post action in all keymaps (no default key assigned) - Add edit_post() action handler in main controller The edit option is only enabled for the user's own posts (not boosts). Users can access the feature through the context menu or by assigning a keyboard shortcut in the keymap editor. --- src/controller/buffers/mastodon/base.py | 25 +++++++++++ .../buffers/mastodon/notifications.py | 7 ++++ src/controller/mainController.py | 9 ++++ src/controller/mastodon/messages.py | 41 +++++++++++++++++++ src/keymaps/Chicken Nugget.keymap | 1 + src/keymaps/Qwitter.keymap | 1 + src/keymaps/Windows 10.keymap | 1 + src/keymaps/Windows11.keymap | 1 + src/keymaps/default.keymap | 1 + src/sessions/mastodon/session.py | 30 ++++++++++++++ src/wxUI/dialogs/mastodon/menus.py | 4 ++ 11 files changed, 121 insertions(+) diff --git a/src/controller/buffers/mastodon/base.py b/src/controller/buffers/mastodon/base.py index 0783d9cb..fb08902d 100644 --- a/src/controller/buffers/mastodon/base.py +++ b/src/controller/buffers/mastodon/base.py @@ -280,6 +280,12 @@ class BaseBuffer(base.Buffer): return menu = menus.base() widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply) + # Enable/disable edit based on whether the post belongs to the user + item = self.get_item() + if item and item.account.id == self.session.db["user_id"] and item.reblog == None: + widgetUtils.connect_event(menu, widgetUtils.MENU, self.edit_status, menuitem=menu.edit) + else: + menu.edit.Enable(False) widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions) if self.can_share() == True: widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost) @@ -501,6 +507,25 @@ class BaseBuffer(base.Buffer): log.exception("") self.session.db[self.name] = items + def edit_status(self, event=None, item=None, *args, **kwargs): + if item == None: + item = self.get_item() + # Check if the post belongs to the current user + if item.account.id != self.session.db["user_id"] or item.reblog != None: + output.speak(_("You can only edit your own posts.")) + return + # Create edit dialog with existing post data + title = _("Edit post") + caption = _("Edit your post here") + post = messages.editPost(session=self.session, item=item, title=title, caption=caption) + response = post.message.ShowModal() + if response == wx.ID_OK: + post_data = post.get_data() + # Call edit_post method in session + call_threaded(self.session.edit_post, post_id=post.post_id, posts=post_data, visibility=post.get_visibility(), language=post.get_language()) + if hasattr(post.message, "destroy"): + post.message.destroy() + def user_details(self): item = self.get_item() pass diff --git a/src/controller/buffers/mastodon/notifications.py b/src/controller/buffers/mastodon/notifications.py index 5c142950..cce39f4d 100644 --- a/src/controller/buffers/mastodon/notifications.py +++ b/src/controller/buffers/mastodon/notifications.py @@ -161,6 +161,13 @@ class NotificationsBuffer(BaseBuffer): menu = menus.notification(notification.type) if self.is_post(): widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply) + # Enable/disable edit based on whether the post belongs to the user + if hasattr(menu, 'edit'): + status = self.get_post() + if status and status.account.id == self.session.db["user_id"] and status.reblog == None: + widgetUtils.connect_event(menu, widgetUtils.MENU, self.edit_status, menuitem=menu.edit) + else: + menu.edit.Enable(False) widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions) if self.can_share() == True: widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost) diff --git a/src/controller/mainController.py b/src/controller/mainController.py index a4264cf6..fef23b5c 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -449,6 +449,15 @@ class Controller(object): buffer = self.search_buffer(buffer.name, buffer.account) buffer.destroy_status() + def edit_post(self, *args, **kwargs): + """ Edits a post in the current buffer. + Users can only edit their own posts.""" + buffer = self.view.get_current_buffer() + if hasattr(buffer, "account"): + buffer = self.search_buffer(buffer.name, buffer.account) + if hasattr(buffer, "edit_status"): + buffer.edit_status() + def exit(self, *args, **kwargs): if config.app["app-settings"]["ask_at_exit"] == True: answer = commonMessageDialogs.exit_dialog(self.view) diff --git a/src/controller/mastodon/messages.py b/src/controller/mastodon/messages.py index 9e61ab70..7502d857 100644 --- a/src/controller/mastodon/messages.py +++ b/src/controller/mastodon/messages.py @@ -262,6 +262,47 @@ class post(messages.basicMessage): visibility_setting = visibility_settings.index(setting) self.message.visibility.SetSelection(setting) +class editPost(post): + def __init__(self, session, item, title, caption, *args, **kwargs): + """ Initialize edit dialog with existing post data. """ + # Extract text from post + if item.reblog != None: + item = item.reblog + text = item.content + # Remove HTML tags from content + import re + text = re.sub('<[^<]+?>', '', text) + # Initialize parent class + super(editPost, self).__init__(session, title, caption, text=text, *args, **kwargs) + # Store the post ID for editing + self.post_id = item.id + # Set visibility + visibility_settings = dict(public=0, unlisted=1, private=2, direct=3) + self.message.visibility.SetSelection(visibility_settings.get(item.visibility, 0)) + # Set language + if item.language: + self.set_language(item.language) + # Set sensitive content and spoiler + if item.sensitive: + self.message.sensitive.SetValue(True) + if item.spoiler_text: + self.message.spoiler.ChangeValue(item.spoiler_text) + self.message.on_sensitivity_changed() + # Load existing media attachments + if hasattr(item, 'media_attachments') and len(item.media_attachments) > 0: + for media in item.media_attachments: + media_info = { + "id": media.id, # Keep the existing media ID + "type": media.type, + "file": media.url, # URL of existing media + "description": media.description or "" + } + self.attachments.append(media_info) + # Display in the attachment list + self.message.add_item(item=[media.url.split('/')[-1], media.type, media.description or ""]) + # Update text processor to reflect the loaded content + self.text_processor() + class viewPost(post): def __init__(self, session, post, offset_hours=0, date="", item_url=""): self.session = session diff --git a/src/keymaps/Chicken Nugget.keymap b/src/keymaps/Chicken Nugget.keymap index aad998a9..9d95a47f 100644 --- a/src/keymaps/Chicken Nugget.keymap +++ b/src/keymaps/Chicken Nugget.keymap @@ -23,6 +23,7 @@ url = string(default="control+win+b") go_home = string(default="control+win+home") go_end = string(default="control+win+end") delete = string(default="control+win+delete") +edit_post = string(default="") clear_buffer = string(default="control+win+shift+delete") repeat_item = string(default="control+win+space") copy_to_clipboard = string(default="control+win+shift+c") diff --git a/src/keymaps/Qwitter.keymap b/src/keymaps/Qwitter.keymap index 3a73baea..7a8bc30a 100644 --- a/src/keymaps/Qwitter.keymap +++ b/src/keymaps/Qwitter.keymap @@ -33,6 +33,7 @@ go_page_up = string(default="control+win+pageup") go_page_down = string(default="control+win+pagedown") update_profile = string(default="control+win+shift+p") delete = string(default="control+win+delete") +edit_post = string(default="") clear_buffer = string(default="control+win+shift+delete") repeat_item = string(default="control+win+space") copy_to_clipboard = string(default="control+win+shift+c") diff --git a/src/keymaps/Windows 10.keymap b/src/keymaps/Windows 10.keymap index 3e21c51b..426c598f 100644 --- a/src/keymaps/Windows 10.keymap +++ b/src/keymaps/Windows 10.keymap @@ -33,6 +33,7 @@ go_page_up = string(default="control+win+pageup") go_page_down = string(default="control+win+pagedown") update_profile = string(default="alt+win+p") delete = string(default="alt+win+delete") +edit_post = string(default="") clear_buffer = string(default="alt+win+shift+delete") repeat_item = string(default="alt+win+space") copy_to_clipboard = string(default="alt+win+shift+c") diff --git a/src/keymaps/Windows11.keymap b/src/keymaps/Windows11.keymap index 518118f8..9ed24488 100644 --- a/src/keymaps/Windows11.keymap +++ b/src/keymaps/Windows11.keymap @@ -33,6 +33,7 @@ go_page_up = string(default="control+win+pageup") go_page_down = string(default="control+win+pagedown") update_profile = string(default="alt+win+p") delete = string(default="alt+win+delete") +edit_post = string(default="") clear_buffer = string(default="alt+win+shift+delete") repeat_item = string(default="control+alt+win+space") copy_to_clipboard = string(default="alt+win+shift+c") diff --git a/src/keymaps/default.keymap b/src/keymaps/default.keymap index 19d9ebc0..8e4bf9b2 100644 --- a/src/keymaps/default.keymap +++ b/src/keymaps/default.keymap @@ -34,6 +34,7 @@ go_page_up = string(default="control+win+pageup") go_page_down = string(default="control+win+pagedown") update_profile = string(default="alt+win+p") delete = string(default="control+win+delete") +edit_post = string(default="") clear_buffer = string(default="control+win+shift+delete") repeat_item = string(default="control+win+space") copy_to_clipboard = string(default="control+win+shift+c") diff --git a/src/sessions/mastodon/session.py b/src/sessions/mastodon/session.py index 57e153ed..de4baad9 100644 --- a/src/sessions/mastodon/session.py +++ b/src/sessions/mastodon/session.py @@ -248,6 +248,36 @@ class Session(base.baseSession): pub.sendMessage("mastodon.error_post", name=self.get_name(), reply_to=reply_to, visibility=visibility, posts=posts, lang=language) return + def edit_post(self, post_id, visibility=None, language=None, posts=[]): + """ Convenience function to edit a post. Only the first item in posts list is used as threads cannot be edited. """ + if len(posts) == 0: + return + obj = posts[0] + text = obj.get("text") + media_ids = [] + try: + poll = None + # Handle poll attachments + if len(obj["attachments"]) == 1 and obj["attachments"][0]["type"] == "poll": + poll = self.api.make_poll(options=obj["attachments"][0]["options"], expires_in=obj["attachments"][0]["expires_in"], multiple=obj["attachments"][0]["multiple"], hide_totals=obj["attachments"][0]["hide_totals"]) + # Handle media attachments + elif len(obj["attachments"]) > 0: + for i in obj["attachments"]: + # If attachment has an 'id', it's an existing media that we keep + if "id" in i: + media_ids.append(i["id"]) + # Otherwise it's a new file to upload + elif "file" in i: + media = self.api_call("media_post", media_file=i["file"], description=i["description"], synchronous=True) + media_ids.append(media.id) + # Call status_update API + item = self.api_call(call_name="status_update", id=post_id, status=text, _sound="tweet_send.ogg", media_ids=media_ids if len(media_ids) > 0 else None, visibility=visibility, poll=poll, sensitive=obj["sensitive"], spoiler_text=obj["spoiler_text"], language=language) + return item + except Exception as e: + log.exception("Error updating post: {}".format(str(e))) + output.speak(_("Error editing post: {}").format(str(e))) + return None + def get_name(self): instance = self.settings["mastodon"]["instance"] instance = instance.replace("https://", "") diff --git a/src/wxUI/dialogs/mastodon/menus.py b/src/wxUI/dialogs/mastodon/menus.py index 63a9cb56..7947d942 100644 --- a/src/wxUI/dialogs/mastodon/menus.py +++ b/src/wxUI/dialogs/mastodon/menus.py @@ -8,6 +8,8 @@ class base(wx.Menu): self.Append(self.boost) self.reply = wx.MenuItem(self, wx.ID_ANY, _(u"Re&ply")) self.Append(self.reply) + self.edit = wx.MenuItem(self, wx.ID_ANY, _(u"&Edit")) + self.Append(self.edit) self.fav = wx.MenuItem(self, wx.ID_ANY, _(u"&Add to favorites")) self.Append(self.fav) self.unfav = wx.MenuItem(self, wx.ID_ANY, _(u"R&emove from favorites")) @@ -36,6 +38,8 @@ class notification(wx.Menu): self.Append(self.boost) self.reply = wx.MenuItem(self, wx.ID_ANY, _(u"Re&ply")) self.Append(self.reply) + self.edit = wx.MenuItem(self, wx.ID_ANY, _(u"&Edit")) + self.Append(self.edit) self.fav = wx.MenuItem(self, wx.ID_ANY, _(u"&Add to favorites")) self.Append(self.fav) self.unfav = wx.MenuItem(self, wx.ID_ANY, _(u"R&emove from favorites")) From de837e15b9cb980d24fd2bd5e37663c514069343 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 6 Nov 2025 17:37:05 -0600 Subject: [PATCH 05/37] fix: Remove parameters that are not editable from post edit function --- src/sessions/mastodon/session.py | 84 +++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/sessions/mastodon/session.py b/src/sessions/mastodon/session.py index de4baad9..47cc47aa 100644 --- a/src/sessions/mastodon/session.py +++ b/src/sessions/mastodon/session.py @@ -248,33 +248,103 @@ class Session(base.baseSession): pub.sendMessage("mastodon.error_post", name=self.get_name(), reply_to=reply_to, visibility=visibility, posts=posts, lang=language) return - def edit_post(self, post_id, visibility=None, language=None, posts=[]): - """ Convenience function to edit a post. Only the first item in posts list is used as threads cannot be edited. """ + def edit_post(self, post_id, posts=[]): + """ Convenience function to edit a post. Only the first item in posts list is used as threads cannot be edited. + + Note: According to Mastodon API, not all fields can be edited. Visibility, language, and reply context cannot be changed. + + Args: + post_id: ID of the status to edit + posts: List with post data. Only first item is used. + + Returns: + Updated status object or None on failure + """ if len(posts) == 0: - return + log.warning("edit_post called with empty posts list") + return None + obj = posts[0] text = obj.get("text") + + if not text: + log.warning("edit_post called without text content") + return None + media_ids = [] + media_attributes = [] + try: poll = None # Handle poll attachments if len(obj["attachments"]) == 1 and obj["attachments"][0]["type"] == "poll": - poll = self.api.make_poll(options=obj["attachments"][0]["options"], expires_in=obj["attachments"][0]["expires_in"], multiple=obj["attachments"][0]["multiple"], hide_totals=obj["attachments"][0]["hide_totals"]) + poll = self.api.make_poll( + options=obj["attachments"][0]["options"], + expires_in=obj["attachments"][0]["expires_in"], + multiple=obj["attachments"][0]["multiple"], + hide_totals=obj["attachments"][0]["hide_totals"] + ) + log.debug("Editing post with poll (this will reset votes)") # Handle media attachments elif len(obj["attachments"]) > 0: for i in obj["attachments"]: # If attachment has an 'id', it's an existing media that we keep if "id" in i: media_ids.append(i["id"]) + # If existing media has metadata to update, use generate_media_edit_attributes + if "description" in i or "focus" in i: + media_attr = self.api.generate_media_edit_attributes( + id=i["id"], + description=i.get("description"), + focus=i.get("focus") + ) + media_attributes.append(media_attr) # Otherwise it's a new file to upload elif "file" in i: - media = self.api_call("media_post", media_file=i["file"], description=i["description"], synchronous=True) + description = i.get("description", "") + focus = i.get("focus", None) + media = self.api_call( + "media_post", + media_file=i["file"], + description=description, + focus=focus, + synchronous=True + ) media_ids.append(media.id) + log.debug("Uploaded new media with id: {}".format(media.id)) + + # Prepare parameters for status_update + update_params = { + "id": post_id, + "status": text, + "_sound": "tweet_send.ogg", + "sensitive": obj.get("sensitive", False), + "spoiler_text": obj.get("spoiler_text", None), + } + + # Add optional parameters only if provided + if media_ids: + update_params["media_ids"] = media_ids + if media_attributes: + update_params["media_attributes"] = media_attributes + if poll: + update_params["poll"] = poll + # Call status_update API - item = self.api_call(call_name="status_update", id=post_id, status=text, _sound="tweet_send.ogg", media_ids=media_ids if len(media_ids) > 0 else None, visibility=visibility, poll=poll, sensitive=obj["sensitive"], spoiler_text=obj["spoiler_text"], language=language) + log.debug("Editing post {} with params: {}".format(post_id, {k: v for k, v in update_params.items() if k not in ["_sound"]})) + item = self.api_call(call_name="status_update", **update_params) + + if item: + log.info("Successfully edited post {}".format(post_id)) return item + + except MastodonAPIError as e: + log.exception("Mastodon API error updating post {}: {}".format(post_id, str(e))) + output.speak(_("Error editing post: {}").format(str(e))) + pub.sendMessage("mastodon.error_edit", name=self.get_name(), post_id=post_id, error=str(e)) + return None except Exception as e: - log.exception("Error updating post: {}".format(str(e))) + log.exception("Unexpected error updating post {}: {}".format(post_id, str(e))) output.speak(_("Error editing post: {}").format(str(e))) return None From 3af372973dbc2e1cecb7398d016b2bcceed219dd Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Thu, 6 Nov 2025 17:39:55 -0600 Subject: [PATCH 06/37] fix: avoid passing params that are not editable to API Calls; display polls as attachments in post edit dialog, warns user about vote resetting when editing a post with poll --- src/controller/buffers/mastodon/base.py | 26 ++++++- src/controller/mastodon/messages.py | 94 +++++++++++++++++++++---- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/src/controller/buffers/mastodon/base.py b/src/controller/buffers/mastodon/base.py index fb08902d..2deff612 100644 --- a/src/controller/buffers/mastodon/base.py +++ b/src/controller/buffers/mastodon/base.py @@ -514,6 +514,29 @@ class BaseBuffer(base.Buffer): if item.account.id != self.session.db["user_id"] or item.reblog != None: output.speak(_("You can only edit your own posts.")) return + # Check if post has a poll with votes - warn user before proceeding + if hasattr(item, 'poll') and item.poll is not None: + votes_count = item.poll.votes_count if hasattr(item.poll, 'votes_count') else 0 + if votes_count > 0: + # Show confirmation dialog + warning_title = _("Warning: Poll with votes") + warning_message = _("This post contains a poll with {votes} votes.\n\n" + "According to Mastodon's API, editing this post will reset ALL votes to zero, " + "even if you don't modify the poll itself.\n\n" + "Do you want to continue editing?").format(votes=votes_count) + dialog = wx.MessageDialog(self.buffer, warning_message, warning_title, + wx.YES_NO | wx.NO_DEFAULT | wx.ICON_WARNING) + result = dialog.ShowModal() + dialog.Destroy() + if result != wx.ID_YES: + output.speak(_("Edit cancelled")) + return + # Log item info for debugging + log.debug("Editing status: id={}, has_media_attachments={}, media_count={}".format( + item.id, + hasattr(item, 'media_attachments'), + len(item.media_attachments) if hasattr(item, 'media_attachments') else 0 + )) # Create edit dialog with existing post data title = _("Edit post") caption = _("Edit your post here") @@ -522,7 +545,8 @@ class BaseBuffer(base.Buffer): if response == wx.ID_OK: post_data = post.get_data() # Call edit_post method in session - call_threaded(self.session.edit_post, post_id=post.post_id, posts=post_data, visibility=post.get_visibility(), language=post.get_language()) + # Note: visibility and language cannot be changed when editing per Mastodon API + call_threaded(self.session.edit_post, post_id=post.post_id, posts=post_data) if hasattr(post.message, "destroy"): post.message.destroy() diff --git a/src/controller/mastodon/messages.py b/src/controller/mastodon/messages.py index 7502d857..2500759a 100644 --- a/src/controller/mastodon/messages.py +++ b/src/controller/mastodon/messages.py @@ -2,6 +2,7 @@ import os import re import wx +import logging import widgetUtils import config import output @@ -14,6 +15,8 @@ from wxUI.dialogs.mastodon import postDialogs from extra.autocompletionUsers import completion from . import userList +log = logging.getLogger("controller.mastodon.messages") + def character_count(post_text, post_cw, character_limit=500): # We will use text for counting character limit only. full_text = post_text+post_cw @@ -264,7 +267,11 @@ class post(messages.basicMessage): class editPost(post): def __init__(self, session, item, title, caption, *args, **kwargs): - """ Initialize edit dialog with existing post data. """ + """ Initialize edit dialog with existing post data. + + Note: Per Mastodon API, visibility and language cannot be changed when editing. + These fields will be displayed but disabled in the UI. + """ # Extract text from post if item.reblog != None: item = item.reblog @@ -276,30 +283,87 @@ class editPost(post): super(editPost, self).__init__(session, title, caption, text=text, *args, **kwargs) # Store the post ID for editing self.post_id = item.id - # Set visibility + # Set visibility (read-only, cannot be changed) visibility_settings = dict(public=0, unlisted=1, private=2, direct=3) self.message.visibility.SetSelection(visibility_settings.get(item.visibility, 0)) - # Set language + self.message.visibility.Enable(False) # Disable as it cannot be edited + # Set language (read-only, cannot be changed) if item.language: self.set_language(item.language) + self.message.language.Enable(False) # Disable as it cannot be edited # Set sensitive content and spoiler if item.sensitive: self.message.sensitive.SetValue(True) if item.spoiler_text: self.message.spoiler.ChangeValue(item.spoiler_text) self.message.on_sensitivity_changed() - # Load existing media attachments - if hasattr(item, 'media_attachments') and len(item.media_attachments) > 0: - for media in item.media_attachments: - media_info = { - "id": media.id, # Keep the existing media ID - "type": media.type, - "file": media.url, # URL of existing media - "description": media.description or "" - } - self.attachments.append(media_info) - # Display in the attachment list - self.message.add_item(item=[media.url.split('/')[-1], media.type, media.description or ""]) + # Load existing poll (if any) + # Note: You cannot have both media and a poll, so check poll first + if hasattr(item, 'poll') and item.poll is not None: + log.debug("Loading existing poll for post {}".format(self.post_id)) + poll = item.poll + # Extract poll options (just the text, not the votes) + poll_options = [option.title for option in poll.options] + # Calculate expires_in based on current time and expires_at + # For editing, we need to provide a new expiration time + # Since we can't get the original expires_in, use a default or let user configure + # For now, use 1 day (86400 seconds) as default + expires_in = 86400 + if hasattr(poll, 'expires_at') and poll.expires_at and not poll.expired: + # Calculate remaining time if poll hasn't expired + from dateutil import parser as date_parser + import datetime + try: + expires_at = poll.expires_at + if isinstance(expires_at, str): + expires_at = date_parser.parse(expires_at) + now = datetime.datetime.now(datetime.timezone.utc) + remaining = (expires_at - now).total_seconds() + if remaining > 0: + expires_in = int(remaining) + except Exception as e: + log.warning("Could not calculate poll expiration: {}".format(e)) + + poll_info = { + "type": "poll", + "file": "", + "description": _("Poll with {} options").format(len(poll_options)), + "options": poll_options, + "expires_in": expires_in, + "multiple": poll.multiple if hasattr(poll, 'multiple') else False, + "hide_totals": poll.voters_count == 0 if hasattr(poll, 'voters_count') else False + } + self.attachments.append(poll_info) + self.message.add_item(item=[poll_info["file"], poll_info["type"], poll_info["description"]]) + log.debug("Loaded poll with {} options. WARNING: Editing will reset all votes!".format(len(poll_options))) + # Load existing media attachments (only if no poll) + elif hasattr(item, 'media_attachments'): + log.debug("Loading existing media attachments for post {}".format(self.post_id)) + log.debug("Item has media_attachments attribute, count: {}".format(len(item.media_attachments))) + if len(item.media_attachments) > 0: + for media in item.media_attachments: + log.debug("Processing media: id={}, type={}, url={}".format(media.id, media.type, media.url)) + media_info = { + "id": media.id, # Keep the existing media ID + "type": media.type, + "file": media.url, # URL of existing media + "description": media.description or "" + } + # Include focus point if available + if hasattr(media, 'meta') and media.meta and 'focus' in media.meta: + focus = media.meta['focus'] + media_info["focus"] = (focus.get('x'), focus.get('y')) + log.debug("Added focus point: {}".format(media_info["focus"])) + self.attachments.append(media_info) + # Display in the attachment list + display_name = media.url.split('/')[-1] + log.debug("Adding item to UI: name={}, type={}, desc={}".format(display_name, media.type, media.description or "")) + self.message.add_item(item=[display_name, media.type, media.description or ""]) + log.debug("Total attachments loaded: {}".format(len(self.attachments))) + else: + log.debug("media_attachments list is empty") + else: + log.debug("Item has no poll or media attachments") # Update text processor to reflect the loaded content self.text_processor() From 377578dbe2149ab1f25d006d6c1a20d7869d0139 Mon Sep 17 00:00:00 2001 From: Manuel cortez Date: Fri, 7 Nov 2025 09:01:11 -0600 Subject: [PATCH 07/37] Added a better HTML filter to remove elements with certain classes --- src/sessions/mastodon/utils.py | 42 ++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/sessions/mastodon/utils.py b/src/sessions/mastodon/utils.py index 12a8c8fa..05a6303f 100644 --- a/src/sessions/mastodon/utils.py +++ b/src/sessions/mastodon/utils.py @@ -3,23 +3,47 @@ import demoji from html.parser import HTMLParser from datetime import datetime, timezone -url_re = re.compile('') +url_re = re.compile(r'') class HTMLFilter(HTMLParser): + # Classes to ignore when parsing HTML + IGNORED_CLASSES = ["quote-inline"] + text = "" first_paragraph = True + skip_depth = 0 # Track nesting depth of ignored elements def handle_data(self, data): - self.text += data + # Only add data if we're not inside an ignored element + if self.skip_depth == 0: + self.text += data def handle_starttag(self, tag, attrs): - if tag == "br": - self.text = self.text+"\n" - elif tag == "p": - if self.first_paragraph: - self.first_paragraph = False - else: - self.text = self.text+"\n\n" + # Check if this tag has a class that should be ignored + attrs_dict = dict(attrs) + tag_class = attrs_dict.get("class", "") + + # Check if any ignored class is present in this tag + should_skip = any(ignored_class in tag_class for ignored_class in self.IGNORED_CLASSES) + + if should_skip: + self.skip_depth += 1 + elif self.skip_depth == 0: # Only process tags if we're not skipping + if tag == "br": + self.text = self.text+"\n" + elif tag == "p": + if self.first_paragraph: + self.first_paragraph = False + else: + self.text = self.text+"\n\n" + else: + # We're inside a skipped element, increment depth for nested tags + self.skip_depth += 1 + + def handle_endtag(self, tag): + # Decrement skip depth when closing any tag while skipping + if self.skip_depth > 0: + self.skip_depth -= 1 def html_filter(data): f = HTMLFilter() From a13e1f1f10cbbca7a832f0db9c38395b469e46f8 Mon Sep 17 00:00:00 2001 From: Manuel cortez Date: Fri, 7 Nov 2025 09:08:39 -0600 Subject: [PATCH 08/37] Added support for reading quoted posts properly --- src/sessions/mastodon/compose.py | 5 +++++ src/sessions/mastodon/templates.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/sessions/mastodon/compose.py b/src/sessions/mastodon/compose.py index d95cc6c4..b9d9534f 100644 --- a/src/sessions/mastodon/compose.py +++ b/src/sessions/mastodon/compose.py @@ -17,6 +17,11 @@ def compose_post(post, db, settings, relative_times, show_screen_names, safe=Tru text = _("Boosted from @{}: {}").format(post.reblog.account.acct, templates.process_text(post.reblog, safe=safe)) else: text = templates.process_text(post, safe=safe) + # Handle quoted posts + if hasattr(post, 'quote') and post.quote != None and hasattr(post.quote, 'quoted_status') and post.quote.quoted_status != None: + quoted_user = post.quote.quoted_status.account.acct + quoted_text = templates.process_text(post.quote.quoted_status, safe=safe) + text = text + " " + _("Quoting @{}: {}").format(quoted_user, quoted_text) filtered = utils.evaluate_filters(post=post, current_context="home") if filtered != None: text = _("hidden by filter {}").format(filtered) diff --git a/src/sessions/mastodon/templates.py b/src/sessions/mastodon/templates.py index 0c0083f6..2674bea3 100644 --- a/src/sessions/mastodon/templates.py +++ b/src/sessions/mastodon/templates.py @@ -76,6 +76,13 @@ def render_post(post, template, settings, relative_times=False, offset_hours=0): else: text = process_text(post, safe=False) safe_text = process_text(post) + # Handle quoted posts + if hasattr(post, 'quote') and post.quote != None and hasattr(post.quote, 'quoted_status') and post.quote.quoted_status != None: + quoted_user = post.quote.quoted_status.account.acct + quoted_text = process_text(post.quote.quoted_status, safe=False) + quoted_safe_text = process_text(post.quote.quoted_status, safe=True) + text = text + " " + _("Quoting @{}: {}").format(quoted_user, quoted_text) + safe_text = safe_text + " " + _("Quoting @{}: {}").format(quoted_user, quoted_safe_text) filtered = utils.evaluate_filters(post=post, current_context="home") if filtered != None: text = _("hidden by filter {}").format(filtered) From 3db6ee3a178b2c89068c4d68f530c868b73df96b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:08:33 +0000 Subject: [PATCH 09/37] Bump pytest from 8.4.2 to 9.0.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.2 to 9.0.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.2...9.0.0) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8dce1c95..0dcf5a26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ pyenchant==3.3.0 pypiwin32==223 Pypubsub==4.0.3 PySocks==1.7.1 -pytest==8.4.2 +pytest==9.0.0 python-dateutil==2.9.0.post0 python-magic-bin==0.4.14 python-vlc==3.0.21203 From 8fb30edf3126b480cb2472d0bde04c57a62e2492 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:08:36 +0000 Subject: [PATCH 10/37] Bump types-python-dateutil from 2.9.0.20251008 to 2.9.0.20251108 Bumps [types-python-dateutil](https://github.com/typeshed-internal/stub_uploader) from 2.9.0.20251008 to 2.9.0.20251108. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-version: 2.9.0.20251108 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8dce1c95..193d1d1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,7 +49,7 @@ sniffio==1.3.1 sound_lib @ git+https://github.com/accessibleapps/sound_lib@a439f0943fb95ee7b6ba24f51a686f47c4ad66b2 sqlitedict==2.1.0 twitter-text-parser==3.0.0 -types-python-dateutil==2.9.0.20251008 +types-python-dateutil==2.9.0.20251108 urllib3==2.5.0 win-inet-pton==1.1.0 winpaths==0.2 From c8879e79985155067217c6c16fb4eec4a1f59687 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:08:42 +0000 Subject: [PATCH 11/37] Bump deepl from 1.23.0 to 1.24.0 Bumps [deepl](https://github.com/DeepLcom/deepl-python) from 1.23.0 to 1.24.0. - [Release notes](https://github.com/DeepLcom/deepl-python/releases) - [Changelog](https://github.com/DeepLcom/deepl-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/DeepLcom/deepl-python/compare/v1.23.0...v1.24.0) --- updated-dependencies: - dependency-name: deepl dependency-version: 1.24.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8dce1c95..b07f7aa6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ cx-Freeze==8.4.1 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 -deepl==1.23.0 +deepl==1.24.0 future==1.0.0 idna==3.11 importlib-metadata==8.7.0 From b419f8fb23c53559d8710289d185bdf66c8eddf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:38:36 +0000 Subject: [PATCH 12/37] Bump numpy from 2.3.4 to 2.3.5 Bumps [numpy](https://github.com/numpy/numpy) from 2.3.4 to 2.3.5. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v2.3.4...v2.3.5) --- updated-dependencies: - dependency-name: numpy dependency-version: 2.3.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f253961e..22cdc466 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ libretranslatepy==2.1.4 lief==0.15.1 Markdown==3.10 Mastodon.py==2.1.4 -numpy==2.3.4 +numpy==2.3.5 oauthlib==3.3.1 packaging==25.0 pillow==12.0.0 From d879a9de5cf6edd19d106f04f15e3c7d75983f72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:38:38 +0000 Subject: [PATCH 13/37] Bump certifi from 2025.10.5 to 2025.11.12 Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.10.5 to 2025.11.12. - [Commits](https://github.com/certifi/python-certifi/compare/2025.10.05...2025.11.12) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.11.12 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f253961e..375dc4c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ arrow==1.4.0 attrs==25.4.0 backports.functools-lru-cache==2.0.0 blurhash==1.1.5 -certifi==2025.10.5 +certifi==2025.11.12 chardet==5.2.0 charset-normalizer==3.4.4 colorama==0.4.6 From b309d23c8ba686252244d4b469476fa54a3aa514 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:38:43 +0000 Subject: [PATCH 14/37] Bump coverage from 7.11.0 to 7.11.3 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.11.0 to 7.11.3. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.11.0...7.11.3) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f253961e..cbaba338 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ chardet==5.2.0 charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 -coverage==7.11.0 +coverage==7.11.3 cx-Freeze==8.4.1 cx-Logging==3.2.1 decorator==5.2.1 From beb621de976b2f913bc7d5fa8c3dba8e0ee257be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:07:43 +0000 Subject: [PATCH 15/37] Bump pytest from 9.0.0 to 9.0.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.0 to 9.0.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.0...9.0.1) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9588f6bc..50ecf407 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ pyenchant==3.3.0 pypiwin32==223 Pypubsub==4.0.3 PySocks==1.7.1 -pytest==9.0.0 +pytest==9.0.1 python-dateutil==2.9.0.post0 python-magic-bin==0.4.14 python-vlc==3.0.21203 From 89160497e42e59e01b2f296f36f1cec831a7b36c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:07:49 +0000 Subject: [PATCH 16/37] Bump types-python-dateutil from 2.9.0.20251108 to 2.9.0.20251115 Bumps [types-python-dateutil](https://github.com/typeshed-internal/stub_uploader) from 2.9.0.20251108 to 2.9.0.20251115. - [Commits](https://github.com/typeshed-internal/stub_uploader/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-version: 2.9.0.20251115 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9588f6bc..391f9b47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,7 +49,7 @@ sniffio==1.3.1 sound_lib @ git+https://github.com/accessibleapps/sound_lib@a439f0943fb95ee7b6ba24f51a686f47c4ad66b2 sqlitedict==2.1.0 twitter-text-parser==3.0.0 -types-python-dateutil==2.9.0.20251108 +types-python-dateutil==2.9.0.20251115 urllib3==2.5.0 win-inet-pton==1.1.0 winpaths==0.2 From fed6f6da600332caa6ee82ea01b220bde43a1a31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:07:54 +0000 Subject: [PATCH 17/37] Bump deepl from 1.24.0 to 1.25.0 Bumps [deepl](https://github.com/DeepLcom/deepl-python) from 1.24.0 to 1.25.0. - [Release notes](https://github.com/DeepLcom/deepl-python/releases) - [Changelog](https://github.com/DeepLcom/deepl-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/DeepLcom/deepl-python/compare/v1.24.0...v1.25.0) --- updated-dependencies: - dependency-name: deepl dependency-version: 1.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9588f6bc..93463e43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ cx-Freeze==8.4.1 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 -deepl==1.24.0 +deepl==1.25.0 future==1.0.0 idna==3.11 importlib-metadata==8.7.0 From 4e255b5f6ebd84e4fccd01d65c80615ec3334dc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:37:03 +0000 Subject: [PATCH 18/37] Bump coverage from 7.11.3 to 7.12.0 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.11.3 to 7.12.0. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.11.3...7.12.0) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67e47927..5f0fb7e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ chardet==5.2.0 charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 -coverage==7.11.3 +coverage==7.12.0 cx-Freeze==8.4.1 cx-Logging==3.2.1 decorator==5.2.1 From 08bb5b41c969cdcfd99b7880c876dd2cf5f94e9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:15:03 +0000 Subject: [PATCH 19/37] Bump cx-freeze from 8.4.1 to 8.5.0 Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 8.4.1 to 8.5.0. - [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases) - [Changelog](https://github.com/marcelotduarte/cx_Freeze/blob/main/CHANGELOG.md) - [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/8.4.1...8.5.0) --- updated-dependencies: - dependency-name: cx-freeze dependency-version: 8.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5f0fb7e4..4945ab1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 coverage==7.12.0 -cx-Freeze==8.4.1 +cx-Freeze==8.5.0 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 From 39e25532d7808778b909451951d9895c41954449 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:03:28 +0000 Subject: [PATCH 20/37] Bump pypubsub from 4.0.3 to 4.0.7 Bumps [pypubsub](https://github.com/schollii/pypubsub) from 4.0.3 to 4.0.7. - [Release notes](https://github.com/schollii/pypubsub/releases) - [Changelog](https://github.com/schollii/pypubsub/blob/master/docs/changelog.rst) - [Commits](https://github.com/schollii/pypubsub/compare/v4.0.3...v4.0.7) --- updated-dependencies: - dependency-name: pypubsub dependency-version: 4.0.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4945ab1a..f81eaa72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ pluggy==1.6.0 psutil==7.1.3 pyenchant==3.3.0 pypiwin32==223 -Pypubsub==4.0.3 +Pypubsub==4.0.7 PySocks==1.7.1 pytest==9.0.1 python-dateutil==2.9.0.post0 From e48ff3d642bc9313dbafe203296e6503df1404a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:03:38 +0000 Subject: [PATCH 21/37] Bump urllib3 from 2.5.0 to 2.6.0 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4945ab1a..0394cbff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ sound_lib @ git+https://github.com/accessibleapps/sound_lib@a439f0943fb95ee7b6ba sqlitedict==2.1.0 twitter-text-parser==3.0.0 types-python-dateutil==2.9.0.20251115 -urllib3==2.5.0 +urllib3==2.6.0 win-inet-pton==1.1.0 winpaths==0.2 wxPython==4.2.4 From 196a71fbd9f4f3e8df1fac5871cbb350f4e79c56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:03:43 +0000 Subject: [PATCH 22/37] Bump pytest from 9.0.1 to 9.0.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.1 to 9.0.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.1...9.0.2) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4945ab1a..fe16dc89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ pyenchant==3.3.0 pypiwin32==223 Pypubsub==4.0.3 PySocks==1.7.1 -pytest==9.0.1 +pytest==9.0.2 python-dateutil==2.9.0.post0 python-magic-bin==0.4.14 python-vlc==3.0.21203 From 1f679e731dc38c655cd6f6e431441e2872471d2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:07:32 +0000 Subject: [PATCH 23/37] Bump urllib3 from 2.6.0 to 2.6.1 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.0 to 2.6.1. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.0...2.6.1) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5ae89913..9f4960f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ sound_lib @ git+https://github.com/accessibleapps/sound_lib@a439f0943fb95ee7b6ba sqlitedict==2.1.0 twitter-text-parser==3.0.0 types-python-dateutil==2.9.0.20251115 -urllib3==2.6.0 +urllib3==2.6.1 win-inet-pton==1.1.0 winpaths==0.2 wxPython==4.2.4 From d1e20fa776b47cba8bf80115efafe4b4c461b9c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:07:36 +0000 Subject: [PATCH 24/37] Bump coverage from 7.12.0 to 7.13.0 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.12.0 to 7.13.0. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.12.0...7.13.0) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5ae89913..9cd7efb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ chardet==5.2.0 charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 -coverage==7.12.0 +coverage==7.13.0 cx-Freeze==8.5.0 cx-Logging==3.2.1 decorator==5.2.1 From 81b1db072cba01776e13a5000e890176a3813232 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:07:44 +0000 Subject: [PATCH 25/37] Bump deepl from 1.25.0 to 1.26.0 Bumps [deepl](https://github.com/DeepLcom/deepl-python) from 1.25.0 to 1.26.0. - [Release notes](https://github.com/DeepLcom/deepl-python/releases) - [Changelog](https://github.com/DeepLcom/deepl-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/DeepLcom/deepl-python/compare/v1.25.0...v1.26.0) --- updated-dependencies: - dependency-name: deepl dependency-version: 1.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5ae89913..8fbeceb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ cx-Freeze==8.5.0 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 -deepl==1.25.0 +deepl==1.26.0 future==1.0.0 idna==3.11 importlib-metadata==8.7.0 From 2d3590a2a0c21086f070a7c27806dd817c4bf2ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:08:35 +0000 Subject: [PATCH 26/37] Bump urllib3 from 2.6.1 to 2.6.2 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.1 to 2.6.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.1...2.6.2) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9010971..91ad0367 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ sound_lib @ git+https://github.com/accessibleapps/sound_lib@a439f0943fb95ee7b6ba sqlitedict==2.1.0 twitter-text-parser==3.0.0 types-python-dateutil==2.9.0.20251115 -urllib3==2.6.1 +urllib3==2.6.2 win-inet-pton==1.1.0 winpaths==0.2 wxPython==4.2.4 From 5d400f9211916e318462689a969d20f31bcc0f77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:08:39 +0000 Subject: [PATCH 27/37] Bump deepl from 1.26.0 to 1.27.0 Bumps [deepl](https://github.com/DeepLcom/deepl-python) from 1.26.0 to 1.27.0. - [Release notes](https://github.com/DeepLcom/deepl-python/releases) - [Changelog](https://github.com/DeepLcom/deepl-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/DeepLcom/deepl-python/compare/v1.26.0...v1.27.0) --- updated-dependencies: - dependency-name: deepl dependency-version: 1.27.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9010971..909cffa9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ cx-Freeze==8.5.0 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 -deepl==1.26.0 +deepl==1.27.0 future==1.0.0 idna==3.11 importlib-metadata==8.7.0 From 06732cf9cf2d899641100049896eaa6a23a9312f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:06:22 +0000 Subject: [PATCH 28/37] Bump numpy from 2.3.5 to 2.4.0 Bumps [numpy](https://github.com/numpy/numpy) from 2.3.5 to 2.4.0. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v2.3.5...v2.4.0) --- updated-dependencies: - dependency-name: numpy dependency-version: 2.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 55940cb4..3b6d5394 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ libretranslatepy==2.1.4 lief==0.15.1 Markdown==3.10 Mastodon.py==2.1.4 -numpy==2.3.5 +numpy==2.4.0 oauthlib==3.3.1 packaging==25.0 pillow==12.0.0 From 243dfdacb471430356ca9d683db3d446a8dc2367 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:06:28 +0000 Subject: [PATCH 29/37] Bump importlib-metadata from 8.7.0 to 8.7.1 Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 8.7.0 to 8.7.1. - [Release notes](https://github.com/python/importlib_metadata/releases) - [Changelog](https://github.com/python/importlib_metadata/blob/main/NEWS.rst) - [Commits](https://github.com/python/importlib_metadata/compare/v8.7.0...v8.7.1) --- updated-dependencies: - dependency-name: importlib-metadata dependency-version: 8.7.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 55940cb4..d82205f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ demoji==1.1.0 deepl==1.27.0 future==1.0.0 idna==3.11 -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 iniconfig==2.3.0 libloader @ git+https://github.com/accessibleapps/libloader@bc94811c095b2e57a036acd88660be9a33260267 libretranslatepy==2.1.4 From cb98f74ba85315b02140fda0ff22a314333cc81d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:06:37 +0000 Subject: [PATCH 30/37] Bump cx-freeze from 8.5.0 to 8.5.1 Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 8.5.0 to 8.5.1. - [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases) - [Changelog](https://github.com/marcelotduarte/cx_Freeze/blob/main/CHANGELOG.md) - [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/8.5.0...8.5.1) --- updated-dependencies: - dependency-name: cx-freeze dependency-version: 8.5.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 55940cb4..0431d925 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 coverage==7.13.0 -cx-Freeze==8.5.0 +cx-Freeze==8.5.1 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 From 39608774e74fae674b1112db5cb3d16667a60f88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:07:42 +0000 Subject: [PATCH 31/37] Bump cx-freeze from 8.5.1 to 8.5.2 Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 8.5.1 to 8.5.2. - [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases) - [Changelog](https://github.com/marcelotduarte/cx_Freeze/blob/main/CHANGELOG.md) - [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/8.5.1...8.5.2) --- updated-dependencies: - dependency-name: cx-freeze dependency-version: 8.5.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9aa70ece..3547a2c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 coverage==7.13.0 -cx-Freeze==8.5.1 +cx-Freeze==8.5.2 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 From eac75d93fd5f825c1e791af806451ba57ceb4c6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:07:45 +0000 Subject: [PATCH 32/37] Bump psutil from 7.1.3 to 7.2.1 Bumps [psutil](https://github.com/giampaolo/psutil) from 7.1.3 to 7.2.1. - [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst) - [Commits](https://github.com/giampaolo/psutil/compare/release-7.1.3...release-7.2.1) --- updated-dependencies: - dependency-name: psutil dependency-version: 7.2.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9aa70ece..3b663e1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,7 +29,7 @@ packaging==25.0 pillow==12.0.0 platform_utils @ git+https://github.com/accessibleapps/platform_utils@e0d79f7b399c4ea677a633d2dde9202350d62c38 pluggy==1.6.0 -psutil==7.1.3 +psutil==7.2.1 pyenchant==3.3.0 pypiwin32==223 Pypubsub==4.0.7 From 067957edbf0b8fd1979170ae9be509578f9e66f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 06:44:45 +0000 Subject: [PATCH 33/37] Bump coverage from 7.13.0 to 7.13.1 Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.0 to 7.13.1. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.0...7.13.1) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d9498cc3..6ac1e40b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ chardet==5.2.0 charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 -coverage==7.13.0 +coverage==7.13.1 cx-Freeze==8.5.2 cx-Logging==3.2.1 decorator==5.2.1 From 2efed8d5c6daa03c6da85a414a53d22955178d76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:08:41 +0000 Subject: [PATCH 34/37] Bump pillow from 12.0.0 to 12.1.0 Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.0.0 to 12.1.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/12.0.0...12.1.0) --- updated-dependencies: - dependency-name: pillow dependency-version: 12.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ac1e40b..e2f93e31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ Mastodon.py==2.1.4 numpy==2.4.0 oauthlib==3.3.1 packaging==25.0 -pillow==12.0.0 +pillow==12.1.0 platform_utils @ git+https://github.com/accessibleapps/platform_utils@e0d79f7b399c4ea677a633d2dde9202350d62c38 pluggy==1.6.0 psutil==7.2.1 From 7bf8e1670f1d85034ac7f281a505ae7f2a53f4a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:08:47 +0000 Subject: [PATCH 35/37] Bump cx-freeze from 8.5.2 to 8.5.3 Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 8.5.2 to 8.5.3. - [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases) - [Changelog](https://github.com/marcelotduarte/cx_Freeze/blob/main/CHANGELOG.md) - [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/8.5.2...8.5.3) --- updated-dependencies: - dependency-name: cx-freeze dependency-version: 8.5.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ac1e40b..0b54784a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ charset-normalizer==3.4.4 colorama==0.4.6 configobj==5.0.9 coverage==7.13.1 -cx-Freeze==8.5.2 +cx-Freeze==8.5.3 cx-Logging==3.2.1 decorator==5.2.1 demoji==1.1.0 From 0e4a1cbe6580f02476b116b46f5513e50500597a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:08:52 +0000 Subject: [PATCH 36/37] Bump certifi from 2025.11.12 to 2026.1.4 Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.11.12 to 2026.1.4. - [Commits](https://github.com/certifi/python-certifi/compare/2025.11.12...2026.01.04) --- updated-dependencies: - dependency-name: certifi dependency-version: 2026.1.4 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ac1e40b..c536271c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ arrow==1.4.0 attrs==25.4.0 backports.functools-lru-cache==2.0.0 blurhash==1.1.5 -certifi==2025.11.12 +certifi==2026.1.4 chardet==5.2.0 charset-normalizer==3.4.4 colorama==0.4.6 From eacce7c1971462a031a7092fc0d9d58979b63add Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 16:57:36 +0000 Subject: [PATCH 37/37] Bump urllib3 from 2.6.2 to 2.6.3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.6.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.2...2.6.3) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4d45a830..e140ed10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ sound_lib @ git+https://github.com/accessibleapps/sound_lib@a439f0943fb95ee7b6ba sqlitedict==2.1.0 twitter-text-parser==3.0.0 types-python-dateutil==2.9.0.20251115 -urllib3==2.6.2 +urllib3==2.6.3 win-inet-pton==1.1.0 winpaths==0.2 wxPython==4.2.4