Mastodon: Started implementation of read preferences from instance. Currently only content warnings are displayed by taking into accounts values from instance preferences

This commit is contained in:
Manuel Cortez 2022-12-23 13:58:10 -06:00
parent 460cea702b
commit 18a7a42b5a
No known key found for this signature in database
GPG Key ID: 9E0735CA15EFE790
10 changed files with 90 additions and 28 deletions

View File

@ -11,6 +11,7 @@ TWBlue Changelog
* Fixed an issue that was preventing TWBlue to create more than one user timeline during startup.
* TWBlue will display properly new paragraphs in mastodon posts.
* In the session manager, Mastodon sessions are now displayed including the instance to avoid confusion.
* TWBlue will now read default visibility preferences when posting new statuses, and display sensitive content. These preferences can be set on the mastodon instance, in the account's preferences section. If you wish to change TWBlue's behavior and have it not read those preferences from your instance, but instead set the default public visibility and hide sensitive content, you can uncheck the Read preferences from instance checkbox in the account options.
## Changes in version 2022.12.13

View File

@ -9,6 +9,7 @@ import config
import sound
import languageHandler
import logging
from mastodon import MastodonNotFoundError
from audio_services import youtube_utils
from controller.buffers.base import base
from controller.mastodon import messages
@ -72,14 +73,22 @@ class BaseBuffer(base.Buffer):
post.message.destroy()
def get_formatted_message(self):
return self.compose_function(self.get_item(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])[1]
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
return self.compose_function(self.get_item(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)[1]
def get_message(self):
post = self.get_item()
if post == None:
return
template = self.session.settings["templates"]["post"]
# If template is set to hide sensitive media by default, let's change it according to user preferences.
if self.session.settings["general"]["read_preferences_from_instance"] == True:
if self.session.expand_spoilers == True and "$safe_text" in template:
template = template.replace("$safe_text", "$text")
elif self.session.expand_spoilers == False and "$text" in template:
template = template.replace("$text", "$safe_text")
t = templates.render_post(post, template, relative_times=self.session.settings["general"]["relative_times"], offset_hours=self.session.db["utc_offset"])
return t
@ -124,7 +133,10 @@ class BaseBuffer(base.Buffer):
else:
post = self.session.db[self.name][0]
output.speak(_("New post in {0}").format(self.get_buffer_name()))
output.speak(" ".join(self.compose_function(post, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])))
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
output.speak(" ".join(self.compose_function(post, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)))
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_("{0} new posts in {1}.").format(number_of_items, self.get_buffer_name()))
@ -150,13 +162,16 @@ class BaseBuffer(base.Buffer):
self.session.db[self.name] = items_db
selection = self.buffer.list.get_selected()
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
if self.session.settings["general"]["reverse_timelines"] == False:
for i in elements:
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(True, *post)
else:
for i in elements:
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(False, *post)
self.buffer.list.select_item(selection)
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
@ -185,27 +200,33 @@ class BaseBuffer(base.Buffer):
if number_of_items == 0 and self.session.settings["general"]["persist_size"] == 0: return
log.debug("The list contains %d items " % (self.buffer.list.get_count(),))
log.debug("Putting %d items on the list" % (number_of_items,))
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
if self.buffer.list.get_count() == 0:
for i in list_to_use:
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(False, *post)
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
elif self.buffer.list.get_count() > 0 and number_of_items > 0:
if self.session.settings["general"]["reverse_timelines"] == False:
items = list_to_use[len(list_to_use)-number_of_items:]
for i in items:
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(False, *post)
else:
items = list_to_use[0:number_of_items]
items.reverse()
for i in items:
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(True, *post)
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
def add_new_item(self, item):
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
if self.session.settings["general"]["reverse_timelines"] == False:
self.buffer.list.insert_item(False, *post)
else:
@ -214,7 +235,10 @@ class BaseBuffer(base.Buffer):
output.speak(" ".join(post[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
def update_item(self, item, position):
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
post = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.list.SetItem(position, 1, post[1])
def bind_events(self):
@ -439,6 +463,7 @@ class BaseBuffer(base.Buffer):
self.buffer.list.remove_item(index)
except Exception as e:
self.session.sound.play("error.ogg")
log.exception("")
self.session.db[self.name] = items
def user_details(self):
@ -472,7 +497,11 @@ class BaseBuffer(base.Buffer):
item = self.get_item()
if item.reblog != None:
item = item.reblog
item = self.session.api.status(item.id)
try:
item = self.session.api.status(item.id)
except MastodonNotFoundError:
output.speak(_("No status found with that ID"))
return
if item.favourited == False:
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
else:
@ -482,7 +511,11 @@ class BaseBuffer(base.Buffer):
item = self.get_item()
if item.reblog != None:
item = item.reblog
item = self.session.api.status(item.id)
try:
item = self.session.api.status(item.id)
except MastodonNotFoundError:
output.speak(_("No status found with that ID"))
return
if item.bookmarked == False:
call_threaded(self.session.api_call, call_name="status_bookmark", preexec_message=_("Adding to bookmarks..."), _sound="favourite.ogg", id=item.id)
else:
@ -491,7 +524,11 @@ class BaseBuffer(base.Buffer):
def view_item(self):
post = self.get_item()
# Update object so we can retrieve newer stats
post = self.session.api.status(id=post.id)
try:
post = self.session.api.status(id=post.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())

View File

@ -4,6 +4,7 @@ import logging
import wx
import widgetUtils
import output
from mastodon import MastodonNotFoundError
from controller.mastodon import messages
from controller.buffers.mastodon.base import BaseBuffer
from mysc.thread_utils import call_threaded
@ -200,7 +201,11 @@ class ConversationBuffer(BaseBuffer):
self.execution_time = current_time
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
self.post = self.session.api.status(id=self.post.id)
try:
self.post = self.session.api.status(id=self.post.id)
except MastodonNotFoundError:
output.speak(_("No status found with that ID"))
return
# toDo: Implement reverse timelines properly here.
try:
results = []

View File

@ -56,13 +56,16 @@ class MentionsBuffer(BaseBuffer):
self.session.db[self.name] = items_db
selection = self.buffer.list.get_selected()
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
if self.session.settings["general"]["reverse_timelines"] == False:
for i in elements:
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(True, *post)
else:
for i in elements:
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"])
post = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(False, *post)
self.buffer.list.select_item(selection)
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)

View File

@ -185,6 +185,11 @@ class post(messages.basicTweet):
visibility_settings = ["public", "unlisted", "private", "direct"]
return visibility_settings[self.message.visibility.GetSelection()]
def set_visibility(self, setting):
visibility_settings = ["public", "unlisted", "private", "direct"]
visibility_setting = visibility_settings.index(setting)
self.message.visibility.SetSelection(setting)
class viewPost(post):
def __init__(self, post, offset_hours=0, date="", item_url=""):
if post.reblog != None:

View File

@ -32,6 +32,7 @@ class accountSettingsController(globalSettingsController):
# widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
# widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
self.dialog.set_value("general", "read_preferences_from_instance", self.config["general"]["read_preferences_from_instance"])
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"])
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_posts_per_call"])
@ -111,6 +112,7 @@ class accountSettingsController(globalSettingsController):
self.needs_restart = True
log.debug("Triggered app restart due to change in relative times.")
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
self.config["general"]["read_preferences_from_instance"] = self.dialog.get_value("general", "read_preferences_from_instance")
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
self.config["general"]["hide_emojis"] = self.dialog.get_value("general", "hide_emojis")
self.config["general"]["max_posts_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")

View File

@ -6,6 +6,7 @@ ignored_clients = list(default=list())
[general]
relative_times = boolean(default=True)
read_preferences_from_instance = boolean(default=True)
max_posts_per_call = integer(default=40)
reverse_timelines = boolean(default=False)
persist_size = integer(default=0)

View File

@ -3,7 +3,7 @@ import arrow
import languageHandler
from . import utils, templates
def compose_post(post, db, relative_times, show_screen_names):
def compose_post(post, db, relative_times, show_screen_names, safe=True):
if show_screen_names == False:
user = post.account.get("display_name")
if user == "":
@ -16,9 +16,9 @@ def compose_post(post, db, relative_times, show_screen_names):
else:
ts = original_date.shift(hours=db["utc_offset"]).format(_("dddd, MMMM D, YYYY H:m"), locale=languageHandler.curLang[:2])
if post.reblog != None:
text = _("Boosted from @{}: {}").format(post.reblog.account.acct, templates.process_text(post.reblog))
text = _("Boosted from @{}: {}").format(post.reblog.account.acct, templates.process_text(post.reblog, safe=safe))
else:
text = templates.process_text(post)
text = templates.process_text(post, safe=safe)
source = post.get("application", "")
# "" means remote user, None for legacy apps so we should cover both sides.
if source != None and source != "":
@ -27,7 +27,7 @@ def compose_post(post, db, relative_times, show_screen_names):
source = ""
return [user+", ", text, ts+", ", source]
def compose_user(user, db, relative_times=True, show_screen_names=False):
def compose_user(user, db, relative_times=True, show_screen_names=False, safe=False):
original_date = arrow.get(user.created_at)
if relative_times:
ts = original_date.humanize(locale=languageHandler.curLang[:2])
@ -38,7 +38,7 @@ def compose_user(user, db, relative_times=True, show_screen_names=False):
name = user.get("username")
return [_("%s (@%s). %s followers, %s following, %s posts. Joined %s") % (name, user.acct, user.followers_count, user.following_count, user.statuses_count, ts)]
def compose_conversation(conversation, db, relative_times, show_screen_names):
def compose_conversation(conversation, db, relative_times, show_screen_names, safe=False):
users = []
for account in conversation.accounts:
if account.display_name != "":
@ -50,7 +50,7 @@ def compose_conversation(conversation, db, relative_times, show_screen_names):
text = _("Last message from {}: {}").format(last_post[0], last_post[1])
return [users, text, last_post[-2], last_post[-1]]
def compose_notification(notification, db, relative_times, show_screen_names):
def compose_notification(notification, db, relative_times, show_screen_names, safe=False):
if show_screen_names == False:
user = notification.account.get("display_name")
if user == "":
@ -64,15 +64,15 @@ def compose_notification(notification, db, relative_times, show_screen_names):
ts = original_date.shift(hours=db["utc_offset"]).format(_("dddd, MMMM D, YYYY H:m"), locale=languageHandler.curLang[:2])
text = "Unknown: %r" % (notification)
if notification.type == "mention":
text = _("{username} has mentionned you: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names)))
text = _("{username} has mentionned you: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
elif notification.type == "reblog":
text = _("{username} has boosted: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names)))
text = _("{username} has boosted: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
elif notification.type == "favourite":
text = _("{username} has added to favorites: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names)))
text = _("{username} has added to favorites: {status}").format(username=user, status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
elif notification.type == "follow":
text = _("{username} has followed you.").format(username=user)
elif notification.type == "poll":
text = _("A poll in which you have voted has expired: {status}").format(status=",".join(compose_post(notification.status, db, relative_times, show_screen_names)))
text = _("A poll in which you have voted has expired: {status}").format(status=",".join(compose_post(notification.status, db, relative_times, show_screen_names, safe=safe)))
elif notification.type == "follow_request":
text = _("{username} wants to follow you.").format(username=user)
return [user, text, ts]

View File

@ -29,6 +29,8 @@ class Session(base.baseSession):
self.type = "mastodon"
self.db["pagination_info"] = dict()
self.char_limit = 500
self.post_visibility = "public"
self.expand_spoilers = False
pub.subscribe(self.on_status, "mastodon.status_received")
pub.subscribe(self.on_status_updated, "mastodon.status_updated")
pub.subscribe(self.on_notification, "mastodon.notification_received")
@ -98,14 +100,18 @@ class Session(base.baseSession):
offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
offset = offset / 60 / 60 * -1
self.db["utc_offset"] = offset
instance = self.api.instance()
if len(self.supported_languages) == 0:
self.supported_languages = self.api.instance().languages
self.supported_languages = instance.languages
self.get_lists()
self.get_muted_users()
# determine instance custom characters limit.
instance = self.api.instance()
if hasattr(instance, "configuration") and hasattr(instance.configuration, "statuses") and hasattr(instance.configuration.statuses, "max_characters"):
self.char_limit = instance.configuration.statuses.max_characters
# User preferences for some things.
preferences = self.api.preferences()
self.post_visibility = preferences.get("posting:default:visibility")
self.expand_spoilers = preferences.get("reading:expand:spoilers")
self.settings.write()
def get_lists(self):

View File

@ -22,6 +22,8 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog):
sizer.Add(autocompletionSizer, 0, wx.ALL, 5)
self.relative_time = wx.CheckBox(self, wx.ID_ANY, _("Relative timestamps"))
sizer.Add(self.relative_time, 0, wx.ALL, 5)
self.read_preferences_from_instance = wx.CheckBox(self, wx.ID_ANY, _("Read preferences from instance (default visibility when publishing and displaying sensitive content)"))
sizer.Add(self.read_preferences_from_instance, 0, wx.ALL, 5)
itemsPerCallBox = wx.BoxSizer(wx.HORIZONTAL)
itemsPerCallBox.Add(wx.StaticText(self, -1, _("Items on each API call")), 0, wx.ALL, 5)
self.itemsPerApiCall = wx.SpinCtrl(self, wx.ID_ANY)