mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2024-11-22 11:18:08 -06:00
Mastodon: Added actions for notifications. closes #517
This commit is contained in:
parent
3907777c91
commit
cdcbcf754a
@ -4,17 +4,19 @@ TWBlue Changelog
|
||||
|
||||
* Core:
|
||||
* The TWBlue website will no longer be available on the twblue.es domain. Beginning in January 2024, TWBlue will live at https://twblue.mcvsoftware.com. Also, we will start releasing versions on [gitHub releases](https://github.com/mcv-software/twblue/releases) so it will be easier to track specific versions.
|
||||
* As of the first release of TWBlue in 2024, we will officially stop generating 32-bit (X86) compatible binaries due to the increasing difficulty of generating versions compatible with this architecture in modern Python versions.
|
||||
* As of the first release of TWBlue in 2024, we will officially stop generating 32-bit (X86) compatible binaries due to the increasing difficulty of generating versions compatible with this architecture in modern Python.
|
||||
* TWBlue should be more reliable when checking for updates.
|
||||
* If running from source, automatic updates will not be checked as this works only for distribution. ([#540](https://github.com/MCV-Software/TWBlue/pull/540))
|
||||
* Fixed the 'report an error' item in the help menu. Now this item redirects to our gitHub issue tracker. ([#524](https://github.com/MCV-Software/TWBlue/pull/524))
|
||||
* Mastodon:
|
||||
* Implemented actions for the notifications buffer on a mastodon instance. Actions can be performed from the contextual menu on every notification, or by using invisible keystrokes. ([#517](https://github.com/mcv-software/twblue(issues/517))
|
||||
* Implemented update profile dialog. ([#547](https://github.com/MCV-Software/TWBlue/pull/547))
|
||||
* It is possible to display an user profile from the user menu within the menu bar, or by using the invisible keystroke for user details. ([#555](https://github.com/MCV-Software/TWBlue/pull/555))
|
||||
* Added possibility to vote in polls.
|
||||
* Added possibility to vote in polls. This is mapped to Alt+Win+Shift+V in the invisible keymaps for windows 10/11.
|
||||
* Added posts search. Take into account that Mastodon instances should be configured with full text search enabled. Search for posts only include posts the logged-in user has interacted with. ([#541](https://github.com/MCV-Software/TWBlue/pull/541))
|
||||
* Added user autocompletion settings in account settings dialog, so it is possible to ask TWBlue to scan mastodon accounts and add people from followers and following buffers. For now, user autocompletion can be used only when composing new posts or replies.
|
||||
* TWBlue should be able to ignore deleted direct messages or messages from deleted accounts. Previously, a direct message that no longer existed in the instance caused errors when loading the direct messages buffer and could potentially affect notifications as well.
|
||||
* TWBlue should be able to ignore notifications from deleted accounts or posts.
|
||||
|
||||
## changes in version 2023.4.13
|
||||
|
||||
|
@ -263,7 +263,10 @@ class BaseBuffer(base.Buffer):
|
||||
menu = menus.base()
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost)
|
||||
if self.can_share() == True:
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost)
|
||||
else:
|
||||
menu.boost.Enable(False)
|
||||
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.url_, menuitem=menu.openUrl)
|
||||
@ -310,14 +313,16 @@ class BaseBuffer(base.Buffer):
|
||||
if index > -1 and self.session.db.get(self.name) != None:
|
||||
return self.session.db[self.name][index]
|
||||
|
||||
def can_share(self):
|
||||
post = self.get_item()
|
||||
if post.visibility == "direct":
|
||||
def can_share(self, item=None):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if item.visibility == "direct":
|
||||
return False
|
||||
return True
|
||||
|
||||
def reply(self, *args):
|
||||
item = self.get_item()
|
||||
def reply(self, item=None, *args, **kwargs):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
visibility = item.visibility
|
||||
if visibility == "direct":
|
||||
title = _("Conversation with {}").format(item.account.username)
|
||||
@ -352,8 +357,9 @@ class BaseBuffer(base.Buffer):
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
def send_message(self, item=None, *args, **kwargs):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
title = _("Conversation with {}").format(item.account.username)
|
||||
caption = _("Write your message here")
|
||||
if item.reblog != None:
|
||||
@ -378,11 +384,12 @@ class BaseBuffer(base.Buffer):
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def share_item(self, *args, **kwargs):
|
||||
if self.can_share() == False:
|
||||
def share_item(self, item=None, *args, **kwargs):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if self.can_share(item=item) == False:
|
||||
return output.speak(_("This action is not supported on conversations."))
|
||||
post = self.get_item()
|
||||
id = post.id
|
||||
id = item.id
|
||||
if self.session.settings["general"]["boost_mode"] == "ask":
|
||||
answer = mastodon_dialogs.boost_question()
|
||||
if answer == True:
|
||||
@ -407,12 +414,11 @@ class BaseBuffer(base.Buffer):
|
||||
pub.sendMessage("toggleShare", shareable=can_share)
|
||||
self.buffer.boost.Enable(can_share)
|
||||
|
||||
def audio(self, url='', *args, **kwargs):
|
||||
def audio(self, item=None, *args, **kwargs):
|
||||
if sound.URLPlayer.player.is_playing():
|
||||
return sound.URLPlayer.stop_audio()
|
||||
item = self.get_item()
|
||||
if item == None:
|
||||
return
|
||||
item = self.get_item()
|
||||
urls = utils.get_media_urls(item)
|
||||
if len(urls) == 1:
|
||||
url=urls[0]
|
||||
@ -428,25 +434,25 @@ class BaseBuffer(base.Buffer):
|
||||
# except:
|
||||
# log.error("Exception while executing audio method.")
|
||||
|
||||
def url(self, url='', announce=True, *args, **kwargs):
|
||||
if url == '':
|
||||
post = self.get_item()
|
||||
if post.reblog != None:
|
||||
urls = utils.find_urls(post.reblog)
|
||||
else:
|
||||
urls = utils.find_urls(post)
|
||||
if len(urls) == 1:
|
||||
url=urls[0]
|
||||
elif len(urls) > 1:
|
||||
urls_list = urlList.urlList()
|
||||
urls_list.populate_list(urls)
|
||||
if urls_list.get_response() == widgetUtils.OK:
|
||||
url=urls_list.get_string()
|
||||
if hasattr(urls_list, "destroy"): urls_list.destroy()
|
||||
if url != '':
|
||||
if announce:
|
||||
output.speak(_(u"Opening URL..."), True)
|
||||
webbrowser.open_new_tab(url)
|
||||
def url(self, announce=True, item=None, *args, **kwargs):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
urls = utils.find_urls(item.reblog)
|
||||
else:
|
||||
urls = utils.find_urls(item)
|
||||
if len(urls) == 1:
|
||||
url=urls[0]
|
||||
elif len(urls) > 1:
|
||||
urls_list = urlList.urlList()
|
||||
urls_list.populate_list(urls)
|
||||
if urls_list.get_response() == widgetUtils.OK:
|
||||
url=urls_list.get_string()
|
||||
if hasattr(urls_list, "destroy"): urls_list.destroy()
|
||||
if url != '':
|
||||
if announce:
|
||||
output.speak(_(u"Opening URL..."), True)
|
||||
webbrowser.open_new_tab(url)
|
||||
|
||||
def clear_list(self):
|
||||
dlg = commonMessageDialogs.clear_list()
|
||||
@ -476,31 +482,37 @@ class BaseBuffer(base.Buffer):
|
||||
item = self.get_item()
|
||||
pass
|
||||
|
||||
def get_item_url(self):
|
||||
post = self.get_item()
|
||||
if post.reblog != None:
|
||||
return post.reblog.url
|
||||
return post.url
|
||||
def get_item_url(self, item=None):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
return item.reblog.url
|
||||
return item.url
|
||||
|
||||
def open_in_browser(self, *args, **kwargs):
|
||||
url = self.get_item_url()
|
||||
def open_in_browser(self, item=None, *args, **kwargs):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
url = self.get_item_url(item=item)
|
||||
output.speak(_("Opening item in web browser..."))
|
||||
webbrowser.open(url)
|
||||
|
||||
def add_to_favorites(self):
|
||||
item = self.get_item()
|
||||
def add_to_favorites(self, item=None):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def remove_from_favorites(self):
|
||||
item = self.get_item()
|
||||
def remove_from_favorites(self, item=None):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def toggle_favorite(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
def toggle_favorite(self, item=None, *args, **kwargs):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
try:
|
||||
@ -513,8 +525,9 @@ class BaseBuffer(base.Buffer):
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def toggle_bookmark(self, *args, **kwargs):
|
||||
item = self.get_item()
|
||||
def toggle_bookmark(self, item=None, *args, **kwargs):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
if item.reblog != None:
|
||||
item = item.reblog
|
||||
try:
|
||||
@ -527,16 +540,17 @@ class BaseBuffer(base.Buffer):
|
||||
else:
|
||||
call_threaded(self.session.api_call, call_name="status_unbookmark", preexec_message=_("Removing from bookmarks..."), _sound="favourite.ogg", id=item.id)
|
||||
|
||||
def view_item(self):
|
||||
post = self.get_item()
|
||||
def view_item(self, item=None):
|
||||
if item == None:
|
||||
item = self.get_item()
|
||||
# Update object so we can retrieve newer stats
|
||||
try:
|
||||
post = self.session.api.status(id=post.id)
|
||||
item = self.session.api.status(id=item.id)
|
||||
except MastodonNotFoundError:
|
||||
output.speak(_("No status found with that ID"))
|
||||
return
|
||||
# print(post)
|
||||
msg = messages.viewPost(post, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url())
|
||||
msg = messages.viewPost(item, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url(item=item))
|
||||
|
||||
def ocr_image(self):
|
||||
post = self.get_item()
|
||||
|
@ -3,15 +3,23 @@ import time
|
||||
import logging
|
||||
import widgetUtils
|
||||
import output
|
||||
from pubsub import pub
|
||||
from controller.buffers.mastodon.base import BaseBuffer
|
||||
from controller.mastodon import messages
|
||||
from sessions.mastodon import compose, templates
|
||||
from wxUI import buffers
|
||||
from wxUI.dialogs.mastodon import dialogs as mastodon_dialogs
|
||||
from wxUI.dialogs.mastodon import menus
|
||||
from mysc.thread_utils import call_threaded
|
||||
|
||||
log = logging.getLogger("controller.buffers.mastodon.notifications")
|
||||
|
||||
class NotificationsBuffer(BaseBuffer):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NotificationsBuffer, self).__init__(*args, **kwargs)
|
||||
self.type = "notificationsBuffer"
|
||||
|
||||
def get_message(self):
|
||||
notification = self.get_item()
|
||||
if notification == None:
|
||||
@ -36,16 +44,90 @@ class NotificationsBuffer(BaseBuffer):
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.post)
|
||||
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.destroy_status, self.buffer.dismiss)
|
||||
|
||||
def fav(self):
|
||||
pass
|
||||
|
||||
def unfav(self):
|
||||
pass
|
||||
|
||||
def vote(self):
|
||||
pass
|
||||
|
||||
def can_share(self):
|
||||
def can_share(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
return super(NotificationsBuffer, self).can_share(item=item.status)
|
||||
return False
|
||||
|
||||
def add_to_favorites(self):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).add_to_favorites(item=item.status)
|
||||
|
||||
def remove_from_favorites(self):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).remove_from_favorites(item=item.status)
|
||||
|
||||
def toggle_favorite(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).toggle_favorite(item=item.status)
|
||||
|
||||
def toggle_bookmark(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).toggle_bookmark(item=item.status)
|
||||
|
||||
def reply(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).reply(item=item.status)
|
||||
|
||||
def share_item(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).share_item(item=item.status)
|
||||
|
||||
def url(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).url(item=item.status, *args, **kwargs)
|
||||
|
||||
def audio(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).audio(item=item.status)
|
||||
|
||||
def view_item(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).view_item(item=item.status)
|
||||
else:
|
||||
pub.sendMessage("execute-action", action="user_details")
|
||||
|
||||
def open_in_browser(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).open_in_browser(item=item.status)
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
if self.is_post():
|
||||
item = self.get_item()
|
||||
super(NotificationsBuffer, self).send_message(item=item.status)
|
||||
else:
|
||||
item = self.get_item()
|
||||
title = _("New conversation with {}").format(item.account.username)
|
||||
caption = _("Write your message here")
|
||||
users_str = "@{} ".format(item.account.acct)
|
||||
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
|
||||
post.message.visibility.SetSelection(3)
|
||||
response = post.message.ShowModal()
|
||||
if response == wx.ID_OK:
|
||||
post_data = post.get_data()
|
||||
call_threaded(self.session.send_post, posts=post_data, visibility="direct")
|
||||
if hasattr(post.message, "destroy"):
|
||||
post.message.destroy()
|
||||
|
||||
def is_post(self):
|
||||
post_types = ["status", "mention", "reblog", "favourite", "update", "poll"]
|
||||
item = self.get_item()
|
||||
if item.type in post_types:
|
||||
return True
|
||||
return False
|
||||
|
||||
def destroy_status(self, *args, **kwargs):
|
||||
@ -64,3 +146,29 @@ class NotificationsBuffer(BaseBuffer):
|
||||
self.session.sound.play("error.ogg")
|
||||
log.exception("")
|
||||
self.session.db[self.name] = items
|
||||
|
||||
def show_menu(self, ev, pos=0, *args, **kwargs):
|
||||
if self.buffer.list.get_count() == 0:
|
||||
return
|
||||
notification = self.get_item()
|
||||
menu = menus.notification(notification.type)
|
||||
if self.is_post():
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply)
|
||||
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)
|
||||
else:
|
||||
menu.boost.Enable(False)
|
||||
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.url_, menuitem=menu.openUrl)
|
||||
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.copy, menuitem=menu.copy)
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.destroy_status, menuitem=menu.remove)
|
||||
if hasattr(menu, "openInBrowser"):
|
||||
widgetUtils.connect_event(menu, widgetUtils.MENU, self.open_in_browser, menuitem=menu.openInBrowser)
|
||||
if pos != 0:
|
||||
self.buffer.PopupMenu(menu, pos)
|
||||
else:
|
||||
self.buffer.PopupMenu(menu, self.buffer.list.list.GetPosition())
|
@ -142,6 +142,17 @@ class Handler(object):
|
||||
users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.account.acct not in users:
|
||||
users.insert(0, item.account.acct)
|
||||
elif buffer.type == "notificationsBuffer":
|
||||
if buffer.is_post():
|
||||
status = item.status
|
||||
if status.reblog != None:
|
||||
users = [user.acct for user in status.reblog.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if status.reblog.account.acct not in users and status.account.id != buffer.session.db["user_id"]:
|
||||
users.insert(0, status.reblog.account.acct)
|
||||
else:
|
||||
users = [user.acct for user in status.mentions if user.id != buffer.session.db["user_id"]]
|
||||
if item.account.acct not in users:
|
||||
users.insert(0, item.account.acct)
|
||||
u = userActions.userActions(buffer.session, users)
|
||||
|
||||
def search(self, controller, session, value):
|
||||
@ -316,6 +327,8 @@ class Handler(object):
|
||||
log.debug(f"Opening user profile. dictionary: {item}")
|
||||
mentionedUsers = list()
|
||||
holdUser = item.account if item.get('account') else None
|
||||
if hasattr(item, "type") and item.type in ["status", "mention", "reblog", "favourite", "update", "poll"]: # statuses in Notification buffers
|
||||
item = item.status
|
||||
if item.get('username'): # account dict
|
||||
holdUser = item
|
||||
elif isinstance(item.get('mentions'), list):
|
||||
|
@ -25,4 +25,32 @@ class base(wx.Menu):
|
||||
self.remove = wx.MenuItem(self, wx.ID_ANY, _(u"&Delete"))
|
||||
self.Append(self.remove)
|
||||
self.userActions = wx.MenuItem(self, wx.ID_ANY, _(u"&User actions..."))
|
||||
self.Append(self.userActions)
|
||||
self.Append(self.userActions)
|
||||
|
||||
class notification(wx.Menu):
|
||||
def __init__(self, item="status"):
|
||||
super(notification, self).__init__()
|
||||
valid_types = ["status", "mention", "reblog", "favourite", "update", "poll"]
|
||||
if item in valid_types:
|
||||
self.boost = wx.MenuItem(self, wx.ID_ANY, _("&Boost"))
|
||||
self.Append(self.boost)
|
||||
self.reply = wx.MenuItem(self, wx.ID_ANY, _(u"Re&ply"))
|
||||
self.Append(self.reply)
|
||||
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"))
|
||||
self.Append(self.unfav)
|
||||
self.openUrl = wx.MenuItem(self, wx.ID_ANY, _("&Open URL"))
|
||||
self.Append(self.openUrl)
|
||||
self.play = wx.MenuItem(self, wx.ID_ANY, _(u"&Play audio"))
|
||||
self.Append(self.play)
|
||||
self.openInBrowser = wx.MenuItem(self, wx.ID_ANY, _(u"&Open in instance"))
|
||||
self.Append(self.openInBrowser)
|
||||
self.view = wx.MenuItem(self, wx.ID_ANY, _(u"&Show post"))
|
||||
self.Append(self.view)
|
||||
self.copy = wx.MenuItem(self, wx.ID_ANY, _(u"&Copy to clipboard"))
|
||||
self.Append(self.copy)
|
||||
self.remove = wx.MenuItem(self, wx.ID_ANY, _(u"&Dismiss"))
|
||||
self.Append(self.remove)
|
||||
self.userActions = wx.MenuItem(self, wx.ID_ANY, _(u"&User actions..."))
|
||||
self.Append(self.userActions)
|
||||
|
Loading…
Reference in New Issue
Block a user