diff --git a/doc/changelog.md b/doc/changelog.md index f3af562c..e07f3c44 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -2,6 +2,11 @@ ## changes in this version +* Added filtering capabilities to TWBlue. ([#102](https://github.com/manuelcortez/TWBlue/issues/102)) + * You can create a filter for the current buffer from the buffer menu in the menu bar. At this moment, invisible interface does not have any shorcut for this. + * You can create filters by word or languages. + * For deleting already created filters, you can go to the filter manager in the buffer menu and delete the filters you won't need. +* Links should be opened properly in quoted tweets ([#167,](https://github.com/manuelcortez/TWBlue/issues/167) [#184](https://github.com/manuelcortez/TWBlue/issues/184)) * Increased display name limit up to 50 characters in update profile dialog. * When authorising an account, you will see a dialogue with a cancel button, in case you want to abort the process. Also, NVDA will not be blocked when the process starts. ([#101](https://github.com/manuelcortez/TWBlue/issues/101)) * In the translator module, the list of available languages is fetched automatically from the provider. That means all of these languages will work and there will not be inconsistencies. Also we've removed the first combo box, because the language is detected automatically by Yandex'S API. ([#153](https://github.com/manuelcortez/TWBlue/issues/153)) diff --git a/src/application.py b/src/application.py index c4950c4e..1e2c097a 100644 --- a/src/application.py +++ b/src/application.py @@ -6,7 +6,7 @@ if snapshot == False: update_url = 'https://twblue.es/updates/stable.php' mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json' else: - version = "1" + version = "2" update_url = 'https://twblue.es/updates/snapshot.php' mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/snapshots.json' authors = [u"Manuel Cortéz", u"José Manuel Delicado"] diff --git a/src/config_utils.py b/src/config_utils.py index e6d926c5..f2cc10e6 100644 --- a/src/config_utils.py +++ b/src/config_utils.py @@ -9,8 +9,6 @@ log = getLogger("config_utils") class ConfigLoadError(Exception): pass def load_config(config_path, configspec_path=None, copy=True, *args, **kwargs): - if os.path.exists(config_path): - clean_config(config_path) spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True) try: config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs) diff --git a/src/controller/filterController.py b/src/controller/filterController.py index 3922ab31..befe3481 100644 --- a/src/controller/filterController.py +++ b/src/controller/filterController.py @@ -3,12 +3,14 @@ import time import widgetUtils import application from wxUI.dialogs import filterDialogs +from wxUI import commonMessageDialogs class filter(object): - def __init__(self, buffer): + def __init__(self, buffer, filter_title=None, if_word_exists=None, in_lang=None, regexp=None, word=None, in_buffer=None): self.buffer = buffer self.dialog = filterDialogs.filterDialog(languages=[i["name"] for i in application.supported_languages]) if self.dialog.get_response() == widgetUtils.OK: + title = self.dialog.get("title") contains = self.dialog.get("contains") term = self.dialog.get("term") regexp = self.dialog.get("regexp") @@ -25,6 +27,48 @@ class filter(object): if i["name"] in langs: langcodes.append(i["code"]) d = dict(in_buffer=self.buffer.name, word=term, regexp=regexp, in_lang=lang_option, languages=langcodes, if_word_exists=contains) - filter_title = "filter_{0}".format(str(time.time())) - self.buffer.session.settings["filters"][filter_title] = d - self.buffer.session.settings.write() \ No newline at end of file + if self.buffer.session.settings["filters"].has_key(title): + return commonMessageDialogs.existing_filter() + self.buffer.session.settings["filters"][title] = d + self.buffer.session.settings.write() + +class filterManager(object): + + def __init__(self, session): + self.session = session + self.dialog = filterDialogs.filterManagerDialog() + self.insert_filters(self.session.settings["filters"]) + if self.dialog.filters.get_count() == 0: + self.dialog.edit.Enable(False) + self.dialog.delete.Enable(False) + else: + widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.edit_filter) + widgetUtils.connect_event(self.dialog.delete, widgetUtils.BUTTON_PRESSED, self.delete_filter) + response = self.dialog.get_response() + + def insert_filters(self, filters): + self.dialog.filters.clear() + for f in filters.keys(): + # ToDo: Add titles to filters. + filterName = f + buffer = filters[f]["in_buffer"] + if filters[f]["if_word_exists"] == "True" and filters[f]["word"] != "": + filter_by_word = "True" + else: + filter_by_word = "False" + filter_by_lang = "" + if filters[f]["in_lang"] != "None": + filter_by_lang = "True" + b = [f, buffer, filter_by_word, filter_by_lang] + self.dialog.filters.insert_item(False, *b) + + def edit_filter(self, *args, **kwargs): + pass + + def delete_filter(self, *args, **kwargs): + filter_title = self.dialog.filters.get_text_column(self.dialog.filters.get_selected(), 0) + response = commonMessageDialogs.delete_filter() + if response == widgetUtils.YES: + self.session.settings["filters"].pop(filter_title) + self.session.settings.write() + self.insert_filters(self.session.settings["filters"]) \ No newline at end of file diff --git a/src/controller/mainController.py b/src/controller/mainController.py index 854d4aae..5fc2df76 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -141,6 +141,7 @@ class Controller(object): widgetUtils.connect_event(self.view, widgetUtils.MENU, self.list_manager, menuitem=self.view.lists) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.get_trending_topics, menuitem=self.view.trends) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.filter, menuitem=self.view.filter) + widgetUtils.connect_event(self.view, widgetUtils.MENU, self.manage_filters, menuitem=self.view.manage_filters) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.find, menuitem=self.view.find) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.accountConfiguration, menuitem=self.view.account_settings) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.configuration, menuitem=self.view.prefs) @@ -490,8 +491,16 @@ class Controller(object): if not hasattr(page.buffer, "list"): output.speak(_(u"No session is currently in focus. Focus a session with the next or previous session shortcut."), True) return + # Let's prevent filtering of some buffers (people buffers, direct messages, events and sent items). + if (page.name == "direct_messages" or page.name =="sent_direct_messages" or page.name == "sent_tweets" or page.name == "events") or page.type == "people": + output.speak(_(u"Filters cannot be applied on this buffer")) + return new_filter = filterController.filter(page) + def manage_filters(self, *args, **kwargs): + page = self.get_best_buffer() + manage_filters = filterController.filterManager(page.session) + def seekLeft(self, *args, **kwargs): try: sound.URLPlayer.seek(-5) diff --git a/src/mysc/thread_utils.py b/src/mysc/thread_utils.py index 92902a53..40c633b2 100644 --- a/src/mysc/thread_utils.py +++ b/src/mysc/thread_utils.py @@ -5,7 +5,6 @@ import threading import wx from pubsub import pub from twython import TwythonRateLimitError -import time def call_threaded(func, *args, **kwargs): #Call the given function in a daemonized thread and return the thread. diff --git a/src/sessionmanager/session.py b/src/sessionmanager/session.py index e6fc0769..688e0d1a 100644 --- a/src/sessionmanager/session.py +++ b/src/sessionmanager/session.py @@ -363,7 +363,6 @@ class Session(object): for z in i.users: ids += str(z) + ", " if ids != "": -# print ids stream_threaded(self.timelinesStream.statuses.filter, self.session_id, follow=ids) def add_friends(self): diff --git a/src/twitter/buffers/stream.py b/src/twitter/buffers/stream.py index 45fd77fa..25662dc1 100644 --- a/src/twitter/buffers/stream.py +++ b/src/twitter/buffers/stream.py @@ -27,21 +27,23 @@ class streamer(TwythonStreamer): def put_data(self, place, data): if self.session.db.has_key(place): - if utils.find_item(data["id"], self.session.db[place]) != None and utils.is_allowed(data, self.session.settings, place): + if utils.find_item(data["id"], self.session.db[place]) != None: log.error("duplicated tweet. Ignoring it...") return False # try: - data_ = self.session.check_quoted_status(data) - data_ = self.session.check_long_tweet(data_) - data = data_ + if utils.is_allowed(data, self.session.settings, place): + data_ = self.session.check_quoted_status(data) + data_ = self.session.check_long_tweet(data_) + data = data_ # except: # pass - if self.session.settings["general"]["reverse_timelines"] == False: - self.session.db[place].append(data) - else: - self.session.db[place].insert(0, data) - utils.is_audio(data) - return True + if self.session.settings["general"]["reverse_timelines"] == False: + self.session.db[place].append(data) + else: + self.session.db[place].insert(0, data) + utils.is_audio(data) + return True + return False def block_user(self, data): id = data["target"]["id"] diff --git a/src/twitter/utils.py b/src/twitter/utils.py index 04d235a8..be3f1f73 100644 --- a/src/twitter/utils.py +++ b/src/twitter/utils.py @@ -140,15 +140,19 @@ def is_allowed(tweet, settings, buffer_name): return filter_tweet(tweet, settings, buffer_name) def filter_tweet(tweet, settings, buffer_name): + if tweet.has_key("full_text"): + value = "full_text" + else: + value = "text" for i in settings["filters"]: if settings["filters"][i]["in_buffer"] == buffer_name: regexp = settings["filters"][i]["regexp"] word = settings["filters"][i]["word"] if word != "" and settings["filters"][i]["if_word_exists"]: - if word not in tweet["full_text"]: + if word in tweet[value]: return False elif word != "" and settings["filters"][i]["if_word_exists"] == False: - if word in tweet["full_text"]: + if word not in tweet[value]: return False if settings["filters"][i]["in_lang"] == "True": if tweet["lang"] not in settings["filters"][i]["languages"]: diff --git a/src/wxUI/commonMessageDialogs.py b/src/wxUI/commonMessageDialogs.py index b643a3e6..539534d2 100644 --- a/src/wxUI/commonMessageDialogs.py +++ b/src/wxUI/commonMessageDialogs.py @@ -79,4 +79,10 @@ def blocked_timeline(): return wx.MessageDialog(None, _(u"You have been blocked from viewing someone's content. In order to avoid conflicts with the full session, TWBlue will remove the affected timeline."), _(u"Error"), wx.OK).ShowModal() def suspended_user(): - return wx.MessageDialog(None, _(u"TWBlue cannot load this timeline because the user has been suspended from Twitter."), _(u"Error"), wx.OK).ShowModal() \ No newline at end of file + return wx.MessageDialog(None, _(u"TWBlue cannot load this timeline because the user has been suspended from Twitter."), _(u"Error"), wx.OK).ShowModal() + +def delete_filter(): + return wx.MessageDialog(None, _(u"Do you really want to delete this filter?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO).ShowModal() + +def existing_filter(): + return wx.MessageDialog(None, _(u"This filter already exists. Please use a different title"), _(u"Error"), wx.OK).ShowModal() diff --git a/src/wxUI/dialogs/filterDialogs.py b/src/wxUI/dialogs/filterDialogs.py index 11ab5b22..16592620 100644 --- a/src/wxUI/dialogs/filterDialogs.py +++ b/src/wxUI/dialogs/filterDialogs.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import baseDialog import wx +import widgetUtils +from multiplatform_widgets import widgets class filterDialog(baseDialog.BaseWXDialog): def __init__(self, value="", languages=[]): @@ -9,28 +11,42 @@ class filterDialog(baseDialog.BaseWXDialog): panel = wx.Panel(self) sizer = wx.BoxSizer(wx.VERTICAL) self.SetTitle(_(u"Create a filter for this buffer")) - self.contains = wx.RadioButton(panel, -1, _(u"Contains"), style=wx.RB_GROUP) - self.doesnt_contain = wx.RadioButton(panel, -1, _(u"Doesn't contain")) - radioSizer1 = wx.BoxSizer(wx.HORIZONTAL) + label = wx.StaticText(panel, wx.NewId(), _(u"Filter title")) + self.title = wx.TextCtrl(panel, -1, value) + dc = wx.WindowDC(self.title) + dc.SetFont(self.title.GetFont()) + self.title.SetSize(dc.GetTextExtent("0"*40)) + tsizer = wx.BoxSizer(wx.HORIZONTAL) + tsizer.Add(label, 0, wx.ALL, 5) + tsizer.Add(self.title, 0, wx.ALL, 5) + sizer.Add(tsizer, 0, wx.ALL, 5) + staticbox = wx.StaticBox(panel, label=_(u"Filter by word")) + self.contains = wx.RadioButton(panel, -1, _(u"Ignore tweets wich contain the following word"), style=wx.RB_GROUP) + self.doesnt_contain = wx.RadioButton(panel, -1, _(u"Ignore tweets without the following word")) + radioSizer1 = wx.StaticBoxSizer(staticbox, wx.HORIZONTAL) radioSizer1.Add(self.contains, 0, wx.ALL, 5) radioSizer1.Add(self.doesnt_contain, 0, wx.ALL, 5) sizer.Add(radioSizer1, 0, wx.ALL, 5) label = wx.StaticText(panel, -1, _(u"word")) self.term = wx.TextCtrl(panel, -1, value) - dc = wx.WindowDC(self.contains) - dc.SetFont(self.contains.GetFont()) - self.contains.SetSize(dc.GetTextExtent("0"*40)) + dc = wx.WindowDC(self.term) + dc.SetFont(self.term.GetFont()) + self.term.SetSize(dc.GetTextExtent("0"*40)) bsizer = wx.BoxSizer(wx.HORIZONTAL) bsizer.Add(label, 0, wx.ALL, 5) - bsizer.Add(self.contains, 0, wx.ALL, 5) + bsizer.Add(self.term, 0, wx.ALL, 5) sizer.Add(bsizer, 0, wx.ALL, 5) self.regexp = wx.CheckBox(panel, wx.NewId(), _(u"Use this term as a regular expression")) sizer.Add(self.regexp, 0, wx.ALL, 5) + staticbox = wx.StaticBox(panel, label=_(u"Filter by language")) self.load_language = wx.RadioButton(panel, -1, _(u"Load tweets in the following languages"), style=wx.RB_GROUP) self.ignore_language = wx.RadioButton(panel, -1, _(u"Ignore tweets in the following languages")) self.skip_language_filtering = wx.RadioButton(panel, -1, _(u"Don't filter by language")) self.skip_language_filtering.SetValue(True) - radioSizer2 = wx.BoxSizer(wx.HORIZONTAL) + widgetUtils.connect_event(self.load_language, widgetUtils.RADIOBUTTON, self.show_language_options) + widgetUtils.connect_event(self.ignore_language, widgetUtils.RADIOBUTTON, self.show_language_options) + widgetUtils.connect_event(self.skip_language_filtering, widgetUtils.RADIOBUTTON, self.hide_language_options) + radioSizer2 = wx.StaticBoxSizer(staticbox, wx.HORIZONTAL) radioSizer2.Add(self.load_language, 0, wx.ALL, 5) radioSizer2.Add(self.ignore_language, 0, wx.ALL, 5) radioSizer2.Add(self.skip_language_filtering, 0, wx.ALL, 5) @@ -62,6 +78,7 @@ class filterDialog(baseDialog.BaseWXDialog): btnsizer.Add(cancel, 0, wx.ALL, 5) sizer.Add(btnsizer, 0, wx.ALL, 5) panel.SetSizer(sizer) + self.hide_language_options() self.SetClientSize(sizer.CalcMin()) def get_lang(self): @@ -81,3 +98,40 @@ class filterDialog(baseDialog.BaseWXDialog): def get_selected_langs(self): return self.indexes + + def hide_language_options(self, *args, **kwargs): + for i in [self.cb, self.add, self.langs, self.remove]: + i.Hide() + + def show_language_options(self, *args, **kwargs): + for i in [self.cb, self.add, self.langs, self.remove]: + i.Show() + +class filterManagerDialog(widgetUtils.BaseDialog): + + def __init__(self, *args, **kwargs): + super(filterManagerDialog, self).__init__(parent=None, *args, **kwargs) + self.SetTitle(_(u"Manage filters")) + panel = wx.Panel(self) + label = wx.StaticText(panel, -1, _(u"Filters")) + self.filters = widgets.list(panel, _(u"Filter"), _(u"Buffer"), _(u"Filter by word"), _(u"Filter by language"), size=(800, 800), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) + self.filters.list.SetFocus() + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(label) + sizer.Add(self.filters.list) + self.edit = wx.Button(panel, wx.NewId(), _(u"Edit")) + self.edit.Enable(False) + self.delete = wx.Button(panel, wx.NewId(), _(u"Remove")) + self.cancel = wx.Button(panel, wx.ID_CANCEL) + btnSizer = wx.BoxSizer() + btnSizer.Add(self.edit, 0, wx.ALL, 5) + btnSizer.Add(self.delete, 0, wx.ALL, 5) + btnSizer.Add(self.cancel, 0, wx.ALL, 5) + sizer.Add(btnSizer, 0, wx.ALL, 5) + panel.SetSizer(sizer) + + def get_item(self): + return self.filters.get_selected() + + def clear(self): + self.filters.clear() diff --git a/src/wxUI/view.py b/src/wxUI/view.py index 7a63339c..5b179fb9 100644 --- a/src/wxUI/view.py +++ b/src/wxUI/view.py @@ -50,7 +50,8 @@ class mainFrame(wx.Frame): buffer = wx.Menu() self.update_buffer = buffer.Append(wx.NewId(), _(u"&Update buffer")) self.trends = buffer.Append(wx.NewId(), _(u"New &trending topics buffer...")) - self.filter = buffer.Append(wx.NewId(), _(u"Create a filter")) + self.filter = buffer.Append(wx.NewId(), _(u"Create a &filter")) + self.manage_filters = buffer.Append(wx.NewId(), _(u"&Manage filters")) self.find = buffer.Append(wx.NewId(), _(u"Find a string in the currently focused buffer...")) self.load_previous_items = buffer.Append(wx.NewId(), _(u"&Load previous items")) buffer.AppendSeparator()