From 83c9db573e8d8237b9ef72e1ed287b0924cde899 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 8 Jul 2022 11:39:14 -0500 Subject: [PATCH 01/17] Added first stage of refactor for autocompletion users --- src/controller/buffers/mastodon/__init__.py | 2 + src/controller/buffers/mastodon/base.py | 141 +++++++++++++++++++ src/controller/settings.py | 8 +- src/extra/autocompletionUsers/__init__.py | 1 - src/extra/autocompletionUsers/manage.py | 16 ++- src/extra/autocompletionUsers/scan.py | 124 ++++++++++++++++ src/extra/autocompletionUsers/settings.py | 59 -------- src/extra/autocompletionUsers/wx_scan.py | 35 +++++ src/extra/autocompletionUsers/wx_settings.py | 27 ---- src/wxUI/dialogs/configuration.py | 9 +- 10 files changed, 326 insertions(+), 96 deletions(-) create mode 100644 src/controller/buffers/mastodon/__init__.py create mode 100644 src/controller/buffers/mastodon/base.py create mode 100644 src/extra/autocompletionUsers/scan.py delete mode 100644 src/extra/autocompletionUsers/settings.py create mode 100644 src/extra/autocompletionUsers/wx_scan.py delete mode 100644 src/extra/autocompletionUsers/wx_settings.py diff --git a/src/controller/buffers/mastodon/__init__.py b/src/controller/buffers/mastodon/__init__.py new file mode 100644 index 00000000..3f036c87 --- /dev/null +++ b/src/controller/buffers/mastodon/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from .base import BaseBuffer \ No newline at end of file diff --git a/src/controller/buffers/mastodon/base.py b/src/controller/buffers/mastodon/base.py new file mode 100644 index 00000000..178dbd10 --- /dev/null +++ b/src/controller/buffers/mastodon/base.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" Common logic to all buffers in TWBlue.""" +import logging +import wx +import output +import sound +import widgetUtils + +log = logging.getLogger("controller.buffers.base.base") + +class Buffer(object): + """ A basic buffer object. This should be the base class for all other derived buffers.""" + + def __init__(self, parent=None, function=None, session=None, *args, **kwargs): + """Inits the main controller for this buffer: + @ parent wx.Treebook object: Container where we will put this buffer. + @ function str or None: function to be called periodically and update items on this buffer. + @ session sessionmanager.session object or None: Session handler for settings, database and data access. + """ + super(Buffer, self).__init__() + self.function = function + # Compose_function will be used to render an object on this buffer. Normally, signature is as follows: + # compose_function(item, db, relative_times, show_screen_names=False, session=None) + # Read more about compose functions in sessions/twitter/compose.py. + self.compose_function = None + self.args = args + self.kwargs = kwargs + # This will be used as a reference to the wx.Panel object wich stores the buffer GUI. + self.buffer = None + # This should countains the account associated to this buffer. + self.account = "" + # This controls whether the start_stream function should be called when starting the program. + self.needs_init = True + # if this is set to False, the buffer will be ignored on the invisible interface. + self.invisible = False + # Control variable, used to track time of execution for calls to start_stream. + self.execution_time = 0 + + def clear_list(self): + pass + + def get_event(self, ev): + """ Catch key presses in the WX interface and generate the corresponding event names.""" + if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio" + elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url" + elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down" + elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up" + elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list" + elif ev.GetKeyCode() == wx.WXK_DELETE: event = "destroy_status" + # Raise a Special event when pressed Shift+F10 because Wx==4.1.x does not seems to trigger this by itself. + # See https://github.com/manuelcortez/TWBlue/issues/353 + elif ev.GetKeyCode() == wx.WXK_F10 and ev.ShiftDown(): event = "show_menu" + else: + event = None + ev.Skip() + if event != None: + try: + ### ToDo: Remove after WX fixes issue #353 in the widgets. + if event == "show_menu": + return self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition()) + getattr(self, event)() + except AttributeError: + pass + + def volume_down(self): + """ Decreases volume by 5%""" + if self.session.settings["sound"]["volume"] > 0.0: + if self.session.settings["sound"]["volume"] <= 0.05: + self.session.settings["sound"]["volume"] = 0.0 + else: + self.session.settings["sound"]["volume"] -=0.05 + sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0)) + self.session.sound.play("volume_changed.ogg") + self.session.settings.write() + + def volume_up(self): + """ Increases volume by 5%.""" + if self.session.settings["sound"]["volume"] < 1.0: + if self.session.settings["sound"]["volume"] >= 0.95: + self.session.settings["sound"]["volume"] = 1.0 + else: + self.session.settings["sound"]["volume"] +=0.05 + sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100)) + self.session.sound.play("volume_changed.ogg") + self.session.settings.write() + + def start_stream(self, mandatory=False, play_sound=True): + pass + + def get_more_items(self): + output.speak(_(u"This action is not supported for this buffer"), True) + + def put_items_on_list(self, items): + pass + + def remove_buffer(self): + return False + + def remove_item(self, item): + f = self.buffer.list.get_selected() + self.buffer.list.remove_item(item) + self.buffer.list.select_item(f) + + def bind_events(self): + pass + + def get_object(self): + return self.buffer + + def get_message(self): + pass + + def set_list_position(self, reversed=False): + if reversed == False: + self.buffer.list.select_item(-1) + else: + self.buffer.list.select_item(0) + + def reply(self): + pass + + def send_message(self): + pass + + def share_item(self): + pass + + def can_share(self): + pass + + def destroy_status(self): + pass + + def post_status(self, *args, **kwargs): + pass + + def save_positions(self): + try: + self.session.db[self.name+"_pos"]=self.buffer.list.get_selected() + except AttributeError: + pass \ No newline at end of file diff --git a/src/controller/settings.py b/src/controller/settings.py index c4ab8d2f..c6d7f5cc 100644 --- a/src/controller/settings.py +++ b/src/controller/settings.py @@ -16,7 +16,7 @@ from pubsub import pub from mysc import autostart as autostart_windows from wxUI.dialogs import configuration from wxUI import commonMessageDialogs -from extra.autocompletionUsers import settings +from extra.autocompletionUsers import scan, manage from extra.ocr import OCRSpace from .editTemplateController import EditTemplate @@ -142,7 +142,7 @@ class accountSettingsController(globalSettingsController): def create_config(self): self.dialog.create_general_account() - widgetUtils.connect_event(self.dialog.general.au, widgetUtils.BUTTON_PRESSED, self.manage_autocomplete) + widgetUtils.connect_event(self.dialog.general.userAutocompletionStart, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_start) self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"]) 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"]) @@ -304,8 +304,8 @@ class accountSettingsController(globalSettingsController): def toggle_state(self,*args,**kwargs): return self.dialog.buffers.change_selected_item() - def manage_autocomplete(self, *args, **kwargs): - configuration = settings.autocompletionSettings(self.buffer.session.settings, self.buffer, self.window) + def on_autocompletion_start(self, *args, **kwargs): + configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window) def add_ignored_client(self, *args, **kwargs): client = commonMessageDialogs.get_ignored_client() diff --git a/src/extra/autocompletionUsers/__init__.py b/src/extra/autocompletionUsers/__init__.py index 475e8255..6b498407 100644 --- a/src/extra/autocompletionUsers/__init__.py +++ b/src/extra/autocompletionUsers/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- # -*- coding: utf-8 -*- """ Autocompletion users feature for TWBlue. This package contains all needed code to support this feature, including automatic addition of users, management and code to show the autocompletion menu when an user is composing a tweet. """ -from . import completion, settings diff --git a/src/extra/autocompletionUsers/manage.py b/src/extra/autocompletionUsers/manage.py index a1acd603..729c1bfb 100644 --- a/src/extra/autocompletionUsers/manage.py +++ b/src/extra/autocompletionUsers/manage.py @@ -1,15 +1,25 @@ # -*- coding: utf-8 -*- +""" Management of users in the local database for autocompletion. """ +import time import widgetUtils +from tweepy.cursor import Cursor from tweepy.errors import TweepyException from . import storage, wx_manage from wxUI import commonMessageDialogs -class autocompletionManage(object): +class autocompletionManager(object): def __init__(self, session): - super(autocompletionManage, self).__init__() + """ class constructor. Manages everything related to user autocompletion. + + :param session: Sessiom where the autocompletion management has been requested. + :type session: sessions.base.Session. + """ + super(autocompletionManager, self).__init__() self.session = session - self.dialog = wx_manage.autocompletionManageDialog() self.database = storage.storage(self.session.session_id) + + def show_settings(self): + self.dialog = wx_manage.autocompletionManageDialog() self.users = self.database.get_all_users() self.dialog.put_users(self.users) widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add_user) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py new file mode 100644 index 00000000..aaa72daf --- /dev/null +++ b/src/extra/autocompletionUsers/scan.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +import time +import widgetUtils +import output +from tweepy.cursor import Cursor +from tweepy.errors import TweepyException +from pubsub import pub +from . import wx_scan +from . import manage +from . import storage +from mysc.thread_utils import call_threaded + +class autocompletionScan(object): + def __init__(self, config, buffer, window): + super(autocompletionScan, self).__init__() + self.config = config + self.buffer = buffer + self.window = window + self.dialog = wx_scan.autocompletionScanDialog() + self.dialog.set("friends", self.config["mysc"]["save_friends_in_autocompletion_db"]) + self.dialog.set("followers", self.config["mysc"]["save_followers_in_autocompletion_db"]) + if self.dialog.get_response() == widgetUtils.OK: + confirmation = wx_scan.confirm() + if confirmation == True: + self.progress_dialog = wx_scan.get_progress_dialog() + call_threaded(self.scan) + # connect method to update progress dialog + pub.subscribe(self.on_update_progress, "on-update-progress") + + def on_update_progress(self, percent): + print(percent) + if percent > 100: + percent = 100 + self.progress_dialog.Update(percent) + + def scan(self): + """ Attempts to add all users selected by current user to the autocomplete database. """ + ids = [] + self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends") + self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers") + output.speak(_("Updating database... You can close this window now. A message will tell you when the process finishes.")) + database = storage.storage(self.buffer.session.session_id) + total_steps = 0 + if self.dialog.get("friends") == True: + total_steps = total_steps + 2 + if self.dialog.get("followers") == True: + total_steps = total_steps + 2 + max_per_stage = 100/total_steps + percent = 0 + # Retrieve ids of all following users + if self.dialog.get("friends") == True: + for i in Cursor(self.buffer.session.twitter.get_friend_ids, count=5000).items(): + if str(i) not in ids: + ids.append(str(i)) + percent = percent + (100*max_per_stage/100) + pub.sendMessage("on-update-progress", percent=percent) + # same step, but for followers. + if self.dialog.get("followers") == True: + for i in Cursor(self.buffer.session.twitter.get_follower_ids, count=5000).items(): + if str(i) not in ids: + ids.append(str(i)) + percent = percent + (100*max_per_stage/100) + pub.sendMessage("on-update-progress", percent=percent) + # As next step requires batches of 100s users, let's split our user Ids so we won't break the param rules. + split_users = [ids[i:i + 100] for i in range(0, len(ids), 100)] + # store returned users in this list. + users = [] + for z in split_users: + if len(z) == 0: + print("Invalid user count") + continue + print(len(z)) + results = self.buffer.session.twitter.lookup_users(user_id=z) + users.extend(results) + time.sleep(1) + percent = percent + (max_per_stage/len(split_users)) + pub.sendMessage("on-update-progress", percent=percent) + for user in users: + database.set_user(user.screen_name, user.name, 1) + self.progress_dialog.Destroy() + wx_scan.show_success_dialog() + self.dialog.Destroy() + + def add_users_to_database(self): + self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer") + self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer") + output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes.")) + database = storage.storage(self.buffer.session.session_id) + if self.dialog.get("followers_buffer") == True: + buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 1) + else: + database.remove_by_buffer(1) + if self.dialog.get("friends_buffer") == True: + buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 2) + else: + database.remove_by_buffer(2) + wx_settings.show_success_dialog() + self.dialog.destroy() + + def view_list(self, ev): + q = manage.autocompletionManager(self.buffer.session) + q.show_settings() + +def execute_at_startup(window, buffer, config): + database = storage.storage(buffer.session.session_id) + if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True: + buffer = window.search_buffer("followers", config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 1) + else: + database.remove_by_buffer(1) + if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True: + buffer = window.search_buffer("friends", config["twitter"]["user_name"]) + for i in buffer.session.db[buffer.name]: + database.set_user(i.screen_name, i.name, 2) + else: + database.remove_by_buffer(2) + + def __del__(self): + pub.unsubscribe(self.on_update_progress, "on-update-progress") \ No newline at end of file diff --git a/src/extra/autocompletionUsers/settings.py b/src/extra/autocompletionUsers/settings.py deleted file mode 100644 index d875fb0d..00000000 --- a/src/extra/autocompletionUsers/settings.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -import widgetUtils -import output -from . import wx_settings -from . import manage -from . import storage -from mysc.thread_utils import call_threaded - -class autocompletionSettings(object): - def __init__(self, config, buffer, window): - super(autocompletionSettings, self).__init__() - self.config = config - self.buffer = buffer - self.window = window - self.dialog = wx_settings.autocompletionSettingsDialog() - self.dialog.set("friends_buffer", self.config["mysc"]["save_friends_in_autocompletion_db"]) - self.dialog.set("followers_buffer", self.config["mysc"]["save_followers_in_autocompletion_db"]) - widgetUtils.connect_event(self.dialog.viewList, widgetUtils.BUTTON_PRESSED, self.view_list) - if self.dialog.get_response() == widgetUtils.OK: - call_threaded(self.add_users_to_database) - - def add_users_to_database(self): - self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer") - self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer") - output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes.")) - database = storage.storage(self.buffer.session.session_id) - if self.dialog.get("followers_buffer") == True: - buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 1) - else: - database.remove_by_buffer(1) - if self.dialog.get("friends_buffer") == True: - buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 2) - else: - database.remove_by_buffer(2) - wx_settings.show_success_dialog() - self.dialog.destroy() - - def view_list(self, ev): - q = manage.autocompletionManage(self.buffer.session) - - -def execute_at_startup(window, buffer, config): - database = storage.storage(buffer.session.session_id) - if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True: - buffer = window.search_buffer("followers", config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 1) - else: - database.remove_by_buffer(1) - if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True: - buffer = window.search_buffer("friends", config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 2) - else: - database.remove_by_buffer(2) diff --git a/src/extra/autocompletionUsers/wx_scan.py b/src/extra/autocompletionUsers/wx_scan.py new file mode 100644 index 00000000..a9554572 --- /dev/null +++ b/src/extra/autocompletionUsers/wx_scan.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +import wx +import widgetUtils +import application + +class autocompletionScanDialog(widgetUtils.BaseDialog): + def __init__(self): + super(autocompletionScanDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings")) + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + self.followers = wx.CheckBox(panel, -1, _("Add followers to the database")) + self.friends = wx.CheckBox(panel, -1, _("Add friends to database")) + sizer.Add(self.followers, 0, wx.ALL, 5) + sizer.Add(self.friends, 0, wx.ALL, 5) + ok = wx.Button(panel, wx.ID_OK) + cancel = wx.Button(panel, wx.ID_CANCEL) + sizerBtn = wx.BoxSizer(wx.HORIZONTAL) + sizerBtn.Add(ok, 0, wx.ALL, 5) + sizer.Add(cancel, 0, wx.ALL, 5) + sizer.Add(sizerBtn, 0, wx.ALL, 5) + panel.SetSizer(sizer) + self.SetClientSize(sizer.CalcMin()) + +def show_success_dialog(): + with wx.MessageDialog(None, _("{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK)as dlg: + dlg.ShowModal() + +def confirm(): + with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result: + if result.ShowModal() == wx.ID_YES: + return True + return False + +def get_progress_dialog(): + return wx.ProgressDialog(_("Retrieving Twitter users from account..."), _("working..."), parent=None, maximum=100) diff --git a/src/extra/autocompletionUsers/wx_settings.py b/src/extra/autocompletionUsers/wx_settings.py deleted file mode 100644 index 3d635935..00000000 --- a/src/extra/autocompletionUsers/wx_settings.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -import wx -import widgetUtils -import application - -class autocompletionSettingsDialog(widgetUtils.BaseDialog): - def __init__(self): - super(autocompletionSettingsDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings")) - panel = wx.Panel(self) - sizer = wx.BoxSizer(wx.VERTICAL) - self.followers_buffer = wx.CheckBox(panel, -1, _(u"Add users from followers buffer")) - self.friends_buffer = wx.CheckBox(panel, -1, _(u"Add users from friends buffer")) - sizer.Add(self.followers_buffer, 0, wx.ALL, 5) - sizer.Add(self.friends_buffer, 0, wx.ALL, 5) - self.viewList = wx.Button(panel, -1, _(u"Manage database...")) - sizer.Add(self.viewList, 0, wx.ALL, 5) - ok = wx.Button(panel, wx.ID_OK) - cancel = wx.Button(panel, wx.ID_CANCEL) - sizerBtn = wx.BoxSizer(wx.HORIZONTAL) - sizerBtn.Add(ok, 0, wx.ALL, 5) - sizer.Add(cancel, 0, wx.ALL, 5) - sizer.Add(sizerBtn, 0, wx.ALL, 5) - panel.SetSizer(sizer) - self.SetClientSize(sizer.CalcMin()) - -def show_success_dialog(): - wx.MessageDialog(None, _(u"{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK).ShowModal() diff --git a/src/wxUI/dialogs/configuration.py b/src/wxUI/dialogs/configuration.py index ba5345c2..31405a66 100644 --- a/src/wxUI/dialogs/configuration.py +++ b/src/wxUI/dialogs/configuration.py @@ -99,8 +99,13 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog): def __init__(self, parent): super(generalAccount, self).__init__(parent) sizer = wx.BoxSizer(wx.VERTICAL) - self.au = wx.Button(self, wx.ID_ANY, _(u"Autocompletion settings...")) - sizer.Add(self.au, 0, wx.ALL, 5) + userAutocompletionBox = wx.StaticBox(self, label=_("User autocompletion settings")) + self.userAutocompletionStart = wx.Button(self, wx.ID_ANY, _("Scan account and add friends and followers to the user autocompletion database")) + self.userAutocompletionManage = wx.Button(self, wx.ID_ANY, _("Manage autocompletion database")) + autocompletionSizer = wx.StaticBoxSizer(userAutocompletionBox, wx.HORIZONTAL) + autocompletionSizer.Add(self.userAutocompletionStart, 0, wx.ALL, 5) + autocompletionSizer.Add(self.userAutocompletionManage, 0, wx.ALL, 5) + sizer.Add(autocompletionSizer, 0, wx.ALL, 5) self.relative_time = wx.CheckBox(self, wx.ID_ANY, _(U"Relative timestamps")) sizer.Add(self.relative_time, 0, wx.ALL, 5) tweetsPerCallBox = wx.BoxSizer(wx.HORIZONTAL) From 6e80b320c916cfe81599468fb7b39b009303b882 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 29 Jul 2022 12:08:05 -0500 Subject: [PATCH 02/17] Improved documentation in some classes --- src/extra/autocompletionUsers/__init__.py | 3 +-- src/extra/autocompletionUsers/completion.py | 2 +- src/extra/autocompletionUsers/manage.py | 11 +++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/extra/autocompletionUsers/__init__.py b/src/extra/autocompletionUsers/__init__.py index 6b498407..78fa88df 100644 --- a/src/extra/autocompletionUsers/__init__.py +++ b/src/extra/autocompletionUsers/__init__.py @@ -1,3 +1,2 @@ # -*- coding: utf-8 -*- -# -*- coding: utf-8 -*- -""" Autocompletion users feature for TWBlue. This package contains all needed code to support this feature, including automatic addition of users, management and code to show the autocompletion menu when an user is composing a tweet. """ +""" Autocompletion users for TWBlue. This package contains all needed code to support this feature, including automatic addition of users, management and code to show the autocompletion menu when an user is composing a tweet. """ diff --git a/src/extra/autocompletionUsers/completion.py b/src/extra/autocompletionUsers/completion.py index 47abfcbf..1c6750a3 100644 --- a/src/extra/autocompletionUsers/completion.py +++ b/src/extra/autocompletionUsers/completion.py @@ -8,7 +8,7 @@ class autocompletionUsers(object): def __init__(self, window, session_id): """ Class constructor. Displays a menu with users matching the specified pattern for autocompletion. - :param window: A wx control where the menu should be displayed. + :param window: A wx control where the menu should be displayed. Normally this is going to be the wx.TextCtrl indicating the tweet's text or direct message recipient. :type window: wx.Dialog :param session_id: Session ID which calls this class. We will load the users database from this session. :type session_id: str. diff --git a/src/extra/autocompletionUsers/manage.py b/src/extra/autocompletionUsers/manage.py index 729c1bfb..c9398a4f 100644 --- a/src/extra/autocompletionUsers/manage.py +++ b/src/extra/autocompletionUsers/manage.py @@ -4,8 +4,8 @@ import time import widgetUtils from tweepy.cursor import Cursor from tweepy.errors import TweepyException -from . import storage, wx_manage from wxUI import commonMessageDialogs +from . import storage, wx_manage class autocompletionManager(object): def __init__(self, session): @@ -16,9 +16,11 @@ class autocompletionManager(object): """ super(autocompletionManager, self).__init__() self.session = session + # Instantiate database so we can perform modifications on it. self.database = storage.storage(self.session.session_id) def show_settings(self): + """ display user management dialog and connect events associated to it. """ self.dialog = wx_manage.autocompletionManageDialog() self.users = self.database.get_all_users() self.dialog.put_users(self.users) @@ -27,6 +29,7 @@ class autocompletionManager(object): self.dialog.get_response() def update_list(self): + """ update users list in management dialog. This function is normallhy used after we modify the database in any way, so we can reload all users in the autocompletion user management list. """ item = self.dialog.users.get_selected() self.dialog.users.clear() self.users = self.database.get_all_users() @@ -34,9 +37,12 @@ class autocompletionManager(object): self.dialog.users.select_item(item) def add_user(self, *args, **kwargs): + """ Add a new Twitter username to the autocompletion database. """ usr = self.dialog.get_user() if usr == False: return + # check if user exists. + # ToDo: in case we want to adapt this for other networks we'd need to refactor this check. try: data = self.session.twitter.get_user(screen_name=usr) except TweepyException as e: @@ -46,7 +52,8 @@ class autocompletionManager(object): self.database.set_user(data.screen_name, data.name, 0) self.update_list() - def remove_user(self, ev): + def remove_user(self, *args, **kwargs): + """ Remove focused user from the autocompletion database. """ if commonMessageDialogs.delete_user_from_db() == widgetUtils.YES: item = self.dialog.users.get_selected() user = self.users[item] From 02e1793d087e150ff472e91778f223a33e8261cf Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 29 Jul 2022 12:11:03 -0500 Subject: [PATCH 03/17] cleaned scan controller and made progress dialog to work --- src/extra/autocompletionUsers/scan.py | 70 +++++++++------------------ 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py index aaa72daf..8decea35 100644 --- a/src/extra/autocompletionUsers/scan.py +++ b/src/extra/autocompletionUsers/scan.py @@ -1,17 +1,28 @@ # -*- coding: utf-8 -*- +""" Scanning code for autocompletion feature on TWBlue. This module can retrieve user objects from the selected Twitter account automatically. """ import time +import wx import widgetUtils import output from tweepy.cursor import Cursor from tweepy.errors import TweepyException from pubsub import pub +from mysc.thread_utils import call_threaded from . import wx_scan from . import manage from . import storage -from mysc.thread_utils import call_threaded class autocompletionScan(object): def __init__(self, config, buffer, window): + """ Class constructor. This class will take care of scanning the selected Twitter account to populate the database with users automatically upon request. + + :param config: Config for the session that will be scanned in search for users. + :type config: dict + :param buffer: home buffer for the focused session. + :type buffer: controller.buffers.twitter.base.baseBuffer + :param window: Main Window of TWBlue. + :type window:wx.Frame + """ super(autocompletionScan, self).__init__() self.config = config self.buffer = buffer @@ -22,16 +33,16 @@ class autocompletionScan(object): if self.dialog.get_response() == widgetUtils.OK: confirmation = wx_scan.confirm() if confirmation == True: - self.progress_dialog = wx_scan.get_progress_dialog() - call_threaded(self.scan) - # connect method to update progress dialog - pub.subscribe(self.on_update_progress, "on-update-progress") + self.progress_dialog = wx_scan.get_progress_dialog(parent=self.dialog) + # connect method to update progress dialog + pub.subscribe(self.on_update_progress, "on-update-progress") + scanner = call_threaded(self.scan) def on_update_progress(self, percent): print(percent) if percent > 100: percent = 100 - self.progress_dialog.Update(percent) + wx.CallAfter(self.progress_dialog.Update, percent) def scan(self): """ Attempts to add all users selected by current user to the autocomplete database. """ @@ -40,27 +51,17 @@ class autocompletionScan(object): self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers") output.speak(_("Updating database... You can close this window now. A message will tell you when the process finishes.")) database = storage.storage(self.buffer.session.session_id) - total_steps = 0 - if self.dialog.get("friends") == True: - total_steps = total_steps + 2 - if self.dialog.get("followers") == True: - total_steps = total_steps + 2 - max_per_stage = 100/total_steps percent = 0 # Retrieve ids of all following users if self.dialog.get("friends") == True: for i in Cursor(self.buffer.session.twitter.get_friend_ids, count=5000).items(): if str(i) not in ids: ids.append(str(i)) - percent = percent + (100*max_per_stage/100) - pub.sendMessage("on-update-progress", percent=percent) # same step, but for followers. if self.dialog.get("followers") == True: for i in Cursor(self.buffer.session.twitter.get_follower_ids, count=5000).items(): if str(i) not in ids: ids.append(str(i)) - percent = percent + (100*max_per_stage/100) - pub.sendMessage("on-update-progress", percent=percent) # As next step requires batches of 100s users, let's split our user Ids so we won't break the param rules. split_users = [ids[i:i + 100] for i in range(0, len(ids), 100)] # store returned users in this list. @@ -73,37 +74,13 @@ class autocompletionScan(object): results = self.buffer.session.twitter.lookup_users(user_id=z) users.extend(results) time.sleep(1) - percent = percent + (max_per_stage/len(split_users)) + percent = percent + (100/len(split_users)) pub.sendMessage("on-update-progress", percent=percent) for user in users: database.set_user(user.screen_name, user.name, 1) - self.progress_dialog.Destroy() - wx_scan.show_success_dialog() - self.dialog.Destroy() - - def add_users_to_database(self): - self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer") - self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer") - output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes.")) - database = storage.storage(self.buffer.session.session_id) - if self.dialog.get("followers_buffer") == True: - buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 1) - else: - database.remove_by_buffer(1) - if self.dialog.get("friends_buffer") == True: - buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"]) - for i in buffer.session.db[buffer.name]: - database.set_user(i.screen_name, i.name, 2) - else: - database.remove_by_buffer(2) - wx_settings.show_success_dialog() - self.dialog.destroy() - - def view_list(self, ev): - q = manage.autocompletionManager(self.buffer.session) - q.show_settings() + wx.CallAfter(self.progress_dialog.Destroy) + wx.CallAfter(self.dialog.Destroy) + pub.unsubscribe(self.on_update_progress, "on-update-progress") def execute_at_startup(window, buffer, config): database = storage.storage(buffer.session.session_id) @@ -118,7 +95,4 @@ def execute_at_startup(window, buffer, config): for i in buffer.session.db[buffer.name]: database.set_user(i.screen_name, i.name, 2) else: - database.remove_by_buffer(2) - - def __del__(self): - pub.unsubscribe(self.on_update_progress, "on-update-progress") \ No newline at end of file + database.remove_by_buffer(2) \ No newline at end of file From 654b34c8e11cfc12e9d659f8defbe677351b225c Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 29 Jul 2022 12:11:27 -0500 Subject: [PATCH 04/17] Cleaned some unneeded dialogs --- src/extra/autocompletionUsers/wx_scan.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/extra/autocompletionUsers/wx_scan.py b/src/extra/autocompletionUsers/wx_scan.py index a9554572..24255174 100644 --- a/src/extra/autocompletionUsers/wx_scan.py +++ b/src/extra/autocompletionUsers/wx_scan.py @@ -21,15 +21,11 @@ class autocompletionScanDialog(widgetUtils.BaseDialog): panel.SetSizer(sizer) self.SetClientSize(sizer.CalcMin()) -def show_success_dialog(): - with wx.MessageDialog(None, _("{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK)as dlg: - dlg.ShowModal() - def confirm(): - with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result: + with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. If this process finish with no error, you will be redirected back to the account settings dialog. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result: if result.ShowModal() == wx.ID_YES: return True return False -def get_progress_dialog(): - return wx.ProgressDialog(_("Retrieving Twitter users from account..."), _("working..."), parent=None, maximum=100) +def get_progress_dialog(parent=None): + return wx.ProgressDialog(_("Retrieving Twitter users from account..."), _("working..."), parent=parent, maximum=100, style=wx.PD_APP_MODAL) From 6fcd0274a9a99825ee3a05ca0fd7f3805fec51e8 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 29 Jul 2022 13:30:43 -0500 Subject: [PATCH 05/17] Added exception handling to scan for users --- src/extra/autocompletionUsers/scan.py | 22 +++++++++++++++------- src/extra/autocompletionUsers/wx_scan.py | 7 ++++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py index 8decea35..c699e27a 100644 --- a/src/extra/autocompletionUsers/scan.py +++ b/src/extra/autocompletionUsers/scan.py @@ -39,7 +39,6 @@ class autocompletionScan(object): scanner = call_threaded(self.scan) def on_update_progress(self, percent): - print(percent) if percent > 100: percent = 100 wx.CallAfter(self.progress_dialog.Update, percent) @@ -59,25 +58,34 @@ class autocompletionScan(object): ids.append(str(i)) # same step, but for followers. if self.dialog.get("followers") == True: - for i in Cursor(self.buffer.session.twitter.get_follower_ids, count=5000).items(): - if str(i) not in ids: - ids.append(str(i)) + try: + for i in Cursor(self.buffer.session.twitter.get_follower_ids, count=5000).items(): + if str(i) not in ids: + ids.append(str(i)) + except TweepyException: + wx.CallAfter(wx_scan.show_error) + self.done() # As next step requires batches of 100s users, let's split our user Ids so we won't break the param rules. split_users = [ids[i:i + 100] for i in range(0, len(ids), 100)] # store returned users in this list. users = [] for z in split_users: if len(z) == 0: - print("Invalid user count") continue - print(len(z)) - results = self.buffer.session.twitter.lookup_users(user_id=z) + try: + results = selff.buffer.session.twitter.lookup_users(user_id=z) + except TweepyException: + wx.CallAfter(wx_scan.show_error) + self.done() users.extend(results) time.sleep(1) percent = percent + (100/len(split_users)) pub.sendMessage("on-update-progress", percent=percent) for user in users: database.set_user(user.screen_name, user.name, 1) + self.done() + + def done(self): wx.CallAfter(self.progress_dialog.Destroy) wx.CallAfter(self.dialog.Destroy) pub.unsubscribe(self.on_update_progress, "on-update-progress") diff --git a/src/extra/autocompletionUsers/wx_scan.py b/src/extra/autocompletionUsers/wx_scan.py index 24255174..f7c2b3eb 100644 --- a/src/extra/autocompletionUsers/wx_scan.py +++ b/src/extra/autocompletionUsers/wx_scan.py @@ -22,10 +22,15 @@ class autocompletionScanDialog(widgetUtils.BaseDialog): self.SetClientSize(sizer.CalcMin()) def confirm(): - with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. If this process finish with no error, you will be redirected back to the account settings dialog. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result: + with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. If this process ends with no error, you will be redirected back to the account settings dialog. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result: if result.ShowModal() == wx.ID_YES: return True return False def get_progress_dialog(parent=None): return wx.ProgressDialog(_("Retrieving Twitter users from account..."), _("working..."), parent=parent, maximum=100, style=wx.PD_APP_MODAL) + +def show_error(): + dlg = wx.MessageDialog(None, _("Error adding users from Twitter. Please try again in about 15 minutes."), _("Error"), style=wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() \ No newline at end of file From f01ad5abb72a624eb4a4e9aa0cfc82f5d2009c92 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 29 Jul 2022 17:27:48 -0500 Subject: [PATCH 06/17] Return on exception --- src/extra/autocompletionUsers/scan.py | 12 +++++++----- src/extra/autocompletionUsers/wx_scan.py | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py index c699e27a..998c5b7c 100644 --- a/src/extra/autocompletionUsers/scan.py +++ b/src/extra/autocompletionUsers/scan.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- """ Scanning code for autocompletion feature on TWBlue. This module can retrieve user objects from the selected Twitter account automatically. """ import time +import threading import wx import widgetUtils import output from tweepy.cursor import Cursor from tweepy.errors import TweepyException from pubsub import pub -from mysc.thread_utils import call_threaded from . import wx_scan from . import manage from . import storage @@ -36,7 +36,8 @@ class autocompletionScan(object): self.progress_dialog = wx_scan.get_progress_dialog(parent=self.dialog) # connect method to update progress dialog pub.subscribe(self.on_update_progress, "on-update-progress") - scanner = call_threaded(self.scan) + scanner = threading.Thread(target=self.scan) + scanner.start() def on_update_progress(self, percent): if percent > 100: @@ -64,7 +65,7 @@ class autocompletionScan(object): ids.append(str(i)) except TweepyException: wx.CallAfter(wx_scan.show_error) - self.done() + return self.done() # As next step requires batches of 100s users, let's split our user Ids so we won't break the param rules. split_users = [ids[i:i + 100] for i in range(0, len(ids), 100)] # store returned users in this list. @@ -74,9 +75,10 @@ class autocompletionScan(object): continue try: results = selff.buffer.session.twitter.lookup_users(user_id=z) - except TweepyException: + except NameError: wx.CallAfter(wx_scan.show_error) - self.done() + wx.CallAfter(self.dialog.SetFocus) + return self.done() users.extend(results) time.sleep(1) percent = percent + (100/len(split_users)) diff --git a/src/extra/autocompletionUsers/wx_scan.py b/src/extra/autocompletionUsers/wx_scan.py index f7c2b3eb..653f5985 100644 --- a/src/extra/autocompletionUsers/wx_scan.py +++ b/src/extra/autocompletionUsers/wx_scan.py @@ -31,6 +31,5 @@ def get_progress_dialog(parent=None): return wx.ProgressDialog(_("Retrieving Twitter users from account..."), _("working..."), parent=parent, maximum=100, style=wx.PD_APP_MODAL) def show_error(): - dlg = wx.MessageDialog(None, _("Error adding users from Twitter. Please try again in about 15 minutes."), _("Error"), style=wx.ICON_ERROR) - dlg.ShowModal() - dlg.Destroy() \ No newline at end of file + with wx.MessageDialog(None, _("Error adding users from Twitter. Please try again in about 15 minutes."), _("Error"), style=wx.ICON_ERROR) as dlg: + dlg.ShowModal() \ No newline at end of file From f9f7a32f90a5fb3a1a08c434572bb2223a5c57b6 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 29 Jul 2022 17:37:28 -0500 Subject: [PATCH 07/17] Bind manage button with code to manage autocompletion db --- src/controller/settings.py | 9 +++++++-- src/extra/autocompletionUsers/manage.py | 4 ++-- src/wxUI/dialogs/configuration.py | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/controller/settings.py b/src/controller/settings.py index c6d7f5cc..4343070d 100644 --- a/src/controller/settings.py +++ b/src/controller/settings.py @@ -142,7 +142,8 @@ class accountSettingsController(globalSettingsController): def create_config(self): self.dialog.create_general_account() - widgetUtils.connect_event(self.dialog.general.userAutocompletionStart, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_start) + 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", "show_screen_names", self.config["general"]["show_screen_names"]) self.dialog.set_value("general", "hide_emojis", self.config["general"]["hide_emojis"]) @@ -304,9 +305,13 @@ class accountSettingsController(globalSettingsController): def toggle_state(self,*args,**kwargs): return self.dialog.buffers.change_selected_item() - def on_autocompletion_start(self, *args, **kwargs): + def on_autocompletion_scan(self, *args, **kwargs): configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window) + def on_autocompletion_manage(self, *args, **kwargs): + configuration = manage.autocompletionManage(self.buffer.session) + configuration.show_settings() + def add_ignored_client(self, *args, **kwargs): client = commonMessageDialogs.get_ignored_client() if client == None: return diff --git a/src/extra/autocompletionUsers/manage.py b/src/extra/autocompletionUsers/manage.py index c9398a4f..25884b04 100644 --- a/src/extra/autocompletionUsers/manage.py +++ b/src/extra/autocompletionUsers/manage.py @@ -7,14 +7,14 @@ from tweepy.errors import TweepyException from wxUI import commonMessageDialogs from . import storage, wx_manage -class autocompletionManager(object): +class autocompletionManage(object): def __init__(self, session): """ class constructor. Manages everything related to user autocompletion. :param session: Sessiom where the autocompletion management has been requested. :type session: sessions.base.Session. """ - super(autocompletionManager, self).__init__() + super(autocompletionManage, self).__init__() self.session = session # Instantiate database so we can perform modifications on it. self.database = storage.storage(self.session.session_id) diff --git a/src/wxUI/dialogs/configuration.py b/src/wxUI/dialogs/configuration.py index 31405a66..510be237 100644 --- a/src/wxUI/dialogs/configuration.py +++ b/src/wxUI/dialogs/configuration.py @@ -100,10 +100,10 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog): super(generalAccount, self).__init__(parent) sizer = wx.BoxSizer(wx.VERTICAL) userAutocompletionBox = wx.StaticBox(self, label=_("User autocompletion settings")) - self.userAutocompletionStart = wx.Button(self, wx.ID_ANY, _("Scan account and add friends and followers to the user autocompletion database")) + self.userAutocompletionScan = wx.Button(self, wx.ID_ANY, _("Scan account and add friends and followers to the user autocompletion database")) self.userAutocompletionManage = wx.Button(self, wx.ID_ANY, _("Manage autocompletion database")) autocompletionSizer = wx.StaticBoxSizer(userAutocompletionBox, wx.HORIZONTAL) - autocompletionSizer.Add(self.userAutocompletionStart, 0, wx.ALL, 5) + autocompletionSizer.Add(self.userAutocompletionScan, 0, wx.ALL, 5) autocompletionSizer.Add(self.userAutocompletionManage, 0, wx.ALL, 5) sizer.Add(autocompletionSizer, 0, wx.ALL, 5) self.relative_time = wx.CheckBox(self, wx.ID_ANY, _(U"Relative timestamps")) From 4b627a13ffd4b94f62747e54ee1de5cce02a32dd Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 29 Jul 2022 17:54:34 -0500 Subject: [PATCH 08/17] Start a small refactor in GUI code --- src/controller/settings.py | 8 ++++++++ src/extra/autocompletionUsers/scan.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/controller/settings.py b/src/controller/settings.py index 4343070d..1749f24e 100644 --- a/src/controller/settings.py +++ b/src/controller/settings.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import os import webbrowser +import threading import logging import sound_lib import paths @@ -307,6 +308,13 @@ class accountSettingsController(globalSettingsController): def on_autocompletion_scan(self, *args, **kwargs): configuration = scan.autocompletionScan(self.buffer.session.settings, self.buffer, self.window) + to_scan = configuration.show_dialog() + if to_scan == True: + configuration.prepare_progress_dialog() + t = threading.Thread(target=configuration.scan) + t.start() + + def on_autocompletion_manage(self, *args, **kwargs): configuration = manage.autocompletionManage(self.buffer.session) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py index 998c5b7c..0137c227 100644 --- a/src/extra/autocompletionUsers/scan.py +++ b/src/extra/autocompletionUsers/scan.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """ Scanning code for autocompletion feature on TWBlue. This module can retrieve user objects from the selected Twitter account automatically. """ import time -import threading import wx import widgetUtils import output @@ -27,17 +26,19 @@ class autocompletionScan(object): self.config = config self.buffer = buffer self.window = window + + def show_dialog(self): self.dialog = wx_scan.autocompletionScanDialog() self.dialog.set("friends", self.config["mysc"]["save_friends_in_autocompletion_db"]) self.dialog.set("followers", self.config["mysc"]["save_followers_in_autocompletion_db"]) if self.dialog.get_response() == widgetUtils.OK: confirmation = wx_scan.confirm() - if confirmation == True: - self.progress_dialog = wx_scan.get_progress_dialog(parent=self.dialog) - # connect method to update progress dialog - pub.subscribe(self.on_update_progress, "on-update-progress") - scanner = threading.Thread(target=self.scan) - scanner.start() + return confirmation + + def prepare_progress_dialog(self): + self.progress_dialog = wx_scan.get_progress_dialog(parent=self.dialog) + # connect method to update progress dialog + pub.subscribe(self.on_update_progress, "on-update-progress") def on_update_progress(self, percent): if percent > 100: @@ -77,7 +78,6 @@ class autocompletionScan(object): results = selff.buffer.session.twitter.lookup_users(user_id=z) except NameError: wx.CallAfter(wx_scan.show_error) - wx.CallAfter(self.dialog.SetFocus) return self.done() users.extend(results) time.sleep(1) From a1929ff1d37a44e14af10865fa38c380d09d0c0c Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Tue, 2 Aug 2022 09:06:46 -0500 Subject: [PATCH 09/17] Make sure Show() is called for progress dialog --- src/extra/autocompletionUsers/scan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py index 0137c227..aea7d615 100644 --- a/src/extra/autocompletionUsers/scan.py +++ b/src/extra/autocompletionUsers/scan.py @@ -39,6 +39,7 @@ class autocompletionScan(object): self.progress_dialog = wx_scan.get_progress_dialog(parent=self.dialog) # connect method to update progress dialog pub.subscribe(self.on_update_progress, "on-update-progress") + self.progress_dialog.Show() def on_update_progress(self, percent): if percent > 100: From 9afd6774f2ee7285e462d66ed883e8bd2a763e6e Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 09:37:18 -0500 Subject: [PATCH 10/17] Added a new and custom progress dialog for user scanning --- src/extra/autocompletionUsers/wx_scan.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/extra/autocompletionUsers/wx_scan.py b/src/extra/autocompletionUsers/wx_scan.py index 653f5985..bfee0cf6 100644 --- a/src/extra/autocompletionUsers/wx_scan.py +++ b/src/extra/autocompletionUsers/wx_scan.py @@ -8,7 +8,7 @@ class autocompletionScanDialog(widgetUtils.BaseDialog): super(autocompletionScanDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings")) panel = wx.Panel(self) sizer = wx.BoxSizer(wx.VERTICAL) - self.followers = wx.CheckBox(panel, -1, _("Add followers to the database")) + self.followers = wx.CheckBox(panel, -1, _("Add followers to database")) self.friends = wx.CheckBox(panel, -1, _("Add friends to database")) sizer.Add(self.followers, 0, wx.ALL, 5) sizer.Add(self.friends, 0, wx.ALL, 5) @@ -21,14 +21,27 @@ class autocompletionScanDialog(widgetUtils.BaseDialog): panel.SetSizer(sizer) self.SetClientSize(sizer.CalcMin()) +class autocompletionScanProgressDialog(widgetUtils.BaseDialog): + def __init__(self, *args, **kwargs): + super(autocompletionScanProgressDialog, self).__init__(parent=None, id=wx.ID_ANY, title=_("Updating autocompletion database"), *args, **kwargs) + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + self.progress_bar = wx.Gauge(parent=panel) + sizer.Add(self.progress_bar) + panel.SetSizerAndFit(sizer) + + def update(self, percent): + self.progress_bar.SetValue(percent) + def confirm(): with wx.MessageDialog(None, _("This process will retrieve the users you selected from Twitter, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in Twitter API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. If this process ends with no error, you will be redirected back to the account settings dialog. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result: if result.ShowModal() == wx.ID_YES: return True return False -def get_progress_dialog(parent=None): - return wx.ProgressDialog(_("Retrieving Twitter users from account..."), _("working..."), parent=parent, maximum=100, style=wx.PD_APP_MODAL) +def show_success(): + with wx.MessageDialog(None, _("TWBlue has imported {} users successfully."), _("Done")) as dlg: + dlg.ShowModal() def show_error(): with wx.MessageDialog(None, _("Error adding users from Twitter. Please try again in about 15 minutes."), _("Error"), style=wx.ICON_ERROR) as dlg: From 03f59064d5552fcfbeb2770593f97f747a089412 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 09:39:45 -0500 Subject: [PATCH 11/17] Report amount of imported users after scan --- src/extra/autocompletionUsers/scan.py | 9 +++++---- src/extra/autocompletionUsers/wx_scan.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/extra/autocompletionUsers/scan.py b/src/extra/autocompletionUsers/scan.py index aea7d615..8a39f2ea 100644 --- a/src/extra/autocompletionUsers/scan.py +++ b/src/extra/autocompletionUsers/scan.py @@ -36,7 +36,7 @@ class autocompletionScan(object): return confirmation def prepare_progress_dialog(self): - self.progress_dialog = wx_scan.get_progress_dialog(parent=self.dialog) + self.progress_dialog = wx_scan.autocompletionScanProgressDialog() # connect method to update progress dialog pub.subscribe(self.on_update_progress, "on-update-progress") self.progress_dialog.Show() @@ -44,7 +44,7 @@ class autocompletionScan(object): def on_update_progress(self, percent): if percent > 100: percent = 100 - wx.CallAfter(self.progress_dialog.Update, percent) + wx.CallAfter(self.progress_dialog.update, percent) def scan(self): """ Attempts to add all users selected by current user to the autocomplete database. """ @@ -76,8 +76,8 @@ class autocompletionScan(object): if len(z) == 0: continue try: - results = selff.buffer.session.twitter.lookup_users(user_id=z) - except NameError: + results = self.buffer.session.twitter.lookup_users(user_id=z) + except TweepyException: wx.CallAfter(wx_scan.show_error) return self.done() users.extend(results) @@ -86,6 +86,7 @@ class autocompletionScan(object): pub.sendMessage("on-update-progress", percent=percent) for user in users: database.set_user(user.screen_name, user.name, 1) + wx.CallAfter(wx_scan.show_success, len(users)) self.done() def done(self): diff --git a/src/extra/autocompletionUsers/wx_scan.py b/src/extra/autocompletionUsers/wx_scan.py index bfee0cf6..f62bfa76 100644 --- a/src/extra/autocompletionUsers/wx_scan.py +++ b/src/extra/autocompletionUsers/wx_scan.py @@ -39,8 +39,8 @@ def confirm(): return True return False -def show_success(): - with wx.MessageDialog(None, _("TWBlue has imported {} users successfully."), _("Done")) as dlg: +def show_success(users): + with wx.MessageDialog(None, _("TWBlue has imported {} users successfully.").format(users), _("Done")) as dlg: dlg.ShowModal() def show_error(): From b2da25dd613b485d81f5e56c2032410d57eefef0 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 09:52:09 -0500 Subject: [PATCH 12/17] Updated code on controllers for changes in user autocompletion module --- src/controller/messages.py | 7 ++++--- src/controller/userActionsController.py | 4 ++-- src/controller/userAliasController.py | 1 - 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controller/messages.py b/src/controller/messages.py index 9b93ed58..c87cbffe 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -11,8 +11,9 @@ from pubsub import pub from twitter_text import parse_tweet from wxUI.dialogs import twitterDialogs, urlList from wxUI import commonMessageDialogs -from extra import translator, SpellChecker, autocompletionUsers +from extra import translator, SpellChecker from extra.AudioUploader import audioUploader +from extra.autocompletionUsers import completion from sessions.twitter import utils class basicTweet(object): @@ -160,7 +161,7 @@ class tweet(basicTweet): self.text_processor() def autocomplete_users(self, *args, **kwargs): - c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) + c = completion.autocompletionUsers(self.message, self.session.session_id) c.show_menu() def add_tweet(self, event, update_gui=True, *args, **kwargs): @@ -269,7 +270,7 @@ class dm(basicTweet): self.text_processor() def autocomplete_users(self, *args, **kwargs): - c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) + c = completion.autocompletionUsers(self.message, self.session.session_id) c.show_menu("dm") def text_processor(self, *args, **kwargs): diff --git a/src/controller/userActionsController.py b/src/controller/userActionsController.py index e9cbcd45..7f1b71a3 100644 --- a/src/controller/userActionsController.py +++ b/src/controller/userActionsController.py @@ -4,7 +4,7 @@ import output from wxUI.dialogs import userActions from pubsub import pub from tweepy.errors import TweepyException -from extra import autocompletionUsers +from extra.autocompletionUsers import completion class userActionsController(object): def __init__(self, buffer, users=[], default="follow"): @@ -17,7 +17,7 @@ class userActionsController(object): self.process_action() def autocomplete_users(self, *args, **kwargs): - c = autocompletionUsers.completion.autocompletionUsers(self.dialog, self.session.session_id) + c = completion.autocompletionUsers(self.dialog, self.session.session_id) c.show_menu("dm") def process_action(self): diff --git a/src/controller/userAliasController.py b/src/controller/userAliasController.py index 69a4fa8f..f40396d5 100644 --- a/src/controller/userAliasController.py +++ b/src/controller/userAliasController.py @@ -2,7 +2,6 @@ import widgetUtils from pubsub import pub from wxUI.dialogs import userAliasDialogs -from extra import autocompletionUsers class userAliasController(object): def __init__(self, settings): From 769436abf771c795afd705b17cc12e2fa9cbce83 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 09:53:35 -0500 Subject: [PATCH 13/17] Updated menu to avoid using deprecated wx methods --- src/extra/autocompletionUsers/wx_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extra/autocompletionUsers/wx_menu.py b/src/extra/autocompletionUsers/wx_menu.py index 065ff2a1..69fe7fa6 100644 --- a/src/extra/autocompletionUsers/wx_menu.py +++ b/src/extra/autocompletionUsers/wx_menu.py @@ -11,7 +11,7 @@ class menu(wx.Menu): def append_options(self, options): for i in options: item = wx.MenuItem(self, wx.ID_ANY, "%s (@%s)" % (i[1], i[0])) - self.AppendItem(item) + self.Append(item) self.Bind(wx.EVT_MENU, lambda evt, temp=i[0]: self.select_text(evt, temp), item) def select_text(self, ev, text): From 015cf9eca3da88edb3cc8cb8eac899c57605d365 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 10:36:54 -0500 Subject: [PATCH 14/17] Fixed small typo --- src/extra/autocompletionUsers/completion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extra/autocompletionUsers/completion.py b/src/extra/autocompletionUsers/completion.py index 1c6750a3..088edbc3 100644 --- a/src/extra/autocompletionUsers/completion.py +++ b/src/extra/autocompletionUsers/completion.py @@ -29,8 +29,8 @@ class autocompletionUsers(object): :param mode: this controls how the dialog will behave. Possible values are 'tweet' and 'dm'. In tweet mode, the matching pattern will be @user (@ is required), while in 'dm' mode the matching pattern will be anything written in the text control. :type mode: str """ - position = self.window.text.GetInsertionPoint() if mode == "tweet": + position = self.window.text.GetInsertionPoint() text = self.window.text.GetValue() text = text[:position] try: @@ -60,7 +60,7 @@ class autocompletionUsers(object): users = self.db.get_users(pattern) if len(users) > 0: menu.append_options(users) - self.window.PopupMenu(menu, self.window.text.GetPosition()) + self.window.PopupMenu(menu, self.window.cb.GetPosition()) menu.destroy() else: output.speak(_(u"There are no results in your users database")) From aab8aafefc5131db5ccc8183b3a5a7b4e5693bb6 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 11:11:31 -0500 Subject: [PATCH 15/17] Introduced user selector controller for implementing user autocompletion in wxUI.dialogs.utils.selectUserDialog --- src/controller/userSelector.py | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/controller/userSelector.py diff --git a/src/controller/userSelector.py b/src/controller/userSelector.py new file mode 100644 index 00000000..b937795d --- /dev/null +++ b/src/controller/userSelector.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" Small utility dessigned to select users from the currently focused item or the autocomplete database. """ +import wx +import widgetUtils +from wxUI.dialogs import utils +from extra.autocompletionUsers import completion + +class userSelector(object): + + def __init__(self, users, session_id, title=_("Select user")): + """ Creates a dialog that chooses an user selector, from where users who have the autocomplete database already filled can also use that feature. + + :param users: lists of users extracted from the currently focused item. + :type users: list + :param session_id: ID of the session to instantiate autocomplete database. + :type session_id: str + :param title: Title of the user selector dialog. + :type title: str + """ + self.session_id = session_id + self.dlg = utils.selectUserDialog(users=users, title=title) + widgetUtils.connect_event(self.dlg.autocompletion, widgetUtils.BUTTON_PRESSED, self.on_autocomplete_users) + + def on_autocomplete_users(self, *args, **kwargs): + """ performs user autocompletion, if configured properly. """ + c = completion.autocompletionUsers(self.dlg, self.session_id) + c.show_menu("dm") + + def get_user(self): + """ Actually shows the dialog and returns an user if the dialog was accepted, None otherwise. + + :rtype: str or None + """ + if self.dlg.ShowModal() == wx.ID_OK: + user = self.dlg.get_user() + else: + user = None + self.dlg.Destroy() + return user From 76a5c960e56e628da3ad6a5f7c971ae99b74c35c Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 11:12:23 -0500 Subject: [PATCH 16/17] Added user autocompletion functionality to some dialogs. Closes #466 --- src/controller/mainController.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/controller/mainController.py b/src/controller/mainController.py index d97e8bd3..c796eb34 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -18,6 +18,7 @@ if system == "Windows": from . import user from . import listsController from . import filterController + from . import userSelector elif system == "Linux": from gtkUI import (view, commonMessageDialogs) from sessions.twitter import utils, compose @@ -518,10 +519,9 @@ class Controller(object): users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name] else: users = utils.get_all_users(tweet, buff.session) - dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users) - if dlg.get_response() == widgetUtils.OK: - user = dlg.get_user() - else: + selector = userSelector.userSelector(users=users, session_id=buff.session.session_id) + user = selector.get_user() + if user == None: return l = listsController.listsController(buff.session, user=user) @@ -535,10 +535,9 @@ class Controller(object): users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name] else: users = utils.get_all_users(tweet, buff.session) - dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users) - if dlg.get_response() == widgetUtils.OK: - user = dlg.get_user() - else: + selector = userSelector.userSelector(users=users, session_id=buff.session.session_id) + user = selector.get_user() + if user == None: return dlg = dialogs.lists.addUserListDialog() dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]]) @@ -564,10 +563,9 @@ class Controller(object): users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name] else: users = utils.get_all_users(tweet, buff.session) - dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users) - if dlg.get_response() == widgetUtils.OK: - user = dlg.get_user() - else: + selector = userSelector.userSelector(users=users, session_id=buff.session.session_id) + user = selector.get_user() + if user == None: return dlg = dialogs.lists.removeUserListDialog() dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]]) From 0f624c7dbec81802214cf54c61de17f72f4f7418 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Wed, 3 Aug 2022 11:24:26 -0500 Subject: [PATCH 17/17] Updated changelog --- doc/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/changelog.md b/doc/changelog.md index df7ece47..e55b8edb 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -2,6 +2,9 @@ TWBlue Changelog ## changes in this version +* the user autocompletion feature has been completely rewritten to be easier to use, particularly for people with many followers/following users: + * In the account settings dialog, there's a button that opens up a new dialog that allows you to "scan" your account in order to add all users from your followers/following list. This process will read your data directly from Twitter and depending in the amount of people you have in your account it might take too many API calls. Please use it with caution. You can, for example, do the process separately for your followers/following people so it will be easier to handle, in case you have a massive amount of people. If TWBlue is unable to complete the scan, you will see an error and will be prompted to try again in 15 minutes, once your API calls have refreshed. + * It is possible to use the user autocompletion functionality in dialogs where you can select an user, for example when adding or removing someone from a list, or displaying lists for someone. * Implemented a new setting, available in the account settings dialog, that allows to hide emojis in twitter usernames. * Fixed error when attempting to mention an user by using the "mention" button in any people buffer. Now tweets should be posted normally. * Fixed error when loading other user lists. ([#465](https://github.com/MCV-Software/TWBlue/issues/465))