mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2025-04-04 19:12:28 -04:00
Merge pull request #762 from MCV-Software/feature/filters
Mastodon: adds Filter support
This commit is contained in:
commit
6aa84daf5e
@ -157,6 +157,9 @@ class BaseBuffer(base.Buffer):
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
filter_status = utils.evaluate_filters(post=i, current_context=utils.get_current_context(self.name))
|
||||
if filter_status == "hide":
|
||||
continue
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
|
@ -56,6 +56,9 @@ class MentionsBuffer(BaseBuffer):
|
||||
items_db = self.session.db[self.name]
|
||||
for i in items:
|
||||
if utils.find_item(i, self.session.db[self.name]) == None:
|
||||
filter_status = utils.evaluate_filters(post=i, current_context=utils.get_current_context(self.name))
|
||||
if filter_status == "hide":
|
||||
continue
|
||||
elements.append(i)
|
||||
if self.session.settings["general"]["reverse_timelines"] == False:
|
||||
items_db.insert(0, i)
|
||||
|
@ -165,6 +165,8 @@ class Controller(object):
|
||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.update_buffer, self.view.update_buffer)
|
||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.manage_aliases, self.view.manageAliases)
|
||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.report_error, self.view.reportError)
|
||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.create_filter, self.view.filter)
|
||||
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.manage_filters, self.view.manage_filters)
|
||||
|
||||
def set_systray_icon(self):
|
||||
self.systrayIcon = sysTrayIcon.SysTrayIcon()
|
||||
@ -1155,3 +1157,15 @@ class Controller(object):
|
||||
handler = self.get_handler(type=buffer.session.type)
|
||||
if handler and hasattr(handler, 'community_timeline'):
|
||||
handler.community_timeline(self, buffer)
|
||||
|
||||
def create_filter(self, *args, **kwargs):
|
||||
buffer = self.get_best_buffer()
|
||||
handler = self.get_handler(type=buffer.session.type)
|
||||
if handler and hasattr(handler, 'create_filter'):
|
||||
handler.create_filter(self, buffer)
|
||||
|
||||
def manage_filters(self, *args, **kwargs):
|
||||
buffer = self.get_best_buffer()
|
||||
handler = self.get_handler(type=buffer.session.type)
|
||||
if handler and hasattr(handler, 'manage_filters'):
|
||||
handler.manage_filters(self, buffer)
|
1
src/controller/mastodon/filters/__init__.py
Normal file
1
src/controller/mastodon/filters/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
112
src/controller/mastodon/filters/create_filter.py
Normal file
112
src/controller/mastodon/filters/create_filter.py
Normal file
@ -0,0 +1,112 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import widgetUtils
|
||||
from wxUI.dialogs.mastodon.filters import create_filter as dialog
|
||||
from mastodon import MastodonAPIError
|
||||
|
||||
class CreateFilterController(object):
|
||||
def __init__(self, session, filter_data=None):
|
||||
super(CreateFilterController, self).__init__()
|
||||
self.session = session
|
||||
self.filter_data = filter_data
|
||||
self.dialog = dialog.CreateFilterDialog(parent=None)
|
||||
if self.filter_data is not None:
|
||||
self.keywords = self.filter_data.get("keywords")
|
||||
self.load_filter_data()
|
||||
else:
|
||||
self.keywords = []
|
||||
widgetUtils.connect_event(self.dialog.keyword_panel.add_button, widgetUtils.BUTTON_PRESSED, self.on_add_keyword)
|
||||
widgetUtils.connect_event(self.dialog.keyword_panel.remove_button, widgetUtils.BUTTON_PRESSED, self.on_remove_keyword)
|
||||
|
||||
def on_add_keyword(self, event):
|
||||
""" Adds a keyword to the list. """
|
||||
keyword = self.dialog.keyword_panel.keyword_text.GetValue().strip()
|
||||
whole_word = self.dialog.keyword_panel.whole_word_checkbox.GetValue()
|
||||
if keyword:
|
||||
for idx, kw in enumerate(self.keywords):
|
||||
if kw['keyword'] == keyword:
|
||||
return
|
||||
keyword_data = {
|
||||
'keyword': keyword,
|
||||
'whole_word': whole_word
|
||||
}
|
||||
self.keywords.append(keyword_data)
|
||||
self.dialog.keyword_panel.add_keyword(keyword, whole_word)
|
||||
|
||||
def on_remove_keyword(self, event):
|
||||
removed = self.dialog.keyword_panel.remove_keyword()
|
||||
if removed is not None:
|
||||
self.keywords.pop(removed)
|
||||
|
||||
def get_expires_in_seconds(self, selection, value):
|
||||
if selection == 0:
|
||||
return None
|
||||
if selection == 1:
|
||||
return value * 3600
|
||||
elif selection == 2:
|
||||
return value * 86400
|
||||
elif selection == 3:
|
||||
return value * 604800
|
||||
elif selection == 4:
|
||||
return value * 2592000
|
||||
return None
|
||||
|
||||
def set_expires_in(self, seconds):
|
||||
if seconds is None:
|
||||
self.dialog.expiration_choice.SetSelection(0)
|
||||
self.dialog.expiration_value.Enable(False)
|
||||
return
|
||||
if seconds % 2592000 == 0 and seconds >= 2592000:
|
||||
self.dialog.expiration_choice.SetSelection(4)
|
||||
self.dialog.expiration_value.SetValue(seconds // 2592000)
|
||||
elif seconds % 604800 == 0 and seconds >= 604800:
|
||||
self.dialog.expiration_choice.SetSelection(3)
|
||||
self.dialog.expiration_value.SetValue(seconds // 604800)
|
||||
elif seconds % 86400 == 0 and seconds >= 86400:
|
||||
self.dialog.expiration_choice.SetSelection(2)
|
||||
self.dialog.expiration_value.SetValue(seconds // 86400)
|
||||
else:
|
||||
self.dialog.expiration_choice.SetSelection(1)
|
||||
self.dialog.expiration_value.SetValue(max(1, seconds // 3600))
|
||||
self.dialog.expiration_value.Enable(True)
|
||||
|
||||
def load_filter_data(self):
|
||||
if 'title' in self.filter_data:
|
||||
self.dialog.name_ctrl.SetValue(self.filter_data['title'])
|
||||
self.dialog.SetTitle(_("Update Filter: {}").format(self.filter_data['title']))
|
||||
if 'context' in self.filter_data:
|
||||
for context in self.filter_data['context']:
|
||||
if context in self.dialog.context_checkboxes:
|
||||
self.dialog.context_checkboxes[context].SetValue(True)
|
||||
if 'filter_action' in self.filter_data:
|
||||
action_index = self.dialog.actions.index(self.filter_data['filter_action']) if self.filter_data['filter_action'] in self.dialog.actions else 0
|
||||
self.dialog.action_choice.SetSelection(action_index)
|
||||
if 'expires_in' in self.filter_data:
|
||||
self.set_expires_in(self.filter_data['expires_in'])
|
||||
print(self.filter_data)
|
||||
if 'keywords' in self.filter_data:
|
||||
self.keywords = self.filter_data['keywords']
|
||||
self.dialog.keyword_panel.set_keywords(self.filter_data['keywords'])
|
||||
|
||||
def get_filter_data(self):
|
||||
filter_data = {
|
||||
'title': self.dialog.name_ctrl.GetValue(),
|
||||
'context': [],
|
||||
'filter_action': self.dialog.actions[self.dialog.action_choice.GetSelection()],
|
||||
'expires_in': self.get_expires_in_seconds(selection=self.dialog.expiration_choice.GetSelection(), value=self.dialog.expiration_value.GetValue()),
|
||||
'keywords_attributes': self.keywords
|
||||
}
|
||||
for context, checkbox in self.dialog.context_checkboxes.items():
|
||||
if checkbox.GetValue():
|
||||
filter_data['context'].append(context)
|
||||
return filter_data
|
||||
|
||||
def get_response(self):
|
||||
response = self.dialog.ShowModal()
|
||||
if response == widgetUtils.OK:
|
||||
filter_data = self.get_filter_data()
|
||||
if self.filter_data == None:
|
||||
result = self.session.api.create_filter_v2(**filter_data)
|
||||
else:
|
||||
result = self.session.api.update_filter_v2(filter_id=self.filter_data['id'], **filter_data)
|
||||
return result
|
||||
return None
|
99
src/controller/mastodon/filters/manage_filters.py
Normal file
99
src/controller/mastodon/filters/manage_filters.py
Normal file
@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import wx
|
||||
import widgetUtils
|
||||
from wxUI import commonMessageDialogs
|
||||
from wxUI.dialogs.mastodon.filters import manage_filters as dialog
|
||||
from . import create_filter
|
||||
from mastodon import MastodonError
|
||||
|
||||
class ManageFiltersController(object):
|
||||
def __init__(self, session):
|
||||
super(ManageFiltersController, self).__init__()
|
||||
self.session = session
|
||||
self.selected_filter_idx = -1
|
||||
self.error_loading = False
|
||||
self.dialog = dialog.ManageFiltersDialog(parent=None)
|
||||
self.dialog.filter_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_filter_selected)
|
||||
self.dialog.filter_list.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.on_filter_deselected)
|
||||
widgetUtils.connect_event(self.dialog.add_button, wx.EVT_BUTTON, self.on_add_filter)
|
||||
widgetUtils.connect_event(self.dialog.edit_button, wx.EVT_BUTTON, self.on_edit_filter)
|
||||
widgetUtils.connect_event(self.dialog.remove_button, wx.EVT_BUTTON, self.on_remove_filter)
|
||||
self.load_filter_data()
|
||||
|
||||
def on_filter_selected(self, event):
|
||||
"""Handle filter selection event."""
|
||||
self.selected_filter_idx = event.GetIndex()
|
||||
self.dialog.edit_button.Enable()
|
||||
self.dialog.remove_button.Enable()
|
||||
|
||||
def on_filter_deselected(self, event):
|
||||
"""Handle filter deselection event."""
|
||||
self.selected_filter_idx = -1
|
||||
self.dialog.edit_button.Disable()
|
||||
self.dialog.remove_button.Disable()
|
||||
|
||||
def get_selected_filter_id(self):
|
||||
"""Get the ID of the currently selected filter."""
|
||||
if self.selected_filter_idx != -1:
|
||||
return self.dialog.filter_list.GetItemData(self.selected_filter_idx)
|
||||
return None
|
||||
|
||||
def load_filter_data(self):
|
||||
try:
|
||||
filters = self.session.api.filters_v2()
|
||||
self.dialog.filter_list.DeleteAllItems()
|
||||
self.on_filter_deselected(None)
|
||||
for i, filter_obj in enumerate(filters):
|
||||
index = self.dialog.filter_list.InsertItem(i, filter_obj.title)
|
||||
keyword_count = len(filter_obj.keywords)
|
||||
self.dialog.filter_list.SetItem(index, 1, str(keyword_count))
|
||||
contexts = ", ".join(filter_obj.context)
|
||||
self.dialog.filter_list.SetItem(index, 2, contexts)
|
||||
self.dialog.filter_list.SetItem(index, 3, filter_obj.filter_action)
|
||||
if filter_obj.expires_at:
|
||||
expiry_str = filter_obj.expires_at.strftime("%Y-%m-%d %H:%M")
|
||||
else:
|
||||
expiry_str = _("Never")
|
||||
self.dialog.filter_list.SetItem(index, 4, expiry_str)
|
||||
self.dialog.filter_list.SetItemData(index, int(filter_obj.id) if isinstance(filter_obj.id, (int, str)) else 0)
|
||||
except MastodonError as e:
|
||||
commonMessageDialogs.error_loading_filters()
|
||||
self.error_loading = True
|
||||
|
||||
def on_add_filter(self, *args, **kwargs):
|
||||
filterController = create_filter.CreateFilterController(self.session)
|
||||
try:
|
||||
filter = filterController.get_response()
|
||||
self.load_filter_data()
|
||||
except MastodonError as error:
|
||||
commonMessageDialogs.error_adding_filter()
|
||||
return self.on_add_filter()
|
||||
|
||||
def on_edit_filter(self, *args, **kwargs):
|
||||
filter_id = self.get_selected_filter_id()
|
||||
if filter_id == None:
|
||||
return
|
||||
try:
|
||||
filter_data = self.session.api.filter_v2(filter_id)
|
||||
filterController = create_filter.CreateFilterController(self.session, filter_data=filter_data)
|
||||
filterController.get_response()
|
||||
self.load_filter_data()
|
||||
except MastodonError as error:
|
||||
commonMessageDialogs.error_adding_filter()
|
||||
|
||||
def on_remove_filter(self, *args, **kwargs):
|
||||
filter_id = self.get_selected_filter_id()
|
||||
if filter_id == None:
|
||||
return
|
||||
dlg = commonMessageDialogs.remove_filter()
|
||||
if dlg == widgetUtils.NO:
|
||||
return
|
||||
try:
|
||||
self.session.api.delete_filter_v2(filter_id)
|
||||
self.load_filter_data()
|
||||
except MastodonError as error:
|
||||
commonMessageDialogs.error_removing_filter()
|
||||
|
||||
def get_response(self):
|
||||
return self.dialog.ShowModal() == wx.ID_OK
|
@ -15,6 +15,7 @@ from wxUI.dialogs.mastodon import updateProfile as update_profile_dialogs
|
||||
from wxUI.dialogs.mastodon import showUserProfile, communityTimeline
|
||||
from sessions.mastodon.utils import html_filter
|
||||
from . import userActions, settings
|
||||
from .filters import create_filter, manage_filters
|
||||
|
||||
log = logging.getLogger("controller.mastodon.handler")
|
||||
|
||||
@ -51,8 +52,8 @@ class Handler(object):
|
||||
favs=None,
|
||||
# In buffer Menu.
|
||||
community_timeline =_("Create c&ommunity timeline"),
|
||||
filter=None,
|
||||
manage_filters=None
|
||||
filter=_("Create a &filter"),
|
||||
manage_filters=_("&Manage filters")
|
||||
)
|
||||
# Name for the "tweet" menu in the menu bar.
|
||||
self.item_menu = _("&Post")
|
||||
@ -406,3 +407,16 @@ class Handler(object):
|
||||
buffer.session.settings.write()
|
||||
communities_position =controller.view.search("communities", buffer.session.get_name())
|
||||
pub.sendMessage("createBuffer", buffer_type="CommunityBuffer", session_type=buffer.session.type, buffer_title=title, parent_tab=communities_position, start=True, kwargs=dict(parent=controller.view.nb, function="timeline", name=tl_info, sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", community_url=url, timeline=bufftype))
|
||||
|
||||
def create_filter(self, controller, buffer):
|
||||
filterController = create_filter.CreateFilterController(buffer.session)
|
||||
try:
|
||||
filter = filterController.get_response()
|
||||
except MastodonError as error:
|
||||
log.exception("Error adding filter.")
|
||||
commonMessageDialogs.error_adding_filter()
|
||||
return self.create_filter(controller=controller, buffer=buffer)
|
||||
|
||||
def manage_filters(self, controller, buffer):
|
||||
manageFiltersController = manage_filters.ManageFiltersController(buffer.session)
|
||||
manageFiltersController.get_response()
|
@ -17,6 +17,9 @@ 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)
|
||||
filtered = utils.evaluate_filters(post=post, current_context="home")
|
||||
if filtered != None:
|
||||
text = _("hidden by filter {}").format(filtered)
|
||||
source = post.get("application", "")
|
||||
# "" means remote user, None for legacy apps so we should cover both sides.
|
||||
if source != None and source != "":
|
||||
@ -73,4 +76,7 @@ def compose_notification(notification, db, settings, relative_times, show_screen
|
||||
text = _("A poll in which you have voted has expired: {status}").format(status=",".join(compose_post(notification.status, db, settings, relative_times, show_screen_names, safe=safe)))
|
||||
elif notification.type == "follow_request":
|
||||
text = _("{username} wants to follow you.").format(username=user)
|
||||
filtered = utils.evaluate_filters(post=notification, current_context="notifications")
|
||||
if filtered != None:
|
||||
text = _("hidden by filter {}").format(filtered)
|
||||
return [user, text, ts]
|
@ -170,6 +170,9 @@ class Session(base.baseSession):
|
||||
log.error("Ignoring an older tweet... Last id: {0}, tweet id: {1}".format(last_id, i.id))
|
||||
continue
|
||||
if utils.find_item(i, self.db[name]) == None:
|
||||
filter_status = utils.evaluate_filters(post=i, current_context=utils.get_current_context(name))
|
||||
if filter_status == "hide":
|
||||
continue
|
||||
if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
|
||||
else: objects.insert(0, i)
|
||||
num = num+1
|
||||
|
@ -75,6 +75,9 @@ def render_post(post, template, settings, relative_times=False, offset_hours=0):
|
||||
else:
|
||||
text = process_text(post, safe=False)
|
||||
safe_text = process_text(post)
|
||||
filtered = utils.evaluate_filters(post=post, current_context="home")
|
||||
if filtered != None:
|
||||
text = _("hidden by filter {}").format(filtered)
|
||||
visibility_settings = dict(public=_("Public"), unlisted=_("Not listed"), private=_("Followers only"), direct=_("Direct"))
|
||||
visibility = visibility_settings.get(post.visibility)
|
||||
available_data.update(lang=post.language, text=text, safe_text=safe_text, visibility=visibility)
|
||||
@ -161,6 +164,9 @@ def render_notification(notification, template, post_template, settings, relativ
|
||||
text = _("A poll in which you have voted has expired: {status}").format(status=render_post(notification.status, post_template, settings, relative_times, offset_hours))
|
||||
elif notification.type == "follow_request":
|
||||
text = _("wants to follow you.")
|
||||
filtered = utils.evaluate_filters(post=notification, current_context="notifications")
|
||||
if filtered != None:
|
||||
text = _("hidden by filter {}").format(filtered)
|
||||
available_data.update(text=text)
|
||||
result = Template(_(template)).safe_substitute(**available_data)
|
||||
result = result.replace(" . ", "")
|
||||
|
@ -1,6 +1,7 @@
|
||||
import re
|
||||
import demoji
|
||||
from html.parser import HTMLParser
|
||||
from datetime import datetime, timezone
|
||||
|
||||
url_re = re.compile('<a\s*href=[\'|"](.*?)[\'"].*?>')
|
||||
|
||||
@ -91,3 +92,59 @@ def demoji_user(name, settings):
|
||||
user = re.sub(r":(.*?):", "", user)
|
||||
return user
|
||||
return name
|
||||
|
||||
def get_current_context(buffer: str) -> str:
|
||||
""" Gets the name of a buffer and returns the context it belongs to. useful for filtering. """
|
||||
if buffer == "home_timeline":
|
||||
return "home"
|
||||
elif buffer == "mentions" or buffer == "notifications":
|
||||
return "notifications"
|
||||
|
||||
def evaluate_filters(post: dict, current_context: str) -> str | None:
|
||||
"""
|
||||
Evaluates the 'filtered' attribute of a Mastodon post to determine its visibility,
|
||||
considering the current context, expiration, and matches (keywords or status).
|
||||
|
||||
Parameters:
|
||||
post (dict): A dictionary representing a Mastodon post.
|
||||
current_context (str): The context in which the post is displayed
|
||||
(e.g., "home", "notifications", "public", "thread", or "profile").
|
||||
|
||||
Returns:
|
||||
- "hide" if any applicable filter indicates the post should be hidden.
|
||||
- A string with the filter's title if an applicable "warn" filter is present.
|
||||
- None if no applicable filters are found, meaning the post should be shown normally.
|
||||
"""
|
||||
filters = post.get("filtered", None)
|
||||
if filters == None:
|
||||
return
|
||||
warn_filter_title = None
|
||||
now = datetime.now(timezone.utc)
|
||||
for result in filters:
|
||||
filter_data = result.get("filter", {})
|
||||
# Check if the filter applies to the current context.
|
||||
filter_contexts = filter_data.get("context", [])
|
||||
if current_context not in filter_contexts:
|
||||
continue # Skip filters not applicable in this context
|
||||
# Check if the filter has expired.
|
||||
expires_at = filter_data.get("expires_at")
|
||||
if expires_at is not None:
|
||||
# If expires_at is a string, attempt to parse it.
|
||||
if isinstance(expires_at, str):
|
||||
try:
|
||||
expiration_dt = datetime.fromisoformat(expires_at)
|
||||
except ValueError:
|
||||
continue # Skip if the date format is invalid
|
||||
else:
|
||||
expiration_dt = expires_at
|
||||
if expiration_dt < now:
|
||||
continue # Skip expired filters
|
||||
action = filter_data.get("filter_action", "")
|
||||
if action == "hide":
|
||||
return "hide"
|
||||
elif action == "warn":
|
||||
title = filter_data.get("title", "")
|
||||
warn_filter_title = title if title else "warn"
|
||||
if warn_filter_title:
|
||||
return warn_filter_title
|
||||
return None
|
@ -46,4 +46,16 @@ def cant_update_source() -> wx.MessageDialog:
|
||||
return dlg.ShowModal()
|
||||
|
||||
def invalid_instance():
|
||||
return wx.MessageDialog(None, _("the provided instance is invalid. Please try again."), _("Invalid instance"), wx.ICON_ERROR).ShowModal()
|
||||
return wx.MessageDialog(None, _("the provided instance is invalid. Please try again."), _("Invalid instance"), wx.ICON_ERROR).ShowModal()
|
||||
|
||||
def error_adding_filter():
|
||||
return wx.MessageDialog(None, _("TWBlue was unable to add or update the filter with the specified settings. Please try again."), _("Error"), wx.ICON_ERROR).ShowModal()
|
||||
|
||||
def error_loading_filters():
|
||||
return wx.MessageDialog(None, _("TWBlue was unable to load your filters from the instance. Please try again."), _("Error"), wx.ICON_ERROR).ShowModal()
|
||||
|
||||
def remove_filter():
|
||||
dlg = wx.MessageDialog(None, _("Do you really want to delete this filter ?"), _("Delete filter"), wx.ICON_QUESTION|wx.YES_NO)
|
||||
return dlg.ShowModal()
|
||||
def error_removing_filters():
|
||||
return wx.MessageDialog(None, _("TWBlue was unable to remove the filter you specified. Please try again."), _("Error"), wx.ICON_ERROR).ShowModal()
|
||||
|
0
src/wxUI/dialogs/mastodon/filters/__init__.py
Normal file
0
src/wxUI/dialogs/mastodon/filters/__init__.py
Normal file
146
src/wxUI/dialogs/mastodon/filters/create_filter.py
Normal file
146
src/wxUI/dialogs/mastodon/filters/create_filter.py
Normal file
@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import wx
|
||||
|
||||
class FilterKeywordPanel(wx.Panel):
|
||||
"""panel to handle filter's keywords. """
|
||||
def __init__(self, parent):
|
||||
super(FilterKeywordPanel, self).__init__(parent)
|
||||
self.keywords = []
|
||||
# Add widgets
|
||||
list_panel = wx.Panel(self)
|
||||
self.keyword_list = wx.ListCtrl(list_panel, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
|
||||
self.keyword_list.InsertColumn(0, _("Keyword"))
|
||||
self.keyword_list.InsertColumn(1, _("Whole word"))
|
||||
self.keyword_list.SetColumnWidth(0, wx.LIST_AUTOSIZE_USEHEADER)
|
||||
self.keyword_list.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER)
|
||||
list_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
list_sizer.Add(self.keyword_list, 1, wx.EXPAND)
|
||||
list_panel.SetSizer(list_sizer)
|
||||
keyword_label = wx.StaticText(self, wx.ID_ANY, label=_("Keyword:"))
|
||||
self.keyword_text = wx.TextCtrl(self, wx.ID_ANY)
|
||||
keyword_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
keyword_sizer.Add(keyword_label, 0, wx.RIGHT, 5)
|
||||
keyword_sizer.Add(self.keyword_text, 0, wx.EXPAND)
|
||||
self.whole_word_checkbox = wx.CheckBox(self, wx.ID_ANY, label=_("Whole word"))
|
||||
self.add_button = wx.Button(self, wx.ID_ANY, label=_("Add"))
|
||||
self.remove_button = wx.Button(self, wx.ID_ANY, label=_("Remove"))
|
||||
input_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
input_sizer.Add(keyword_sizer, 1, wx.RIGHT, 5)
|
||||
input_sizer.Add(self.whole_word_checkbox, 0)
|
||||
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
button_sizer.Add(self.add_button, 0, wx.RIGHT, 5)
|
||||
button_sizer.Add(self.remove_button, 0)
|
||||
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
main_sizer.Add(wx.StaticText(self, label=_("Palabras clave a filtrar:")), 0, wx.BOTTOM, 5)
|
||||
main_sizer.Add(list_panel, 1, wx.EXPAND | wx.BOTTOM, 5)
|
||||
main_sizer.Add(input_sizer, 0, wx.EXPAND | wx.BOTTOM, 5)
|
||||
main_sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT)
|
||||
self.SetSizer(main_sizer)
|
||||
|
||||
def add_keyword(self, keyword, whole_word=False):
|
||||
""" Adds a keyword to the list. """
|
||||
index = self.keyword_list.InsertItem(self.keyword_list.GetItemCount(), keyword)
|
||||
self.keyword_list.SetItem(index, 1, _("Yes") if whole_word else _("No"))
|
||||
self.keyword_list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
self.keyword_list.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER)
|
||||
self.keyword_text.Clear()
|
||||
self.whole_word_checkbox.SetValue(False)
|
||||
|
||||
def remove_keyword(self):
|
||||
""" Remove a keyword from the list. """
|
||||
selection = self.keyword_list.GetFirstSelected()
|
||||
if selection != -1:
|
||||
self.keyword_list.DeleteItem(selection)
|
||||
return selection
|
||||
|
||||
def set_keywords(self, keywords):
|
||||
""" Set the list of keyword. """
|
||||
self.keyword_list.DeleteAllItems()
|
||||
for keyword_data in keywords:
|
||||
if isinstance(keyword_data, dict):
|
||||
kw = keyword_data.get('keyword', '')
|
||||
whole_word = keyword_data.get('whole_word', False)
|
||||
self.keywords.append({'keyword': kw, 'whole_word': whole_word})
|
||||
index = self.keyword_list.InsertItem(self.keyword_list.GetItemCount(), kw)
|
||||
self.keyword_list.SetItem(index, 1, _("Yes") if whole_word else _("No"))
|
||||
else:
|
||||
self.keywords.append({'keyword': keyword_data, 'whole_word': False})
|
||||
index = self.keyword_list.InsertItem(self.keyword_list.GetItemCount(), keyword_data)
|
||||
self.keyword_list.SetItem(index, 1, _("No"))
|
||||
self.keyword_list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
self.keyword_list.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER)
|
||||
|
||||
class CreateFilterDialog(wx.Dialog):
|
||||
def __init__(self, parent, title=_("New filter")):
|
||||
super(CreateFilterDialog, self).__init__(parent, title=title, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
|
||||
self.contexts = ["home", "public", "notifications", "thread", "account"]
|
||||
self.context_labels = {
|
||||
"home": _("Home timeline"),
|
||||
"public": _("Public statuses"),
|
||||
"notifications": _("Notifications"),
|
||||
"thread": _("Threads"),
|
||||
"account": _("Profiles")
|
||||
}
|
||||
self.actions = ["hide", "warn"]
|
||||
self.action_labels = {
|
||||
"hide": _("Hide posts"),
|
||||
"warn": _("Set a content warning to posts")
|
||||
}
|
||||
self.expiration_options = [
|
||||
("never", _("Never")),
|
||||
("hours", _("Hours")),
|
||||
("days", _("Days")),
|
||||
("weeks", _("Weeks")),
|
||||
("months", _("months"))
|
||||
]
|
||||
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
name_label = wx.StaticText(self, wx.ID_ANY, label=_("Title:"))
|
||||
self.name_ctrl = wx.TextCtrl(self, wx.ID_ANY)
|
||||
|
||||
name_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
name_sizer.Add(name_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5)
|
||||
name_sizer.Add(self.name_ctrl, 1, wx.EXPAND)
|
||||
main_sizer.Add(name_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||
static_box = wx.StaticBox(self, wx.ID_ANY, label=_("Apply to:"))
|
||||
context_sizer = wx.StaticBoxSizer(static_box, wx.VERTICAL)
|
||||
self.context_checkboxes = {}
|
||||
context_grid = wx.FlexGridSizer(rows=3, cols=2, vgap=5, hgap=10)
|
||||
for context in self.contexts:
|
||||
checkbox = wx.CheckBox(static_box, wx.ID_ANY, label=self.context_labels[context])
|
||||
self.context_checkboxes[context] = checkbox
|
||||
context_grid.Add(checkbox)
|
||||
context_sizer.Add(context_grid, 0, wx.ALL, 10)
|
||||
main_sizer.Add(context_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||
action_label = wx.StaticText(self, wx.ID_ANY, label=_("Action:"))
|
||||
self.action_choice = wx.Choice(self, wx.ID_ANY)
|
||||
for action in self.actions:
|
||||
self.action_choice.Append(self.action_labels[action])
|
||||
action_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
action_sizer.Add(action_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5)
|
||||
action_sizer.Add(self.action_choice, 1)
|
||||
main_sizer.Add(action_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||
expiration_label = wx.StaticText(self, wx.ID_ANY, label=_("Expires in:"))
|
||||
self.expiration_choice = wx.Choice(self, wx.ID_ANY)
|
||||
for e, label in self.expiration_options:
|
||||
self.expiration_choice.Append(label)
|
||||
self.expiration_value = wx.SpinCtrl(self, wx.ID_ANY, min=1, max=9999, initial=1)
|
||||
self.expiration_value.Enable(False)
|
||||
self.expiration_choice.Bind(wx.EVT_CHOICE, self.on_expiration_changed)
|
||||
expiration_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
expiration_sizer.Add(expiration_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5)
|
||||
expiration_sizer.Add(self.expiration_choice, 1, wx.RIGHT, 5)
|
||||
expiration_sizer.Add(self.expiration_value, 0)
|
||||
main_sizer.Add(expiration_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||
self.keyword_panel = FilterKeywordPanel(self)
|
||||
main_sizer.Add(self.keyword_panel, 1, wx.EXPAND | wx.ALL, 10)
|
||||
button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
|
||||
main_sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||
self.SetSizer(main_sizer)
|
||||
self.SetSize((450, 550))
|
||||
self.action_choice.SetSelection(0)
|
||||
self.expiration_choice.SetSelection(0)
|
||||
wx.CallAfter(self.name_ctrl.SetFocus)
|
||||
|
||||
def on_expiration_changed(self, event):
|
||||
selection = self.expiration_choice.GetSelection()
|
||||
self.expiration_value.Enable(selection != 0)
|
35
src/wxUI/dialogs/mastodon/filters/manage_filters.py
Normal file
35
src/wxUI/dialogs/mastodon/filters/manage_filters.py
Normal file
@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import wx
|
||||
|
||||
class ManageFiltersDialog(wx.Dialog):
|
||||
"""
|
||||
A dialog that displays a list of Mastodon filters and provides controls
|
||||
to add, edit and remove them.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, title=_("Filters"), *args, **kwargs):
|
||||
"""Initialize the filters view dialog. """
|
||||
super(ManageFiltersDialog, self).__init__(parent, title=title, *args, **kwargs)
|
||||
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.filter_list = wx.ListCtrl(self, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.BORDER_SUNKEN)
|
||||
self.filter_list.InsertColumn(0, _("Title"), width=150)
|
||||
self.filter_list.InsertColumn(1, _("Keywords"), width=80)
|
||||
self.filter_list.InsertColumn(2, _("Contexts"), width=150)
|
||||
self.filter_list.InsertColumn(3, _("Action"), width=100)
|
||||
self.filter_list.InsertColumn(4, _("Expires"), width=150)
|
||||
main_sizer.Add(self.filter_list, 1, wx.EXPAND | wx.ALL, 10)
|
||||
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.add_button = wx.Button(self, label=_("Add"))
|
||||
self.edit_button = wx.Button(self, label=_("Edit"))
|
||||
self.remove_button = wx.Button(self, label=_("Remove"))
|
||||
close_button = wx.Button(self, wx.ID_CLOSE)
|
||||
self.edit_button.Disable()
|
||||
self.remove_button.Disable()
|
||||
button_sizer.Add(self.add_button, 0, wx.RIGHT, 5)
|
||||
button_sizer.Add(self.edit_button, 0, wx.RIGHT, 5)
|
||||
button_sizer.Add(self.remove_button, 0, wx.RIGHT, 5)
|
||||
button_sizer.Add((0, 0), 1, wx.EXPAND) # Spacer to push close button to right
|
||||
button_sizer.Add(close_button, 0)
|
||||
self.SetEscapeId(close_button.GetId())
|
||||
main_sizer.Add(button_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
|
||||
self.SetSizer(main_sizer)
|
Loading…
x
Reference in New Issue
Block a user