feat: implement mute conversation support for Mastodon

This feature allows users to visually hide muted conversations from the Home timeline. It includes:

- Automatic filtering of muted posts in Home.

- Immediate visual removal of posts when muting a conversation.

- New 'mute_conversation' action in context menu and keyboard shortcuts.

- Default shortcut: Alt+Win+Shift+Delete

- Win10/11 shortcut: Ctrl+Alt+Win+Backspace
This commit is contained in:
2026-01-11 01:22:29 -06:00
parent ccaa0d98ff
commit cb0bb4cf27
8 changed files with 53 additions and 1 deletions

View File

@@ -40,9 +40,31 @@ class BaseBuffer(base.Buffer):
self.buffer.account = account self.buffer.account = account
self.bind_events() self.bind_events()
self.sound = sound self.sound = sound
pub.subscribe(self.on_mute_cleanup, "mastodon.mute_cleanup")
if "-timeline" in self.name or "-followers" in self.name or "-following" in self.name or "searchterm" in self.name: if "-timeline" in self.name or "-followers" in self.name or "-following" in self.name or "searchterm" in self.name:
self.finished_timeline = False self.finished_timeline = False
def on_mute_cleanup(self, conversation_id, session_name):
if self.name != "home_timeline":
return
if session_name != self.session.get_name():
return
items_to_remove = []
for index, item in enumerate(self.session.db[self.name]):
c_id = None
if hasattr(item, "conversation_id"):
c_id = item.conversation_id
elif isinstance(item, dict):
c_id = item.get("conversation_id")
if c_id == conversation_id:
items_to_remove.append(index)
items_to_remove.sort(reverse=True)
for index in items_to_remove:
self.session.db[self.name].pop(index)
self.buffer.list.remove_item(index)
def create_buffer(self, parent, name): def create_buffer(self, parent, name):
self.buffer = buffers.mastodon.basePanel(parent, name) self.buffer = buffers.mastodon.basePanel(parent, name)
@@ -293,6 +315,7 @@ class BaseBuffer(base.Buffer):
menu.boost.Enable(False) menu.boost.Enable(False)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav) widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav) widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.mute_conversation, menuitem=menu.mute)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl) widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.audio, menuitem=menu.play) widgetUtils.connect_event(menu, widgetUtils.MENU, self.audio, menuitem=menu.play)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view) widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
@@ -612,6 +635,22 @@ class BaseBuffer(base.Buffer):
else: else:
call_threaded(self.session.api_call, call_name="status_unbookmark", preexec_message=_("Removing from bookmarks..."), _sound="favourite.ogg", id=item.id) call_threaded(self.session.api_call, call_name="status_unbookmark", preexec_message=_("Removing from bookmarks..."), _sound="favourite.ogg", id=item.id)
def mute_conversation(self, event=None, item=None, *args, **kwargs):
if item == None:
item = self.get_item()
if item.reblog != None:
item = item.reblog
try:
item = self.session.api.status(item.id)
except MastodonNotFoundError:
output.speak(_("No status found with that ID"))
return
if item.muted == False:
call_threaded(self.session.api_call, call_name="status_mute", preexec_message=_("Muting conversation..."), _sound="favourite.ogg", id=item.id)
pub.sendMessage("mastodon.mute_cleanup", conversation_id=item.conversation_id, session_name=self.session.get_name())
else:
call_threaded(self.session.api_call, call_name="status_unmute", preexec_message=_("Unmuting conversation..."), _sound="favourite.ogg", id=item.id)
def view_item(self, item=None): def view_item(self, item=None):
if item == None: if item == None:
item = self.get_item() item = self.get_item()

View File

@@ -57,5 +57,6 @@ update_buffer = string(default="control+alt+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
mute_conversation=string(default="control+alt+win+back")
find = string(default="control+win+{") find = string(default="control+win+{")
vote=string(default="alt+win+shift+v") vote=string(default="alt+win+shift+v")

View File

@@ -57,5 +57,6 @@ update_buffer = string(default="control+alt+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
mute_conversation=string(default="control+alt+win+back")
find = string(default="control+win+{") find = string(default="control+win+{")
vote=string(default="alt+win+shift+v") vote=string(default="alt+win+shift+v")

View File

@@ -57,3 +57,5 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
mute_conversation=string(default="alt+win+shift+delete")
vote=string(default="alt+win+shift+v")

View File

@@ -59,4 +59,5 @@ update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
mute_conversation=string(default="alt+win+shift+delete")
vote=string(default="alt+win+shift+v") vote=string(default="alt+win+shift+v")

View File

@@ -54,4 +54,5 @@ actions = {
"update_buffer": _(u"Updates the buffer and retrieves possible lost items there."), "update_buffer": _(u"Updates the buffer and retrieves possible lost items there."),
"ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."), "ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."),
"add_alias": _("Adds an alias to an user"), "add_alias": _("Adds an alias to an user"),
"mute_conversation": _("Mute/Unmute conversation"),
} }

View File

@@ -140,6 +140,11 @@ def evaluate_filters(post: dict, current_context: str) -> str | None:
- None if no applicable filters are found, meaning the post should be shown normally. - None if no applicable filters are found, meaning the post should be shown normally.
""" """
filters = post.get("filtered", None) filters = post.get("filtered", None)
# Automatically hide muted conversations from home timeline.
if current_context == "home" and post.get("muted") == True:
return "hide"
if filters == None: if filters == None:
return return
warn_filter_title = None warn_filter_title = None

View File

@@ -14,6 +14,8 @@ class base(wx.Menu):
self.Append(self.fav) self.Append(self.fav)
self.unfav = wx.MenuItem(self, wx.ID_ANY, _(u"R&emove from favorites")) self.unfav = wx.MenuItem(self, wx.ID_ANY, _(u"R&emove from favorites"))
self.Append(self.unfav) self.Append(self.unfav)
self.mute = wx.MenuItem(self, wx.ID_ANY, _(u"Mute/Unmute conversation"))
self.Append(self.mute)
self.openUrl = wx.MenuItem(self, wx.ID_ANY, _("&Open URL")) self.openUrl = wx.MenuItem(self, wx.ID_ANY, _("&Open URL"))
self.Append(self.openUrl) self.Append(self.openUrl)
self.openInBrowser = wx.MenuItem(self, wx.ID_ANY, _(u"&Open in instance")) self.openInBrowser = wx.MenuItem(self, wx.ID_ANY, _(u"&Open in instance"))