diff --git a/src/interactors/postDisplayer.py b/src/interactors/postDisplayer.py index c49b455..dff2fe5 100644 --- a/src/interactors/postDisplayer.py +++ b/src/interactors/postDisplayer.py @@ -185,6 +185,31 @@ class displayAudioInteractor(base.baseInteractor): post = self.view.get_audio() self.presenter.remove_from_library(post) +class displayPollInteractor(base.baseInteractor): + + def set(self, control, value): + if not hasattr(self.view, control): + raise AttributeError("The control is not present in the view.") + getattr(self.view, control).SetValue(value) + + def done(self): + self.view.done() + + def add_options(self, options, multiple): + self.view.add_options(options, multiple) + + def install(self, *args, **kwargs): + super(displayPollInteractor, self).install(*args, **kwargs) + pub.subscribe(self.set, self.modulename+"_set") + pub.subscribe(self.done, self.modulename+"_done") + pub.subscribe(self.add_options, self.modulename+"_add_options") + + def uninstall(self): + super(displayPollInteractor, self).uninstall() + pub.unsubscribe(self.set, self.modulename+"_set") + pub.unsubscribe(self.done, self.modulename+"_done") + pub.unsubscribe(self.add_options, self.modulename+"_add_options") + class displayFriendshipInteractor(base.baseInteractor): def add_items(self, control, items): diff --git a/src/presenters/main.py b/src/presenters/main.py new file mode 100644 index 0000000..3d1af8d --- /dev/null +++ b/src/presenters/main.py @@ -0,0 +1,836 @@ +# -*- coding: utf-8 -*- +import time +import os +import webbrowser +import logging +import output +import interactors +import views +import config +from vk_api.exceptions import LoginRequired, VkApiError +from requests.exceptions import ConnectionError +from pubsub import pub +from mysc.repeating_timer import RepeatingTimer +from mysc.thread_utils import call_threaded +from mysc import localization +from sessionmanager import session, utils, renderers +from update import updater +from issueReporter import issueReporter +from controller import buffers, selector +from . import player, longpollthread +from . import base + +log = logging.getLogger("controller.main") + +class Controller(base.basePresenter): + + def search(self, tab_name): + for i in range(0, len(self.buffers)): + if self.buffers[i].name == tab_name: + return self.buffers[i] + return False + + def get_all_buffers(self, contains): + results = [] + for i in self.buffers: + if contains in i.name: + results.append(i) + return results + + def __init__(self, *args, **kwargs): + super(Controller, self).__init__(*args, **kwargs) + log.debug("Starting main controller...") + self.buffers = [] + player.setup() + self.session = session.sessions[list(session.sessions.keys())[0]] + self.create_controls() + call_threaded(updater.do_update, update_type=self.session.settings["general"]["update_channel"]) + + def create_buffer(self, buffer_type="baseBuffer", buffer_title="", parent_tab=None, loadable=False, get_items=False, kwargs={}): + if not hasattr(buffers, buffer_type): + raise AttributeError("Specified buffer type does not exist.") + buffer = getattr(buffers, buffer_type)(**kwargs) + if loadable: + buffer.can_get_items = False + self.buffers.append(buffer) + if parent_tab == None: + self.send_message("add_buffer", widget=buffer.tab, title=buffer_title) + else: + self.send_message("insert_buffer", widget=buffer.tab, title=buffer_title, parent=parent_tab) + if get_items: + call_threaded(buffer.get_items) + + def create_empty_buffer(self, buffer_type="empty", buffer_title="", parent_tab=None, kwargs={}): + if not hasattr(buffers, buffer_type): + raise AttributeError("Specified buffer type does not exist.") + buffer = getattr(buffers, buffer_type)(**kwargs) + self.buffers.append(buffer) + if parent_tab == None: + self.window.add_buffer(buffer.tab, buffer_title) + else: + self.window.insert_buffer(buffer.tab, buffer_title, self.window.search(parent_tab)) + + def create_controls(self): + log.debug("Creating controls for the window...") + pub.sendMessage("create_empty_buffer", buffer_title=_("Posts"), kwargs=dict(parent=self.window.tb, name="posts")) + pub.sendMessage("create_buffer", buffer_type="baseBuffer", buffer_title=_("Home"), parent_tab="posts", kwargs=dict(parent=self.window.tb, name="home_timeline", session=self.session, composefunc="render_newsfeed_item", endpoint="newsfeed", count=self.session.settings["buffers"]["count_for_wall_buffers"])) + pub.sendMessage("create_buffer", buffer_type="feedBuffer", buffer_title=_("My wall"), parent_tab="posts", kwargs=dict(parent=self.window.tb, name="me_feed", composefunc="render_status", session=self.session, endpoint="get", parent_endpoint="wall", extended=1, count=self.session.settings["buffers"]["count_for_wall_buffers"])) + pub.sendMessage("create_empty_buffer", buffer_title=_("Music"), kwargs=dict(parent=self.window.tb, name="audios")) + pub.sendMessage("create_buffer", buffer_type="audioBuffer", buffer_title=_("My audios"), parent_tab="audios", kwargs=dict(parent=self.window.tb, name="me_audio", composefunc="render_audio", session=self.session, endpoint="get", parent_endpoint="audio")) + if self.session.settings["vk"]["use_alternative_tokens"] == False: + pub.sendMessage("create_buffer", buffer_type="audioBuffer", buffer_title=_("Populars"), parent_tab="audios", kwargs=dict(parent=self.window.tb, name="popular_audio", composefunc="render_audio", session=self.session, endpoint="getPopular", parent_endpoint="audio", full_list=True, count=self.session.settings["buffers"]["count_for_audio_buffers"])) + pub.sendMessage("create_buffer", buffer_type="audioBuffer", buffer_title=_("Recommendations"), parent_tab="audios", kwargs=dict(parent=self.window.tb, name="recommended_audio", composefunc="render_audio", session=self.session, endpoint="getRecommendations", parent_endpoint="audio", full_list=True, count=self.session.settings["buffers"]["count_for_audio_buffers"])) + pub.sendMessage("create_empty_buffer", buffer_type="empty", buffer_title=_("Albums"), parent_tab="audios", kwargs=dict(parent=self.window.tb, name="albums")) + pub.sendMessage("create_empty_buffer", buffer_title=_("Video"), kwargs=dict(parent=self.window.tb, name="videos")) + pub.sendMessage("create_buffer", buffer_type="videoBuffer", buffer_title=_("My videos"), parent_tab="videos", kwargs=dict(parent=self.window.tb, name="me_video", composefunc="render_video", session=self.session, endpoint="get", parent_endpoint="video", count=self.session.settings["buffers"]["count_for_video_buffers"])) + pub.sendMessage("create_empty_buffer", buffer_title=_("Albums"), parent_tab="videos", kwargs=dict(parent=self.window.tb, name="video_albums")) + pub.sendMessage("create_empty_buffer", buffer_title=_("People"), kwargs=dict(parent=self.window.tb, name="people")) + pub.sendMessage("create_buffer", buffer_type="peopleBuffer", buffer_title=_("Friends"), parent_tab="people", kwargs=dict(parent=self.window.tb, name="friends_", composefunc="render_person", session=self.session, endpoint="get", parent_endpoint="friends", count=5000, order="hints", fields="uid, first_name, last_name, last_seen")) + pub.sendMessage("create_empty_buffer", buffer_title=_("Friendship requests"), parent_tab="people", kwargs=dict(parent=self.window.tb, name="requests")) + pub.sendMessage("create_buffer", buffer_type="requestsBuffer", buffer_title=_("Pending requests"), parent_tab="requests", kwargs=dict(parent=self.window.tb, name="friend_requests", composefunc="render_person", session=self.session, count=1000)) + pub.sendMessage("create_buffer", buffer_type="requestsBuffer", buffer_title=_("I follow"), parent_tab="requests", kwargs=dict(parent=self.window.tb, name="friend_requests_sent", composefunc="render_person", session=self.session, count=1000, out=1)) + pub.sendMessage("create_buffer", buffer_type="requestsBuffer", buffer_title=_("Subscribers"), parent_tab="requests", kwargs=dict(parent=self.window.tb, name="subscribers", composefunc="render_person", session=self.session, count=1000, need_viewed=1)) + pub.sendMessage("create_buffer", buffer_type="documentBuffer", buffer_title=_("Documents"), parent_tab=None, loadable=True, kwargs=dict(parent=self.window.tb, name="documents", composefunc="render_document", session=self.session, endpoint="get", parent_endpoint="docs")) + pub.sendMessage("create_empty_buffer", buffer_title=_("Communities"), kwargs=dict(parent=self.window.tb, name="communities")) + pub.sendMessage("create_empty_buffer", buffer_title=_("Chats"), kwargs=dict(parent=self.window.tb, name="chats")) + pub.sendMessage("create_empty_buffer", buffer_title=_("Timelines"), kwargs=dict(parent=self.window.tb, name="timelines")) + self.window.realize() + self.repeatedUpdate = RepeatingTimer(120, self.update_all_buffers) + self.repeatedUpdate.start() + self.readMarker = RepeatingTimer(60, self.mark_as_read) + self.readMarker.start() + + def connect_events(self): + log.debug("Connecting events to responses...") + pub.subscribe(self.in_post, "posted") + pub.subscribe(self.download, "download-file") + pub.subscribe(self.play_audio, "play-audio") + pub.subscribe(self.play_audios, "play-audios") + pub.subscribe(self.view_post, "open-post") + pub.subscribe(self.update_status_bar, "update-status-bar") + pub.subscribe(self.chat_from_id, "new-chat") + pub.subscribe(self.authorisation_failed, "authorisation-failed") + pub.subscribe(self.user_profile, "user-profile") + pub.subscribe(self.user_online, "user-online") + pub.subscribe(self.user_offline, "user-offline") + pub.subscribe(self.notify, "notify") + pub.subscribe(self.handle_longpoll_read_timeout, "longpoll-read-timeout") + pub.subscribe(self.create_buffer, "create_buffer") + pub.subscribe(self.create_empty_buffer, "create_empty_buffer") + pub.subscribe(self.user_typing, "user-typing") + widgetUtils.connect_event(self.window, widgetUtils.CLOSE_EVENT, self.exit) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.update_buffer, menuitem=self.window.update_buffer) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.check_for_updates, menuitem=self.window.check_for_updates) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.search_audios, menuitem=self.window.search_audios) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.search_videos, menuitem=self.window.search_videos) + widgetUtils.connect_event(self.window, widgetUtils.MENU,self.remove_buffer, menuitem=self.window.remove_buffer_) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.get_more_items, menuitem=self.window.load_previous_items) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.changelog, menuitem=self.window.changelog) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.configuration, menuitem=self.window.settings_dialog) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.new_timeline, menuitem=self.window.timeline) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.create_audio_album, menuitem=self.window.audio_album) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.delete_audio_album, menuitem=self.window.delete_audio_album) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.create_video_album, menuitem=self.window.video_album) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.delete_video_album, menuitem=self.window.delete_video_album) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.check_documentation, menuitem=self.window.documentation) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.menu_play_pause, menuitem=self.window.player_play) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.menu_play_next, menuitem=self.window.player_next) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.menu_play_previous, menuitem=self.window.player_previous) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.menu_play_all, menuitem=self.window.player_play_all) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.menu_volume_down, menuitem=self.window.player_volume_down) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.menu_volume_up, menuitem=self.window.player_volume_up) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.menu_mute, menuitem=self.window.player_mute) + pub.subscribe(self.get_chat, "order-sent-message") + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.view_my_profile, menuitem=self.window.view_profile) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.view_my_profile_in_browser, menuitem=self.window.open_in_browser) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.set_status, menuitem=self.window.set_status) + widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_report_error, menuitem=self.window.report) + self.window.tb.Bind(wx.EVT_CONTEXT_MENU, self.on_context_menu) + + def disconnect_events(self): + log.debug("Disconnecting some events...") + pub.unsubscribe(self.in_post, "posted") + pub.unsubscribe(self.download, "download-file") + pub.unsubscribe(self.play_audio, "play-audio") + pub.unsubscribe(self.authorisation_failed, "authorisation-failed") + pub.unsubscribe(self.play_audios, "play-audios") + pub.unsubscribe(self.view_post, "open-post") + pub.unsubscribe(self.update_status_bar, "update-status-bar") + pub.unsubscribe(self.user_online, "user-online") + pub.unsubscribe(self.user_offline, "user-offline") + pub.unsubscribe(self.notify, "notify") + + def on_context_menu(self, event, *args, **kwargs): + """ Handles context menu event in the tree buffers.""" + # If the focus is not in the TreeCtrl of the Treebook, then we should not display any menu. + if isinstance(self.window.FindFocus(), wx.TreeCtrl) == False: + event.Skip() + return + menu = None + # Get the current buffer and let's choose a different menu depending on the selected buffer. + current_buffer = self.get_current_buffer() + # Deal with menu for community buffers. + if current_buffer.name.endswith("_community"): + menu = menus.communityBufferMenu() + # disable post loading if the community has already loaded posts. + if current_buffer.can_get_items: + menu.load_posts.Enable(False) + # Disable loading of audios, videos, documents or topics depending in two conditions. + # 1. If the buffer already exists, which means they are already loaded, or + # 2. If the group_info does not have counters for such items, which would indicate there are no items posted yet. + if self.search(current_buffer.name+"_audios") != False: + menu.load_audios.Enable(False) + elif hasattr(current_buffer, "group_info") and "audios" not in current_buffer.group_info["counters"]: + menu.load_audios.Enable(False) + if self.search(current_buffer.name+"_videos") != False: + menu.load_videos.Enable(False) + elif hasattr(current_buffer, "group_info") and "videos" not in current_buffer.group_info["counters"]: + menu.load_videos.Enable(False) + if self.search(current_buffer.name+"_topics") != False: + menu.load_topics.Enable(False) + elif hasattr(current_buffer, "group_info") and "topics" not in current_buffer.group_info["counters"]: + menu.load_topics.Enable(False) + + if self.search(current_buffer.name+"_documents") != False: + menu.load_documents.Enable(False) + elif hasattr(current_buffer, "group_info") and "docs" not in current_buffer.group_info["counters"]: + menu.load_documents.Enable(False) + # Connect the rest of the functions. + widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_posts, menuitem=menu.load_posts) + widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_topics, menuitem=menu.load_topics) + widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_audios, menuitem=menu.load_audios) + widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_videos, menuitem=menu.load_videos) + widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_documents, menuitem=menu.load_documents) + # Deal with the communities section itself. + if current_buffer.name == "communities": + menu = wx.Menu() + # Insert a different option depending if group buffers are loaded or scheduled to be loaded or not. + if self.session.settings["load_at_startup"]["communities"] == False and not hasattr(self.session, "groups"): + option = menu.Append(wx.NewId(), _("Load groups")) + widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_buffers, menuitem=option) + else: + option = menu.Append(wx.NewId(), _("Discard groups")) + widgetUtils.connect_event(menu, widgetUtils.MENU, self.unload_community_buffers, menuitem=option) + if menu != None: + self.window.PopupMenu(menu, self.window.FindFocus().GetPosition()) + # If there are no available menus, let's indicate it. + else: + output.speak(_("menu unavailable for this buffer.")) + + def authorisation_failed(self): + commonMessages.bad_authorisation() + + def login(self): + self.window.change_status(_("Logging in VK")) + self.session.login() + self.window.change_status(_("Ready")) + for i in self.buffers: + if hasattr(i, "get_items"): + # Translators: {0} will be replaced with the name of a buffer. + self.window.change_status(_("Loading items for {0}").format(i.name,)) + i.get_items() + self.window.change_status(_("Ready")) + self.create_unread_messages() + self.status_setter = RepeatingTimer(280, self.set_online) + self.status_setter.start() + self.set_online(notify=True) + self.get_audio_albums(self.session.user_id) + self.get_video_albums(self.session.user_id) + self.get_communities(self.session.user_id) + self.create_longpoll_thread() + + def create_longpoll_thread(self, notify=False): + try: + self.longpoll = longpollthread.worker(self.session) + self.longpoll.start() + if notify: + self.notify(message=_("Chat server reconnected")) + except ConnectionError: + pub.sendMessage("longpoll-read-timeout") + + def in_post(self, buffer): + buffer = self.search(buffer) + buffer.get_items() + buffer = self.search("home_timeline") + buffer.get_items() + + def update_all_buffers(self): + log.debug("Updating buffers...") + self.get_audio_albums(self.session.user_id, create_buffers=False) + self.get_video_albums(self.session.user_id, create_buffers=False) + for i in self.buffers: + if hasattr(i, "get_items"): + i.get_items() + log.debug("Updated %s" % (i.name)) + + def download(self, url, filename): + log.debug("downloading %s URL to %s filename" % (url, filename,)) + call_threaded(utils.download_file, url, filename, self.window) + + def play_audio(self, audio_object): + # Restricted audios don't include an URL paramether. + # Restriction can be due to licensed content to unauthorized countries. + if "url" in audio_object and audio_object["url"] =="": + self.notify(message=_("This file could not be played because it is not allowed in your country")) + return + call_threaded(player.player.play, audio_object, fresh=True) + + def play_audios(self, audios): + player.player.play_all(audios, shuffle=self.window.player_shuffle.IsChecked()) + + def view_post(self, post_object, controller_): + p = getattr(presenters, controller_+"Presenter")(session=self.session, postObject=post_object, interactor=getattr(interactors, controller_+"Interactor")(), view=getattr(views, controller_)()) + + def exit(self, *args, **kwargs): + log.debug("Receibed an exit signal. closing...") + self.set_offline() + self.disconnect_events() + volume = player.player.volume + config.app["sound"]["volume"] = volume + config.app.write() + self.window.Destroy() + wx.GetApp().ExitMainLoop() + + def update_buffer(self, *args, **kwargs): + b = self.get_current_buffer() + b.get_items() + + def get_more_items(self, *args, **kwargs): + b = self.get_current_buffer() + b.get_more_items() + + def check_for_updates(self, *args, **kwargs): + update = updater.do_update(update_type=self.session.settings["general"]["update_channel"]) + if update == False: + commonMessages.no_update_available() + + def search_audios(self, *args, **kwargs): + dlg = searchDialogs.searchAudioDialog() + if dlg.get_response() == widgetUtils.OK: + q = dlg.get("term") + auto_complete = True + count = 300 + performer_only = dlg.get_state("title") + sort = dlg.get_sort_order() + newbuff = buffers.audioBuffer(parent=self.window.tb, name="{0}_audiosearch".format(q,), session=self.session, composefunc="render_audio", parent_endpoint="audio", endpoint="search", q=q, auto_complete=auto_complete, count=count, performer_only=performer_only, sort=sort) + self.buffers.append(newbuff) + call_threaded(newbuff.get_items) + # Translators: {0} will be replaced with the search term. + self.window.insert_buffer(newbuff.tab, _("Search for {0}").format(q,), self.window.search("audios")) + + def search_videos(self, *args, **kwargs): + dlg = searchDialogs.searchVideoDialog() + if dlg.get_response() == widgetUtils.OK: + params = {} + params["q"] = dlg.get("term") + params["count"] = 200 + hd = dlg.get_checkable("hd") + if hd != 0: + params["hd"] = 1 + params["adult"] = dlg.get_checkable("safe_search") + params["sort"] = dlg.get_sort_order() +# params["filters"] = "youtube, vimeo, short, long, mp4" + print(params) + newbuff = buffers.videoBuffer(parent=self.window.tb, name="{0}_videosearch".format(params["q"],), session=self.session, composefunc="render_video", parent_endpoint="video", endpoint="search", **params) + self.buffers.append(newbuff) + call_threaded(newbuff.get_items) + # Translators: {0} will be replaced with the search term. + self.window.insert_buffer(newbuff.tab, _("Search for {0}").format(params["q"],), self.window.search("videos")) + + def update_status_bar(self, status): + self.window.change_status(status) + + def remove_buffer(self, event, mandatory=False, *args, **kwargs): + buffer = self.get_current_buffer() + buff = self.window.search(buffer.name) + answer = buffer.remove_buffer(mandatory) + if answer == False: + return + self.window.remove_buffer(buff) + self.buffers.remove(buffer) + del buffer + + def changelog(self, *args, **kwargs): + lang = localization.get("documentation") + os.chdir("documentation/%s" % (lang,)) + webbrowser.open("changelog.html") + os.chdir("../../") + + def configuration(self, *args, **kwargs): + """ Opens the global settings dialogue.""" + presenter = presenters.configurationPresenter(session=self.session, view=views.configurationDialog(title=_("Preferences")), interactor=interactors.configurationInteractor()) + + def new_timeline(self, *args, **kwargs): + b = self.get_current_buffer() + # If executing this method from an empty buffer we should get the newsfeed buffer. + if not hasattr(b, "get_users"): + b = self.search("home_timeline") + # Get a list of (id, user) objects. + d = [] + for i in self.session.db["users"]: + d.append((i, self.session.get_user(i)["user1_gen"])) + # Do the same for communities. + for i in self.session.db["groups"]: + d.append((-i, self.session.get_user(-i)["user1_nom"])) + a = timeline.timelineDialog([i[1] for i in d]) + if a.get_response() == widgetUtils.OK: + user = a.get_user() + buffertype = a.get_buffer_type() + user_id = "" + for i in d: + if i[1] == user: + user_id = i[0] + if user_id == "": + user_data = self.session.vk.client.utils.resolveScreenName(screen_name=user) + if type(user_data) == list: + commonMessages.no_user_exist() + return + user_id = user_data["object_id"] + if buffertype == "audio": + buffer = buffers.audioBuffer(parent=self.window.tb, name="{0}_audio".format(user_id,), composefunc="render_audio", session=self.session, create_tab=False, endpoint="get", parent_endpoint="audio", owner_id=user_id) + user = self.session.get_user(user_id, key="user1") + name_ = _("{user1_nom}'s audios").format(**user) + elif buffertype == "wall": + buffer = buffers.feedBuffer(parent=self.window.tb, name="{0}_feed".format(user_id,), composefunc="render_status", session=self.session, create_tab=False, endpoint="get", parent_endpoint="wall", extended=1, count=self.session.settings["buffers"]["count_for_wall_buffers"], owner_id=user_id) + user = self.session.get_user(user_id, key="user1") + name_ = _("{user1_nom}'s posts").format(**user) + elif buffertype == "friends": + buffer = buffers.peopleBuffer(parent=self.window.tb, name="friends_{0}".format(user_id,), composefunc="render_person", session=self.session, create_tab=False, endpoint="get", parent_endpoint="friends", count=5000, fields="uid, first_name, last_name, last_seen", user_id=user_id) + user = self.session.get_user(user_id, key="user1") + name_ = _("{user1_nom}'s friends").format(**user) + wx.CallAfter(self.complete_buffer_creation, buffer=buffer, name_=name_, position=self.window.search("timelines")) + + def complete_buffer_creation(self, buffer, name_, position): + answer = buffer.get_items() + if answer is not True: + commonMessages.show_error_code(answer) + return + self.buffers.append(buffer) + self.window.insert_buffer(buffer.tab, name_, position) + + def search_chat_buffer(self, user_id): + for i in self.buffers: + if "_messages" in i.name: + if "peer_id" in i.kwargs and i.kwargs["peer_id"] == user_id: return i + return None + + def chat_from_id(self, user_id, setfocus=True, unread=False): + b = self.search_chat_buffer(user_id) + if b != None: + pos = self.window.search(b.name) + if setfocus: + self.window.change_buffer(pos) + return b.tab.text.SetFocus() + return + # Get name based in the ID. + # for users. + if user_id > 0 and user_id < 2000000000: + user = self.session.get_user(user_id, key="user1") + name = user["user1_nom"] + elif user_id > 2000000000: + chat = self.session.vk.client.messages.getChat(chat_id=user_id-2000000000) + name = chat["title"] + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="chatBuffer", buffer_title=name, parent_tab="chats", get_items=True, kwargs=dict(parent=self.window.tb, name="{0}_messages".format(user_id,), composefunc="render_message", session=self.session, unread=unread, count=200, peer_id=user_id, rev=0, extended=True, fields="id, user_id, date, read_state, out, body, attachments, deleted")) +# if setfocus: +# pos = self.window.search(buffer.name) +# self.window.change_buffer(pos) +# call_threaded(buffer.get_items, unread=unread) +# if setfocus: buffer.tab.text.SetFocus() +# return True + + def user_online(self, event): + if self.session.settings["chat"]["notify_online"] == False: + return + user_name = self.session.get_user(event.user_id) + msg = _("{user1_nom} is online.").format(**user_name) + sound = "friend_online.ogg" + self.notify(msg, sound, self.session.settings["chat"]["notifications"]) + + def user_offline(self, event): + if self.session.settings["chat"]["notify_offline"] == False: + return + user_name = self.session.get_user(event.user_id) + msg = _("{user1_nom} is offline.").format(**user_name) + sound = "friend_offline.ogg" + self.notify(msg, sound, self.session.settings["chat"]["notifications"]) + + def user_typing(self, obj): + buffer = self.search_chat_buffer(obj.user_id) + if buffer != None and buffer == self.get_current_buffer(): + user = self.session.get_user(obj.user_id) + output.speak(_("{user1_nom} is typing...").format(**user)) + + def get_chat(self, obj=None): + """ Searches or creates a chat buffer with the id of the user that is sending or receiving a message. + obj vk_api.longpoll.EventType: an event wich defines some data from the vk's long poll server.""" + message = {} + uid = obj.peer_id + buffer = self.search_chat_buffer(uid) + if obj.from_me: + message.update(out=0) + # If there is no buffer, we must create one in a wxThread so it will not crash. + if buffer == None: + wx.CallAfter(self.chat_from_id, uid, setfocus=self.session.settings["chat"]["automove_to_conversations"], unread=True) + self.session.soundplayer.play("conversation_opened.ogg") + return + # If the chat already exists, let's create a dictionary wich will contains data of the received message. + message.update(id=obj.message_id, user_id=uid, date=obj.timestamp, body=utils.clean_text(obj.text), attachments=obj.attachments) + # if attachments is true, let's request for the full message with attachments formatted in a better way. + # ToDo: code improvements. We shouldn't need to request the same message again just for these attachments. + if len(message["attachments"]) != 0: + message_ids = message["id"] + results = self.session.vk.client.messages.getById(message_ids=message_ids) + message = results["items"][0] + if obj.from_me: + message["from_id"] = self.session.user_id + else: + message.update(read_state=0, out=0) + message["from_id"] = obj.user_id + data = [message] + # Let's add this to the buffer. + # ToDo: Clean this code and test how is the database working with this set to True. + num = self.session.order_buffer(buffer.name, data, True) + buffer.insert(self.session.db[buffer.name]["items"][-1], False) + self.session.soundplayer.play("message_received.ogg") + wx.CallAfter(self.reorder_buffer, buffer) + # Check if we have to read the message aloud + if buffer == self.get_current_buffer(): + rendered_message = renderers.render_message(message, self.session) + output.speak(rendered_message[0]) + + def set_online(self, notify=False): + try: + r = self.session.vk.client.account.setOnline() + except: + log.error("Error in setting online for the current user") + if notify: + self.window.notify("Socializer", "online now!") + + def set_offline(self): + try: + r = self.session.vk.client.account.setOffline() + except: + log.error("Error in setting offline status for the current user") + + def create_unread_messages(self): + if self.session.settings["chat"]["open_unread_conversations"] == False: + return + try: + log.debug("Getting possible unread messages.") + msgs = self.session.vk.client.messages.getConversations(count=200) + except VkApiError as ex: + if ex.code == 6: + log.exception("Something went wrong when getting messages. Waiting a second to retry") + for i in msgs["items"]: + call_threaded(self.chat_from_id, i["last_message"]["peer_id"], setfocus=False, unread=False) + time.sleep(0.6) + + def mark_as_read(self): + for i in self.buffers: + if hasattr(i, "reads") and len(i.reads) != 0: + response = self.session.vk.client.messages.markAsRead(peer_id=i.kwargs["peer_id"]) + i.clear_reads() + i.reads = [] + time.sleep(1) + + def get_audio_albums(self, user_id=None, create_buffers=True): + if self.session.settings["load_at_startup"]["audio_albums"] == False: + return + log.debug("Create audio albums...") + if self.session.settings["vk"]["use_alternative_tokens"]: + albums = self.session.vk.client_audio.get_albums(owner_id=user_id) + else: + albums = self.session.vk.client.audio.getPlaylists(owner_id=user_id) + albums = albums["items"] + self.session.audio_albums = albums + if create_buffers: + for i in albums: + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="audioAlbum", buffer_title=_("Album: {0}").format(i["title"],), parent_tab="albums", loadable=True, kwargs=dict(parent=self.window.tb, name="{0}_audio_album".format(i["id"],), composefunc="render_audio", session=self.session, endpoint="get", parent_endpoint="audio", owner_id=user_id, album_id=i["id"])) + time.sleep(0.6) + + def get_video_albums(self, user_id=None, create_buffers=True): + if self.session.settings["load_at_startup"]["video_albums"] == False: + return + log.debug("Create video albums...") + albums = self.session.vk.client.video.getAlbums(owner_id=user_id) + self.session.video_albums = albums["items"] + if create_buffers: + for i in albums["items"]: + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="videoAlbum", buffer_title=_("Album: {0}").format(i["title"],), parent_tab="video_albums", loadable=True, kwargs=dict(parent=self.window.tb, name="{0}_video_album".format(i["id"],), composefunc="render_video", session=self.session, endpoint="get", parent_endpoint="video", count=self.session.settings["buffers"]["count_for_video_buffers"], user_id=user_id, album_id=i["id"])) + time.sleep(0.15) + + def get_communities(self, user_id=None, create_buffers=True, force_action=False): + if self.session.settings["vk"]["invited_to_group"] == False: + self.session.settings["vk"]["invited_to_group"] = True + self.session.settings.write() + socializer_group = self.session.vk.client.groups.getById(group_ids="175825000")[0] + if socializer_group["is_member"] ==False: + d = commonMessages.join_group() + self.session.settings["vk"]["invited_to_group"] = True + self.session.settings.write() + if d == widgetUtils.YES: + result = self.session.vk.client.groups.join(group_id=socializer_group["id"]) + if result == 1: + commonMessages.group_joined() + else: + log.error("Invalid result when joining the Socializer's group: %d" % (result)) + if self.session.settings["load_at_startup"]["communities"] == False and force_action == False: + return + log.debug("Create community buffers...") + groups= self.session.vk.client.groups.get(user_id=user_id, extended=1, count=1000) + self.session.groups=groups["items"] + # Let's feed the local database cache with new groups coming from here. + data= dict(profiles=[], groups=self.session.groups) + self.session.process_usernames(data) + if create_buffers: + for i in self.session.groups: + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="communityBuffer", buffer_title=i["name"], parent_tab="communities", loadable=True, get_items=True, kwargs=dict(parent=self.window.tb, name="{0}_community".format(i["id"],), composefunc="render_status", session=self.session, endpoint="get", parent_endpoint="wall", count=self.session.settings["buffers"]["count_for_wall_buffers"], owner_id=-1*i["id"])) + time.sleep(0.15) + + def create_audio_album(self, *args, **kwargs): + d = creation.audio_album() + if d.get_response() == widgetUtils.OK and d.get("title") != "": + response = self.session.vk.client.audio.createPlaylist(owner_id=self.session.user_id, title=d.get("title")) + if "id" not in response: + return + album_id = response["id"] + buffer = buffers.audioAlbum(parent=self.window.tb, name="{0}_audio_album".format(album_id,), composefunc="render_audio", session=self.session, endpoint="get", parent_endpoint="audio", full_list=True, count=self.session.settings["buffers"]["count_for_audio_buffers"], user_id=self.session.user_id, album_id=album_id) + buffer.can_get_items = False + # Translators: {0} will be replaced with an audio album's title. + name_ = _("Album: {0}").format(d.get("title"),) + self.buffers.append(buffer) + self.window.insert_buffer(buffer.tab, name_, self.window.search("albums")) + buffer.get_items() + self.get_audio_albums(user_id=self.session.user_id, create_buffers=False) + + def delete_audio_album(self, *args, **kwargs): + if len(self.session.audio_albums) == 0: + return commonMessages.no_audio_albums() + answer = selector.album(_("Select the album you want to delete"), self.session) + if answer.item == None: + return + response = commonMessages.delete_audio_album() + if response != widgetUtils.YES: return + removal = self.session.vk.client.audio.deletePlaylist(playlist_id=answer.item, owner_id=self.session.user_id) + if removal == 1: + buffer = self.search("{0}_audio_album".format(answer.item,)) + buff = self.window.search(buffer.name) + self.window.remove_buffer(buff) + self.buffers.remove(buffer) + del buffer + self.get_audio_albums(user_id=self.session.user_id, create_buffers=False) + + def create_video_album(self, *args, **kwargs): + d = creation.audio_album() + if d.get_response() == widgetUtils.OK and d.get("title") != "": + response = self.session.vk.client.video.addAlbum(title=d.get("title")) + if ("album_id" in response) == False: return + album_id = response["album_id"] + buffer = buffers.videoAlbum(parent=self.window.tb, name="{0}_video_album".format(album_id,), composefunc="render_video", session=self.session, endpoint="get", parent_endpoint="video", count=self.session.settings["buffers"]["count_for_video_buffers"], user_id=self.session.user_id, album_id=album_id) + buffer.can_get_items = False + # Translators: {0} will be replaced with a video album's title. + name_ = _("Album: {0}").format(d.get("title"),) + self.buffers.append(buffer) + self.window.insert_buffer(buffer.tab, name_, self.window.search("video_albums")) + buffer.get_items() + self.session.video_albums = self.session.vk.client.video.getAlbums(owner_id=self.session.user_id)["items"] + + def delete_video_album(self, *args, **kwargs): + if len(self.session.video_albums) == 0: + return commonMessages.no_video_albums() + answer = selector.album(_("Select the album you want to delete"), self.session, "video_albums") + if answer.item == None: + return + response = commonMessages.delete_audio_album() + if response != widgetUtils.YES: return + removal = self.session.vk.client.video.deleteAlbum(album_id=answer.item) + buffer = self.search("{0}_video_album".format(answer.item,)) + buff = self.window.search(buffer.name) + self.window.remove_buffer(buff) + self.buffers.remove(buffer) + del buffer + self.session.video_albums = self.session.vk.client.video.getAlbums(owner_id=self.session.user_id)["items"] + + def check_documentation(self, *args, **kwargs): + lang = localization.get("documentation") + os.chdir("documentation/%s" % (lang,)) + webbrowser.open("manual.html") + os.chdir("../../") + + def menu_play_pause(self, *args, **kwargs): + if player.player.stream != None: + return player.player.pause() + else: + b = self.get_current_buffer() + if hasattr(b, "play_all"): + b.play_all() + else: + self.search("me_audio").play_all() + + def menu_play_next(self, *args, **kwargs): + return player.player.play_next() +# b = self.get_current_buffer() +# if hasattr(b, "play_next"): +# b.play_next() +# else: +# self.search("me_audio").play_next() + + def menu_play_previous(self, *args, **kwargs): + return player.player.play_previous() +# b = self.get_current_buffer() +# if hasattr(b, "play_previous"): +# b.play_previous() +# else: +# self.search("me_audio").play_previous() + + def menu_play_all(self, *args, **kwargs): + b = self.get_current_buffer() + if hasattr(b, "play_all"): + b.play_all() + else: + self.search("me_audio").play_all() + + def menu_volume_down(self, *args, **kwargs): + player.player.volume = player.player.volume-5 + + def menu_volume_up(self, *args, **kwargs): + player.player.volume = player.player.volume+5 + + def menu_mute(self, *args, **kwargs): + player.player.volume = 0 + + def user_profile(self, person): + p = presenters.userProfilePresenter(session=self.session, user_id=person, view=views.userProfileDialog(), interactor=interactors.userProfileInteractor()) + + def view_my_profile(self, *args, **kwargs): + self.user_profile(self.session.user_id) + + def view_my_profile_in_browser(self, *args, **kwargs): + webbrowser.open_new_tab("https://vk.com/id{id}".format(id=self.session.user_id,)) + + def notify(self, message="", sound="", type="native"): + if type == "native": + self.window.notify(_("Socializer"), message) + else: + if sound != "": + self.session.soundplayer.play(sound) + if message != "": + output.speak(message) + + def handle_longpoll_read_timeout(self): + if hasattr(self, "longpoll"): + self.notify(message=_("Chat disconnected. Trying to connect in 60 seconds")) + time.sleep(60) + if hasattr(self, "longpoll"): + del self.longpoll + self.create_longpoll_thread(notify=True) + + def set_status(self, *args, **kwargs): + dlg = wx.TextEntryDialog(self.window, _("Write your status message"), _("Set status")) + if dlg.ShowModal() == widgetUtils.OK: + result = dlg.GetValue() + info = self.session.vk.client.account.saveProfileInfo(status=result) + commonMessages.updated_status() + dlg.Destroy() + + def on_report_error(self, *args, **kwargs): + r = issueReporter.reportBug() + + def reorder_buffer(self, buffer): + """ this puts the chat buffers at the top of the list when there are new incoming messages. + In order to do so, we search for the current buffer's tab, remove the page from the TreeCtrl (without destroying the associated tab) + and reinsert it as a new child of the chat buffer. + Lastly we ensure the user is focused in the same buffer than before.""" + buffer_window = self.window.search(buffer.name) + # If buffer window is already in the first position after chat, we should not do anything here because calculations for moving buffers are expensive. + if buffer_window == self.window.search("chats")+1: + return + # Gets buffer title so we don't have to generate it again in future. + buffer_title = self.window.get_buffer_text(buffer_window) + # Determine if the current buffer is the buffer receiving a new message. + if buffer == self.get_current_buffer(): + focused_buffer = True + else: + focused_buffer = False + # This call will not destroy the associated tab for the chat buffer, thus allowing us to readd it in other position. + self.window.remove_buffer_from_position(buffer_window) + self.window.insert_chat_buffer(buffer.tab, buffer_title, self.window.search("chats")+1) + # Let's manipulate focus so users will not notice the change in buffers. + if focused_buffer: + new_position = self.window.search(buffer.name) + self.window.change_buffer(new_position) + else: + new_position = self.window.search(self.get_current_buffer().name) + self.window.change_buffer(new_position) + + def load_community_posts(self, *args, **kwargs): + """ Load community posts. It just calls to the needed method in the community buffer.""" + current_buffer = self.get_current_buffer() + if current_buffer.name.endswith("_community"): + current_buffer.load_community() + + def load_community_audios(self, *args, **kwargs): + """ Load community audios if they are not loaded already.""" + current_buffer = self.get_current_buffer() + # Get group_info if the community buffer does not have it already, so future menus will be able to use it. + if not hasattr(current_buffer, "group_info"): + group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] + current_buffer.group_info = group_info + if "audios" not in current_buffer.group_info["counters"]: + commonMessages.community_no_items() + return + new_name = current_buffer.name+"_audios" + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="audioBuffer", buffer_title=_("Audios"), parent_tab=current_buffer.tab.name, get_items=True, kwargs=dict(parent=self.window.tb, name=new_name, composefunc="render_audio", session=self.session, endpoint="get", parent_endpoint="audio", owner_id=current_buffer.kwargs["owner_id"])) + + def load_community_videos(self, *args, **kwargs): + """ Load community videos if they are not loaded already.""" + current_buffer = self.get_current_buffer() + # Get group_info if the community buffer does not have it already, so future menus will be able to use it. + if not hasattr(current_buffer, "group_info"): + group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] + current_buffer.group_info = group_info + if "videos" not in current_buffer.group_info["counters"]: + commonMessages.community_no_items() + return + new_name = current_buffer.name+"_videos" + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="videoBuffer", buffer_title=_("Videos"), parent_tab=current_buffer.tab.name, get_items=True, kwargs=dict(parent=self.window.tb, name=new_name, composefunc="render_video", session=self.session, endpoint="get", parent_endpoint="video", count=self.session.settings["buffers"]["count_for_video_buffers"], owner_id=current_buffer.kwargs["owner_id"])) + + def load_community_topics(self, *args, **kwargs): + """ Load community topics.""" + current_buffer = self.get_current_buffer() + # Get group_info if the community buffer does not have it already, so future menus will be able to use it. + if not hasattr(current_buffer, "group_info"): + group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] + current_buffer.group_info = group_info + if "topics" not in current_buffer.group_info["counters"]: + commonMessages.community_no_items() + return + new_name = current_buffer.name+"_topics" + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="topicBuffer", buffer_title=_("Topics"), parent_tab=current_buffer.tab.name, get_items=True, kwargs=dict(parent=self.window.tb, name=new_name, composefunc="render_topic", session=self.session, endpoint="getTopics", parent_endpoint="board", count=100, group_id=-1*current_buffer.kwargs["owner_id"], extended=1)) + + def load_community_documents(self, *args, **kwargs): + current_buffer = self.get_current_buffer() + # Get group_info if the community buffer does not have it already, so future menus will be able to use it. + if not hasattr(current_buffer, "group_info"): + group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] + current_buffer.group_info = group_info + if "docs" not in current_buffer.group_info["counters"]: + commonMessages.community_no_items() + return + new_name = current_buffer.name+"_documents" + wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="documentCommunityBuffer", buffer_title=_("Documents"), parent_tab=current_buffer.tab.name, get_items=True, kwargs=dict(parent=self.window.tb, name=new_name, composefunc="render_document", session=self.session, endpoint="get", parent_endpoint="docs", owner_id=current_buffer.kwargs["owner_id"])) + + def load_community_buffers(self, *args, **kwargs): + """ Load all community buffers regardless of the setting present in optional buffers tab of the preferences dialog.""" + call_threaded(self.get_communities, self.session.user_id, force_action=True) + + def unload_community_buffers(self, *args, **kwargs): + """ Delete all buffers belonging to groups.""" + communities = self.get_all_buffers("_community") + for buffer in communities: + buff = self.window.search(buffer.name) + self.window.remove_buffer(buff) + self.buffers.remove(buffer) + del self.session.groups + diff --git a/src/presenters/postDisplayer.py b/src/presenters/postDisplayer.py index dc6a6e2..cb20d00 100644 --- a/src/presenters/postDisplayer.py +++ b/src/presenters/postDisplayer.py @@ -404,6 +404,8 @@ class displayPostPresenter(base.basePresenter): break if url != "": webbrowser.open_new_tab(url) + elif attachment["type"] == "poll": + a = displayPollPresenter(session=self.session, poll=attachment, interactor=interactors.displayPollInteractor(), view=views.displayPoll()) else: log.debug("Unhandled attachment: %r" % (attachment,)) @@ -635,6 +637,27 @@ class displayTopicPresenter(displayPostPresenter): c["post_id"] = self.post["id"] a = displayTopicCommentPresenter(session=self.session, postObject=c, interactor=interactors.displayPostInteractor(), view=views.displayComment()) +class displayPollPresenter(base.basePresenter): + + def __init__(self, session, poll, view, interactor): + super(displayPollPresenter, self).__init__(view=view, interactor=interactor, modulename="display_poll") + self.poll = poll["poll"] + self.session = session + self.load_poll() + self.run() + + def load_poll(self): + user = self.session.get_user(self.poll["author_id"]) + title = _("Poll from {user1_nom}").format(**user) + self.send_message("set_title", value=title) + self.send_message("set", control="question", value=self.poll["question"]) + options = [] + for i in self.poll["answers"]: + options.append(i["text"]) + self.send_message("add_options", options=options, multiple=self.poll["multiple"]) + print(self.poll) + self.send_message("done") + class displayAudioPresenter(base.basePresenter): def __init__(self, session, postObject, view, interactor): super(displayAudioPresenter, self).__init__(view=view, interactor=interactor, modulename="display_audio") diff --git a/src/views/dialogs/postDisplay.py b/src/views/dialogs/postDisplay.py index dbb874e..958d91e 100644 --- a/src/views/dialogs/postDisplay.py +++ b/src/views/dialogs/postDisplay.py @@ -304,4 +304,39 @@ class displayFriendship(widgetUtils.BaseDialog): btnbox.Add(close, 0, wx.ALL, 5) sizer.Add(btnbox, 0, wx.ALL, 5) panel.SetSizer(sizer) - self.SetClientSize(sizer.CalcMin()) \ No newline at end of file + self.SetClientSize(sizer.CalcMin()) + +class displayPoll(widgetUtils.BaseDialog): + + def __init__(self, question="", *args, **kwargs): + super(displayPoll, self).__init__(parent=None, *args, **kwargs) + self.panel = wx.Panel(self, -1) + self.sizer = wx.BoxSizer(wx.VERTICAL) + label = wx.StaticText(self.panel, wx.NewId(), _("Question")) + self.question = wx.TextCtrl(self.panel, wx.NewId(), question, style=wx.TE_MULTILINE|wx.TE_READONLY, size=(730, -1)) + self.sizer.Add(label, 0, wx.ALL, 5) + self.sizer.Add(self.question, 0, wx.ALL, 5) + + def add_options(self, options, multiple=False): + self.options = [] + sizer = wx.StaticBoxSizer(parent=self.panel, orient=wx.VERTICAL, label=_("Options")) + for i in options: + if multiple == False: + if len(self.options) == 0: + control = wx.RadioButton(sizer.GetStaticBox(), wx.NewId(), i, style=wx.RB_GROUP) + else: + control = wx.RadioButton(sizer.GetStaticBox(), wx.NewId(), i) + else: + control = wx.CheckBox(sizer.GetStaticBox(), wx.NewId(), i) + self.options.append(control) + sizer.Add(control, 0, wx.ALL, 5) + self.sizer.Add(sizer, 0, wx.ALL, 5) + + def done(self): + self.ok = wx.Button(self.panel, wx.ID_OK, _("Vote")) + cancel = wx.Button(self.panel, wx.ID_CANCEL) + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self.ok, 0, wx.ALL, 5) + sizer.Add(cancel, 0, wx.ALL, 5) + self.panel.SetSizer(self.sizer) + self.SetClientSize(self.sizer.CalcMin()) \ No newline at end of file