diff --git a/src/controller/buffers.py b/src/controller/buffers.py deleted file mode 100644 index f72a5d9..0000000 --- a/src/controller/buffers.py +++ /dev/null @@ -1,1611 +0,0 @@ -# -*- coding: utf-8 -*- -""" A buffer is a (virtual) list of items. All items belong to a category (wall posts, messages, persons...)""" -import os -import time -import random -import logging -import webbrowser -import arrow -import wx -import presenters -import views -import interactors -import languageHandler -import widgetUtils -import output -from pubsub import pub -from vk_api.exceptions import VkApiError -from vk_api import upload -from requests.exceptions import ReadTimeout, ConnectionError -from mutagen.id3 import ID3 -from presenters import player -from wxUI.tabs import home -from wxUI.dialogs import timeline -from sessionmanager import session, renderers, utils -from mysc.thread_utils import call_threaded -from wxUI import commonMessages, menus -from sessionmanager.renderers import add_attachment -from . import selector - -log = logging.getLogger("controller.buffers") - -class baseBuffer(object): - """ a basic representation of a buffer. Other buffers should be derived from this class. This buffer represents the "news feed" """ - - def get_post(self): - """ Return the currently focused post.""" - # Handle case where there are no items in the buffer. - if self.tab.list.get_count() == 0: - wx.Bell() - return None - return self.session.db[self.name]["items"][self.tab.list.get_selected()] - - def __init__(self, parent=None, name="", session=None, composefunc=None, create_tab=True, *args, **kwargs): - """ Constructor: - @parent wx.Treebook: parent for the buffer panel, - @name str: Name for saving this buffer's data in the local storage variable, - @session sessionmanager.session.vkSession: Session for performing operations in the Vk API. This session should be logged in when this class is instanciated. - @composefunc str: This function will be called for composing the result which will be put in the listCtrl. Composefunc should exist in the sessionmanager.renderers module. - args and kwargs will be passed to get_items() without any filtering. Be careful there. - """ - super(baseBuffer, self).__init__() - self.parent = parent - self.args = args - self.kwargs = kwargs - self.session = session - self.compose_function = composefunc - self.name = name - if create_tab: - self.create_tab(self.parent) - #Update_function will be called every 3 minutes and it should be able to - # Get all new items in the buffer and sort them properly in the CtrlList. - # ToDo: Shall we allow dinamically set for update_function? - self.update_function = "get_page" - self.name = name - # source_key and post_key will point to the keys for sender and posts in VK API objects. - # They can be changed in the future for other item types in different buffers. - self.user_key = "source_id" - self.post_key = "post_id" - # When set to False, update_function won't be executed here. - self.can_get_items = True - - def create_tab(self, parent): - """ Create the Wx panel.""" - self.tab = home.homeTab(parent) - # Bind local events (they will respond to events happened in the buffer). - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - - def insert(self, item, reversed=False): - """ Add a new item to the list. Uses renderers.composefunc for parsing the dictionary and create a valid result for putting it in the list.""" - try: - item_ = getattr(renderers, self.compose_function)(item, self.session) - wx.CallAfter(self.tab.list.insert_item, reversed, *item_) - except: - log.exception(item) - - def get_items(self, show_nextpage=False): - """ Retrieve items from the VK API. This function is called repeatedly by the main controller and users could call it implicitly as well with the update buffer option. - @show_nextpage boolean: If it's true, it will try to load previous results. - """ - if self.can_get_items == False: return - retrieved = True # Control variable for handling unauthorised/connection errors. - try: - num = getattr(self.session, "get_newsfeed")(show_nextpage=show_nextpage, name=self.name, *self.args, **self.kwargs) - except VkApiError as err: - log.error("Error {0}: {1}".format(err.code, err.error)) - retrieved = err.code - return retrieved - except: - log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) - return False - if not hasattr(self, "tab"): - # Create GUI associated to this buffer. - self.create_tab(self.parent) - # Add name to the new control so we could look for it when needed. - if show_nextpage == False: - if self.tab.list.get_count() > 0 and num > 0: - v = [i for i in self.session.db[self.name]["items"][:num]] - v.reverse() - [wx.CallAfter(self.insert, i, True) for i in v] - else: - [wx.CallAfter(self.insert, i) for i in self.session.db[self.name]["items"][:num]] - else: - if num > 0: - [wx.CallAfter(self.insert, i, False) for i in self.session.db[self.name]["items"][-num:]] - return retrieved - - def get_more_items(self): - """ Returns previous items in the buffer.""" - self.get_items(show_nextpage=True) - - def post(self, *args, **kwargs): - """ Create a post in the current user's wall. - This process is handled in two parts. This is the first part, where the GUI is created and user can send the post. - During the second part (threaded), the post will be sent to the API.""" - p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=_("Write your post"), message="", text="")) - if hasattr(p, "text") or hasattr(p, "privacy"): - post_arguments=dict(privacy=p.privacy, message=p.text) - attachments = [] - if hasattr(p, "attachments"): - attachments = p.attachments - call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) - - def connect_events(self): - """ Bind all events to this buffer""" - widgetUtils.connect_event(self.tab.post, widgetUtils.BUTTON_PRESSED, self.post) - widgetUtils.connect_event(self.tab.list.list, widgetUtils.KEYPRESS, self.get_event) - widgetUtils.connect_event(self.tab.list.list, wx.EVT_CONTEXT_MENU, self.show_menu) - self.tab.set_focus_function(self.onFocus) - - def show_menu(self, ev, pos=0, *args, **kwargs): - """ Show contextual menu when pressing menu key or right mouse click in a list item.""" - if self.tab.list.get_count() == 0: return - menu = self.get_menu() - if pos != 0: - self.tab.PopupMenu(menu, pos) - else: - self.tab.PopupMenu(menu, self.tab.list.list.GetPosition()) - - def show_menu_by_key(self, ev): - """ Show contextual menu when menu key is pressed""" - if self.tab.list.get_count() == 0: - return - if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU: - self.show_menu(widgetUtils.MENU, pos=self.tab.list.list.GetPosition()) - - def get_menu(self): - """ Returns contextual menu options. They will change according to the focused item""" - p = self.get_post() - if p == None: - return - # determine if the current user is able to delete the object. - if "can_delete" in p: - can_delete = True==p["can_delete"] - else: - can_delete = False - m = menus.postMenu(can_delete=can_delete) - if ("likes" in p) == False: - m.like.Enable(False) - elif p["likes"]["user_likes"] == 1: - m.like.Enable(False) - m.dislike.Enable(True) - if ("comments" in p) == False: - m.comment.Enable(False) - m.open_in_browser.Enable(False) - if "type" in p and p["type"] != "friend" and p["type"] != "audio" and p["type"] != "video" and p["type"] != "playlist" or self.name != "home_timeline": - m.open_in_browser.Enable(True) - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_post, menuitem=m.open) - widgetUtils.connect_event(m, widgetUtils.MENU, self.do_like, menuitem=m.like) - widgetUtils.connect_event(m, widgetUtils.MENU, self.do_dislike, menuitem=m.dislike) - widgetUtils.connect_event(m, widgetUtils.MENU, self.do_comment, menuitem=m.comment) - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_in_browser, menuitem=m.open_in_browser) - if hasattr(m, "view_profile"): - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_person_profile, menuitem=m.view_profile) - if hasattr(m, "delete"): - widgetUtils.connect_event(m, widgetUtils.MENU, self.delete, menuitem=m.delete) - return m - - def do_like(self, *args, **kwargs): - """ Set like in the currently focused post.""" - post = self.get_post() - if post == None: - return - user = post[self.user_key] - id = post[self.post_key] - if "type" in post: - type_ = post["type"] - else: - type_ = "post" - l = self.session.vk.client.likes.add(owner_id=user, item_id=id, type=type_) - self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["count"] = l["likes"] - self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["user_likes"] = 1 - # Translators: This will be used when user presses like. - output.speak(_("You liked this")) - - def do_dislike(self, *args, **kwargs): - """ Set dislike (undo like) in the currently focused post.""" - post = self.get_post() - if post == None: - return - user = post[self.user_key] - id = post[self.post_key] - if "type" in post: - type_ = post["type"] - else: - type_ = "post" - l = self.session.vk.client.likes.delete(owner_id=user, item_id=id, type=type_) - self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["count"] = l["likes"] - self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["user_likes"] = 2 - # Translators: This will be user in 'dislike' - output.speak(_("You don't like this")) - - def do_comment(self, *args, **kwargs): - """ Make a comment into the currently focused post.""" - post = self.get_post() - if post == None: - return - comment = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=_("Add a comment"), message="", text="", mode="comment")) - if hasattr(comment, "text") or hasattr(comment, "privacy"): - msg = comment.text - try: - user = post[self.user_key] - id = post[self.post_key] - self.session.vk.client.wall.addComment(owner_id=user, post_id=id, text=msg) - output.speak(_("You've posted a comment")) - except Exception as msg: - log.error(msg) - - def delete(self, *args, **kwargs): - post = self.get_post() - if ("type" in post and post["type"] == "post") or self.name != "newsfeed": - question = commonMessages.remove_post() - if question == widgetUtils.NO: - return - if "owner_id" in self.kwargs: - result = self.session.vk.client.wall.delete(owner_id=self.kwargs["owner_id"], post_id=post[self.post_key]) - else: - result = self.session.vk.client.wall.delete(post_id=post[self.post_key]) - pub.sendMessage("post_deleted", post_id=post[self.post_key]) - self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) - self.tab.list.remove_item(self.tab.list.get_selected()) - - def get_event(self, ev): - """ Parses keyboard input in the ListCtrl and executes the event associated with user keypresses.""" - if ev.GetKeyCode() == wx.WXK_RETURN: event = "open_post" - else: - event = None - ev.Skip() - if event != None: - try: - getattr(self, event)() - except AttributeError: - pass - - def volume_down(self): - """ Decreases player volume by 2%""" - player.player.volume = player.player.volume-2 - - def volume_up(self): - """ Increases player volume by 2%""" - player.player.volume = player.player.volume+2 - - def play_audio(self, *args, **kwargs): - """ Play audio in currently focused buffer, if possible.""" - post = self.get_post() - if post == None: - return - if "type" in post and post["type"] == "audio": - pub.sendMessage("play", object=post["audio"]["items"][0]) - return True - - def open_person_profile(self, *args, **kwargs): - """ Views someone's profile.""" - selected = self.get_post() - if selected == None: - return - # Check all possible keys for an user object in VK API. - keys = ["from_id", "source_id", "id"] - for i in keys: - if i in selected: - pub.sendMessage("user-profile", person=selected[i]) - break - - def open_post(self, *args, **kwargs): - """ Opens the currently focused post.""" - post = self.get_post() - if post == None: - return - if "type" in post and post["type"] == "audio": - a = presenters.displayAudioPresenter(session=self.session, postObject=post["audio"]["items"], interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) - elif "type" in post and post["type"] == "friend": - pub.sendMessage("open-post", post_object=post, controller_="displayFriendship", vars=dict(caption=_("{user1_nom} added the following friends"))) - else: - pub.sendMessage("open-post", post_object=post, controller_="displayPost") - - def pause_audio(self, *args, **kwargs): - """ pauses audio playback.""" - pub.sendMessage("pause") - - def remove_buffer(self, mandatory): - """ Function for removing a buffer. Returns True if removal is successful, False otherwise""" - return False - - def get_users(self): - """ Returns source user in the post.""" - post = self.get_post() - if post == None: - return - if ("type" in post) == False: - return [post["from_id"]] - else: - return [post["source_id"]] - - def onFocus(self, event, *args,**kwargs): - """ Function executed when the item in a list is selected. - For this buffer it updates the date of posts in the list.""" - post = self.get_post() - if post == None: - return - original_date = arrow.get(post["date"]) - created_at = original_date.humanize(locale=languageHandler.curLang[:2]) - self.tab.list.list.SetItem(self.tab.list.get_selected(), 2, created_at) - event.Skip() - - def open_in_browser(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - url = "https://vk.com/wall{user_id}_{post_id}".format(user_id=post["source_id"], post_id=post["post_id"]) - webbrowser.open_new_tab(url) - -class feedBuffer(baseBuffer): - """ This buffer represents an user's wall. It may be used either for the current user or someone else.""" - - def get_items(self, show_nextpage=False): - """ Update buffer with newest items or get older items in the buffer.""" - if self.can_get_items == False: return - retrieved = True - try: - num = getattr(self.session, "get_page")(show_nextpage=show_nextpage, name=self.name, *self.args, **self.kwargs) - except VkApiError as err: - log.error("Error {0}: {1}".format(err.code, err.error)) - retrieved = err.code - return retrieved - except: - log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) - return False - if not hasattr(self, "tab"): - # Create GUI associated to this buffer. - self.create_tab(self.parent) - # Add name to the new control so we could look for it when needed. - self.tab.name = self.name - if show_nextpage == False: - if self.tab.list.get_count() > 0 and num > 0: - v = [i for i in self.session.db[self.name]["items"][:num]] - v.reverse() - [wx.CallAfter(self.insert, i, True) for i in v] - else: - [wx.CallAfter(self.insert, i) for i in self.session.db[self.name]["items"][:num]] - else: - if num > 0: - [wx.CallAfter(self.insert, i, False) for i in self.session.db[self.name]["items"][-num:]] - return retrieved - - def remove_buffer(self, mandatory=False): - """ Remove buffer if the current buffer is not the logged user's wall.""" - if "me_feed" == self.name: - output.speak(_("This buffer can't be deleted")) - return False - else: - if mandatory == False: - dlg = commonMessages.remove_buffer() - else: - dlg = widgetUtils.YES - if dlg == widgetUtils.YES: - self.session.db.pop(self.name) - return True - else: - return False - - def __init__(self, *args, **kwargs): - super(feedBuffer, self).__init__(*args, **kwargs) - self.user_key = "from_id" - self.post_key = "id" - self.can_post = True - self.can_write_private_message = True - # if this is an user timeline we must check permissions to hide buttons when needed. - if "owner_id" in self.kwargs and self.kwargs["owner_id"] > 0 and "feed" in self.name: - permissions = self.session.vk.client.users.get(user_ids=self.kwargs["owner_id"], fields="can_post, can_see_all_posts, can_write_private_message") - self.can_post = permissions[0]["can_post"] - self.can_see_all_posts = permissions[0]["can_see_all_posts"] - self.can_write_private_message = permissions[0]["can_write_private_message"] - log.debug("Checked permissions on buffer {0}, permissions were {1}".format(self.name, permissions)) - - def post(self, *args, **kwargs): - """ Create a post in the wall for the specified user - This process is handled in two parts. This is the first part, where the GUI is created and user can send the post. - During the second part (threaded), the post will be sent to the API.""" - if "owner_id" not in self.kwargs: - return super(feedBuffer, self).post() - owner_id = self.kwargs["owner_id"] - user = self.session.get_user(owner_id, key="user1") - title = _("Post to {user1_nom}'s wall").format(**user) - p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=title, message="", text="")) - if hasattr(p, "text") or hasattr(p, "privacy"): - post_arguments=dict(privacy=p.privacy, message=p.text, owner_id=owner_id) - attachments = [] - if hasattr(p, "attachments"): - attachments = p.attachments - call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) - - def open_in_browser(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - url = "https://vk.com/wall{user_id}_{post_id}".format(user_id=post["from_id"], post_id=post["id"]) - webbrowser.open_new_tab(url) - -class communityBuffer(feedBuffer): - - def __init__(self, *args, **kwargs): - super(communityBuffer, self).__init__(*args, **kwargs) - self.group_id = self.kwargs["owner_id"] - - def create_tab(self, parent): - self.tab = home.communityTab(parent) - self.connect_events() - self.tab.name = self.name - self.tab.post.Enable(False) - - def connect_events(self): - super(communityBuffer, self).connect_events() - widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_community) - - def load_community(self, *args, **kwargs): - output.speak(_("Loading community...")) - self.can_get_items = True - self.tab.load.Enable(False) - wx.CallAfter(self.get_items) - - def get_items(self, *args, **kwargs): - """ This method retrieves community information, useful to show different parts of the community itself.""" - if self.can_get_items: - # Strangely, groups.get does not return counters so we need those to show options for loading specific posts for communities. - group_info = self.session.vk.client.groups.getById(group_ids=-1*self.kwargs["owner_id"], fields="counters")[0] - self.session.db["group_info"][self.group_id].update(group_info) - if "can_post" in self.session.db["group_info"][self.group_id] and self.session.db["group_info"][self.group_id]["can_post"] == True: - self.tab.post.Enable(True) - super(communityBuffer, self).get_items(*args, **kwargs) - - def post(self, *args, **kwargs): - menu = wx.Menu() - user1 = self.session.get_user(self.session.user_id) - user2 = self.session.get_user(self.kwargs["owner_id"]) - user = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user1)) - group = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user2)) - menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 1), group) - menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 0), user) - self.tab.post.PopupMenu(menu, self.tab.post.GetPosition()) - - def _post(self, event, from_group): - owner_id = self.kwargs["owner_id"] - user = self.session.get_user(owner_id, key="user1") - title = _("Post to {user1_nom}'s wall").format(**user) - p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=title, message="", text="")) - if hasattr(p, "text") or hasattr(p, "privacy"): - post_arguments=dict(privacy=p.privacy, message=p.text, owner_id=owner_id, from_group=from_group) - attachments = [] - if hasattr(p, "attachments"): - attachments = p.attachments - call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) - -class topicBuffer(feedBuffer): - - def create_tab(self, parent): - self.tab = home.topicTab(parent) - self.connect_events() - self.tab.name = self.name - if "can_create_topic" not in self.session.db["group_info"][self.kwargs["group_id"]*-1] or ("can_create_topic" in self.session.db["group_info"][self.kwargs["group_id"]*-1] and self.session.db["group_info"][self.kwargs["group_id"]*-1]["can_create_topic"] != True): - self.tab.post.Enable(False) - - def onFocus(self, event, *args, **kwargs): - event.Skip() - - def open_post(self, *args, **kwargs): - """ Opens the currently focused post.""" - post = self.get_post() - if post == None: - return - a = presenters.displayTopicPresenter(session=self.session, postObject=post, group_id=self.kwargs["group_id"], interactor=interactors.displayPostInteractor(), view=views.displayTopic()) - - def open_in_browser(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - # In order to load the selected topic we firstly have to catch the group_id, which is present in self.kwargs - # After getting the group_id we should make it negative - group_id = self.kwargs["group_id"]*-1 - url = "https://vk.com/topic{group_id}_{topic_id}".format(group_id=group_id, topic_id=post["id"]) - webbrowser.open_new_tab(url) - - def post(self, *args, **kwargs): - menu = wx.Menu() - user1 = self.session.get_user(self.session.user_id) - user2 = self.session.get_user(-1*self.kwargs["group_id"]) - user = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user1)) - group = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user2)) - menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 1), group) - menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 0), user) - self.tab.post.PopupMenu(menu, self.tab.post.GetPosition()) - - def _post(self, event, from_group): - owner_id = self.kwargs["group_id"] - user = self.session.get_user(-1*owner_id, key="user1") - title = _("Create topic in {user1_nom}").format(**user) - p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createTopicDialog(title=title, message="", text="", topic_title="")) - if hasattr(p, "text") or hasattr(p, "privacy"): - title = p.view.title.GetValue() - msg = p.text - post_arguments = dict(title=title, text=msg, group_id=owner_id, from_group=from_group) - attachments = [] - if hasattr(p, "attachments"): - attachments = p.attachments - call_threaded(pub.sendMessage, "post", parent_endpoint="board", child_endpoint="addTopic", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) - -class documentBuffer(feedBuffer): - can_get_items = False - - def create_tab(self, parent): - self.tab = home.documentTab(parent) - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - - def onFocus(self, event, *args,**kwargs): - post = self.get_post() - if post == None: - return - original_date = arrow.get(post["date"]) - created_at = original_date.humanize(locale=languageHandler.curLang[:2]) - self.tab.list.list.SetItem(self.tab.list.get_selected(), 4, created_at) - event.Skip() - - def connect_events(self): - super(documentBuffer, self).connect_events() - # Check if we have a load button in the tab, because documents community buffers don't include it. - if hasattr(self.tab, "load"): - widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_documents) - - def load_documents(self, *args, **kwargs): - output.speak(_("Loading documents...")) - self.can_get_items = True - self.tab.load.Enable(False) - wx.CallAfter(self.get_items) - - def get_menu(self): - p = self.get_post() - if p == None: - return - if p["owner_id"] == self.session.user_id: - added = True - else: - added = False - m = menus.documentMenu(added) - widgetUtils.connect_event(m, widgetUtils.MENU, self.add_remove_document, menuitem=m.action) - widgetUtils.connect_event(m, widgetUtils.MENU, self.download, menuitem=m.download) - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_in_browser, menuitem=m.open_in_browser) - return m - - def add_remove_document(self, *args, **kwargs): - p = self.get_post() - if p == None: - return - if p["owner_id"] == self.session.user_id: - result = self.session.vk.client.docs.delete(owner_id=p["owner_id"], doc_id=p["id"]) - if result == 1: - output.speak(_("The document has been successfully deleted.")) - self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) - self.tab.list.remove_item(self.tab.list.get_selected()) - else: - result = self.session.vk.client.docs.add(owner_id=p["owner_id"], doc_id=p["id"]) - output.speak(_("The document has been successfully added.")) - - def download(self, *args, **kwargs): - post = self.get_post() - filename = utils.safe_filename(post["title"]) - # If document does not end in .extension we must fix it so the file dialog will save it properly later. - if filename.endswith(post["ext"]) == False: - filename = filename+ "."+post["ext"] - filepath = self.tab.get_download_path(filename) - if filepath != None: - pub.sendMessage("download-file", url=post["url"], filename=filepath) - - def open_in_browser(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - url = "https://vk.com/doc{user_id}_{post_id}".format(user_id=post["owner_id"], post_id=post["id"]) - webbrowser.open_new_tab(url) - -class documentCommunityBuffer(documentBuffer): - can_get_items = True - - def create_tab(self, parent): - self.tab = home.documentCommunityTab(parent) - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - -class audioBuffer(feedBuffer): - def create_tab(self, parent): - self.tab = home.audioTab(parent) - self.tab.name = self.name - self.connect_events() - if self.name == "me_audio": - self.tab.post.Enable(True) - - def get_event(self, ev): - if ev.GetKeyCode() == wx.WXK_RETURN: - if len(self.tab.list.get_multiple_selection()) < 2: - event = "play_all" - else: - event = "play_audio" - else: - event = None - ev.Skip() - if event != None: - try: - getattr(self, event)(skip_pause=True) - except AttributeError: - pass - - def connect_events(self): - widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio) - widgetUtils.connect_event(self.tab.play_all, widgetUtils.BUTTON_PRESSED, self.play_all) - pub.subscribe(self.change_label, "playback-changed") - super(audioBuffer, self).connect_events() - - def play_audio(self, *args, **kwargs): - if player.player.check_is_playing() and not "skip_pause" in kwargs: - return pub.sendMessage("pause") - selected = self.tab.list.get_multiple_selection() - if len(selected) == 0: - return - elif len(selected) == 1: - pub.sendMessage("play", object=self.session.db[self.name]["items"][selected[0]]) - else: - selected_audios = [self.session.db[self.name]["items"][item] for item in selected] - pub.sendMessage("play-all", list_of_songs=selected_audios) - return True - - def play_next(self, *args, **kwargs): - selected = self.tab.list.get_selected() - if selected < 0: - selected = 0 - if self.tab.list.get_count() <= selected+1: - newpos = 0 - else: - newpos = selected+1 - self.tab.list.select_item(newpos) - self.play_audio() - - def play_previous(self, *args, **kwargs): - selected = self.tab.list.get_selected() - if selected <= 0: - selected = self.tab.list.get_count() - newpos = selected-1 - self.tab.list.select_item(newpos) - self.play_audio() - - def open_post(self, *args, **kwargs): - selected = self.tab.list.get_multiple_selection() - if len(selected) < 1: - return - audios = [self.session.db[self.name]["items"][audio] for audio in selected] - a = presenters.displayAudioPresenter(session=self.session, postObject=audios, interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) - - def play_all(self, *args, **kwargs): - selected = self.tab.list.get_selected() - if selected == -1: - selected = 0 - if self.name not in self.session.db: - return - audios = [i for i in self.session.db[self.name]["items"][selected:]] - pub.sendMessage("play-all", list_of_songs=audios) - return True - - def remove_buffer(self, mandatory=False): - if "me_audio" == self.name or "popular_audio" == self.name or "recommended_audio" == self.name: - output.speak(_("This buffer can't be deleted")) - return False - else: - if mandatory == False: - dlg = commonMessages.remove_buffer() - else: - dlg = widgetUtils.YES - if dlg == widgetUtils.YES: - self.session.db.pop(self.name) - return True - else: - return False - - def get_more_items(self, *args, **kwargs): - # Translators: Some buffers can't use the get previous item feature due to API limitations. - output.speak(_("This buffer doesn't support getting more items.")) - - def onFocus(self, event, *args, **kwargs): - event.Skip() - - def add_to_library(self, *args, **kwargs): - call_threaded(self._add_to_library, *args, **kwargs) - - def _add_to_library(self, *args, **kwargs): - selected = self.tab.list.get_multiple_selection() - if len(selected) < 1: - return - audios = [self.session.db[self.name]["items"][audio] for audio in selected] - errors_detected = 0 - for i in audios: - args = {} - args["audio_id"] = i["id"] - if "album_id" in i: - args["album_id"] = i["album_id"] - args["owner_id"] = i["owner_id"] - try: - audio = self.session.vk.client.audio.add(**args) - except VkApiError: - errors_detected = errors_detected + 1 - continue - if audio != None and int(audio) < 21: - errors_detected = errors_detected + 1 - if errors_detected == 0: - if len(selected) == 1: - msg = _("Audio added to your library") - elif len(selected) > 1 and len(selected) < 5: - msg = _("{0} audios were added to your library.").format(len(selected),) - else: - msg = _("{audios} audios were added to your library.").format(audios=len(selected),) - output.speak(msg) - else: - output.speak(_("{0} errors occurred while attempting to add {1} audios to your library.").format(errors_detected, len(selected))) - - def remove_from_library(self, *args, **kwargs): - call_threaded(self._remove_from_library, *args, **kwargs) - - def _remove_from_library(self, *args, **kwargs): - selected = self.tab.list.get_multiple_selection() - if len(selected) < 1: - return - audios = [self.session.db[self.name]["items"][audio] for audio in selected] - errors_detected = 0 - audios_removed = 0 - for i in range(0, len(selected)): - args = {} - args["audio_id"] = audios[i]["id"] - args["owner_id"] = self.session.user_id - result = self.session.vk.client.audio.delete(**args) - if int(result) != 1: - errors_dtected = errors_detected + 1 - else: - self.session.db[self.name]["items"].pop(selected[i]-audios_removed) - self.tab.list.remove_item(selected[i]-audios_removed) - audios_removed = audios_removed + 1 - if errors_detected == 0: - if len(selected) == 1: - msg = _("Audio removed.") - elif len(selected) > 1 and len(selected) < 5: - msg = _("{0} audios were removed.").format(len(selected),) - else: - msg = _("{audios} audios were removed.").format(audios=len(selected),) - output.speak(msg) - else: - output.speak(_("{0} errors occurred while attempting to remove {1} audios.").format(errors_detected, len(selected))) - - def move_to_album(self, *args, **kwargs): - if len(self.session.audio_albums) == 0: - return commonMessages.no_audio_albums() - album = selector.album(_("Select the album where you want to move this song"), self.session) - if album.item == None: - return - call_threaded(self._move_to_album, album.item, *args, **kwargs) - - def _move_to_album(self, album, *args, **kwargs): - selected = self.tab.list.get_multiple_selection() - if len(selected) < 1: - return - audios = [self.session.db[self.name]["items"][audio] for audio in selected] - errors_detected = 0 - for i in audios: - id = i["id"] - try: - response = self.session.vk.client.audio.add(playlist_id=album, audio_id=id, owner_id=i["owner_id"]) - except VkApiError: - errors_detected = errors_detected + 1 - if errors_detected == 0: - if len(selected) == 1: - msg = _("Audio added to playlist.") - elif len(selected) > 1 and len(selected) < 5: - msg = _("{0} audios were added to playlist.").format(len(selected),) - else: - msg = _("{audios} audios were added to playlist.").format(audios=len(selected),) - output.speak(msg) - else: - output.speak(_("{0} errors occurred while attempting to add {1} audios to your playlist.").format(errors_detected, len(selected))) - - def get_menu(self): - p = self.get_post() - if p == None: - return - m = menus.audioMenu() - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_post, menuitem=m.open) - widgetUtils.connect_event(m, widgetUtils.MENU, self.play_audio, menuitem=m.play) - widgetUtils.connect_event(m, widgetUtils.MENU, self.move_to_album, menuitem=m.move) - widgetUtils.connect_event(m, widgetUtils.MENU, self.download, menuitem=m.download) - widgetUtils.connect_event(m, widgetUtils.MENU, self.select_all, menuitem=m.select) - widgetUtils.connect_event(m, widgetUtils.MENU, self.deselect_all, menuitem=m.deselect) - # if owner_id is the current user, the audio is added to the user's audios. - if p["owner_id"] == self.session.user_id: - m.library.SetItemLabel(_("&Remove")) - widgetUtils.connect_event(m, widgetUtils.MENU, self.remove_from_library, menuitem=m.library) - else: - widgetUtils.connect_event(m, widgetUtils.MENU, self.add_to_library, menuitem=m.library) - return m - - def post(self, *args, **kwargs): - """ Uploads an audio to the current user's library from the computer. """ - file = self.tab.get_file_to_upload() - if file == None: - return - audio_tags = ID3(file) - if "TIT2" in audio_tags: - title = audio_tags["TIT2"].text[0] - else: - title = _("Untitled") - if "TPE1" in audio_tags: - artist = audio_tags["TPE1"].text[0] - else: - artist = _("Unknown artist") - uploader = upload.VkUpload(self.session.vk.session_object) - call_threaded(uploader.audio, file, title=title, artist=artist) - - def open_in_browser(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - url = "https://vk.com/audio{user_id}_{post_id}".format(user_id=post["owner_id"], post_id=post["id"]) - webbrowser.open_new_tab(url) - - def change_label(self, stopped): - if hasattr(self.tab, "play"): - if stopped == False: - self.tab.play.SetLabel(_("P&ause")) - else: - self.tab.play.SetLabel(_("P&lay")) - - def __del__(self): - pub.unsubscribe(self.change_label, "playback-changed") - - def download(self, *args, **kwargs): - selected = self.tab.list.get_multiple_selection() - if len(selected) < 1: - return - audios = [self.session.db[self.name]["items"][audio] for audio in selected] - if len(audios) == 0: - return - elif len(audios) == 1: - multiple = False - filename = utils.safe_filename("{0} - {1}.mp3".format(audios[0]["title"], audios[0]["artist"])) - else: - multiple = True - filename = "" # No default filename for multiple files. - path = self.tab.get_download_path(filename=filename, multiple=multiple) - self.download_threaded(path, multiple, audios) - - def download_threaded(self, path, multiple, audios): - if multiple == False: - url = audios[0]["url"] - pub.sendMessage("download-file", url=url, filename=path) - return - else: - downloads = [] - for i in audios: - filename = utils.safe_filename("{0} - {1}.mp3".format(i["title"], i["artist"])) - filepath = os.path.join(path, filename) - downloads.append((utils.transform_audio_url(i["url"]), filepath)) - pub.sendMessage("download-files", downloads=downloads) - - def select_all(self, *args, **kwargs): - items = self.tab.list.list.GetItemCount() - for i in range(0, items): - self.tab.list.list.SetItemImage(i, 1) - - def deselect_all(self, *args, **kwargs): - items = self.tab.list.list.GetItemCount() - for i in range(0, items): - self.tab.list.list.SetItemImage(i, 0) - -class audioAlbum(audioBuffer): - """ this buffer was supposed to be used with audio albums - but is deprecated as VK removed its audio support for third party apps.""" - - def create_tab(self, parent): - self.tab = home.audioAlbumTab(parent) - self.tab.play.Enable(False) - self.tab.play_all.Enable(False) - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - - def connect_events(self): - super(audioAlbum, self).connect_events() - widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_album) - - def load_album(self, *args, **kwargs): - output.speak(_("Loading album...")) - self.can_get_items = True - self.tab.load.Enable(False) - wx.CallAfter(self.get_items) - self.tab.play.Enable(True) - self.tab.play_all.Enable(True) - -class videoBuffer(feedBuffer): - """ This buffer represents video elements, and it can be used for showing videos for the logged user or someone else.""" - - def create_tab(self, parent): - self.tab = home.videoTab(parent) - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - - def connect_events(self): - widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio) - super(videoBuffer, self).connect_events() - - def play_audio(self, *args, **kwargs): - """ Due to inheritance this method should be called play_audio, but play the currently focused video. - Opens a webbrowser pointing to the video's URL.""" - selected = self.tab.list.get_selected() - if self.tab.list.get_count() == 0: - return - if selected == -1: - selected = 0 - output.speak(_("Opening video in webbrowser...")) - webbrowser.open_new_tab(self.session.db[self.name]["items"][selected]["player"]) -# print self.session.db[self.name]["items"][selected] - return True - - def open_post(self, *args, **kwargs): - pass - - def remove_buffer(self, mandatory=False): - if "me_video" == self.name: - output.speak(_("This buffer can't be deleted")) - return False - else: - if mandatory == False: - dlg = commonMessages.remove_buffer() - else: - dlg = widgetUtils.YES - if dlg == widgetUtils.YES: - self.session.db.pop(self.name) - return True - else: - return False - - def get_more_items(self, *args, **kwargs): - # Translators: Some buffers can't use the get previous item feature due to API limitations. - output.speak(_("This buffer doesn't support getting more items.")) - - def onFocus(self, event, *args, **kwargs): - event.Skip() - - def add_to_library(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - args = {} - args["video_id"] = post["id"] - if "album_id" in post: - args["album_id"] = post["album_id"] - args["owner_id"] = post["owner_id"] - video = self.session.vk.client.video.add(**args) - if video != None and int(video) > 21: - output.speak(_("Video added to your library")) - - def remove_from_library(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - args = {} - args["video_id"] = post["id"] - args["owner_id"] = self.session.user_id - result = self.session.vk.client.video.delete(**args) - if int(result) == 1: - output.speak(_("Removed video from library")) - self.tab.list.remove_item(self.tab.list.get_selected()) - - def move_to_album(self, *args, **kwargs): - if len(self.session.video_albums) == 0: - return commonMessages.no_video_albums() - post= self.get_post() - if post == None: - return - album = selector.album(_("Select the album where you want to move this video"), self.session, "video_albums") - if album.item == None: return - id = post["id"] - response = self.session.vk.client.video.addToAlbum(album_ids=album.item, video_id=id, target_id=self.session.user_id, owner_id=self.get_post()["owner_id"]) - if response == 1: - # Translators: Used when the user has moved an video to an album. - output.speak(_("Moved")) - - def get_menu(self): - """ We'll use the same menu that is used for audio items, as the options are exactly the same""" - p = self.get_post() - if p == None: - return - m = menus.audioMenu() - widgetUtils.connect_event(m, widgetUtils.MENU, self.move_to_album, menuitem=m.move) - # if owner_id is the current user, the audio is added to the user's audios. - if p["owner_id"] == self.session.user_id: - m.library.SetItemLabel(_("&Remove")) - widgetUtils.connect_event(m, widgetUtils.MENU, self.remove_from_library, menuitem=m.library) - else: - widgetUtils.connect_event(m, widgetUtils.MENU, self.add_to_library, menuitem=m.library) - return m - - def open_in_browser(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - url = "https://vk.com/video{user_id}_{video_id}".format(user_id=post["owner_id"], video_id=post["id"]) - webbrowser.open_new_tab(url) - -class videoAlbum(videoBuffer): - - def create_tab(self, parent): - self.tab = home.videoAlbumTab(parent) - self.tab.play.Enable(False) - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - - def connect_events(self): - super(videoAlbum, self).connect_events() - widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_album) - - def load_album(self, *args, **kwargs): - output.speak(_("Loading album...")) - self.can_get_items = True - self.tab.load.Enable(False) - wx.CallAfter(self.get_items) - self.tab.play.Enable(True) - -class emptyBuffer(object): - - def __init__(self, name=None, parent=None, *args, **kwargs): - self.tab = home.empty(parent=parent, name=name) - self.name = name - - def get_items(self, *args, **kwargs): - if not hasattr(self, "tab"): - # Create GUI associated to this buffer. - self.create_tab(self.parent) - # Add name to the new control so we could look for it when needed. - self.tab.name = self.name - pass - - def get_more_items(self, *args, **kwargs): - output.speak(_("This buffer doesn't support getting more items.")) - - def remove_buffer(self, mandatory=False): return False - -class chatBuffer(baseBuffer): - - def insert(self, item, reversed=False): - """ Add a new item to the list. Uses session.composefunc for parsing the dictionary and create a valid result for putting it in the list.""" - # as this tab is based in a text control, we have to overwrite the defaults. - item_ = getattr(renderers, self.compose_function)(item, self.session) - # the self.chat dictionary will have (first_line, last_line) as keys and message ID as a value for looking into it when needed. - # Here we will get first and last line of a chat message appended to the history. - values = self.tab.add_message(item_[0], reverse=reversed) - self.chats[values] = item["id"] - - def get_focused_post(self): - """ Gets chat message currently in focus""" - # this function replaces self.get_post for this buffer, as we rely in a TextCtrl control for getting chats. - # Instead of the traditional method to do the trick. - # Get text position here. - position = self.tab.history.PositionToXY(self.tab.history.GetInsertionPoint()) - id_ = None - # The dictionary keys should be looked in reverse order as we are interested in the last result only. - for i in reversed(list(self.chats.keys())): - # Check if position[2] (line position) matches with something in self.chats - # (All messages, except the last one, should be able to be matched here). - # position[2]+1 is added because line may start with 0, while in wx.TextCtrl.GetNumberOfLines() it returns a value counting from 1. - if position[2]+1 >= i[0]: - id_ = self.chats[i] - # If we find our chat message, let's skip the rest of the loop. - break - # Retrieve here the object based in id_ - if id_ != None: - for i in self.session.db[self.name]["items"]: - if i["id"] == id_: - return i - return False - - get_post = get_focused_post - - def onFocus(self, event, *args, **kwargs): - if event.GetKeyCode() == wx.WXK_UP or event.GetKeyCode() == wx.WXK_DOWN or event.GetKeyCode() == wx.WXK_START or event.GetKeyCode() == wx.WXK_PAGEUP or event.GetKeyCode() == wx.WXK_PAGEDOWN or event.GetKeyCode() == wx.WXK_END: - msg = self.get_focused_post() - if msg == False: # Handle the case where the last line of the control cannot be matched to anything. - return - # Mark unread conversations as read. - if "read_state" in msg and msg["read_state"] == 0 and "out" in msg and msg["out"] == 0: - self.session.soundplayer.play("message_unread.ogg") - call_threaded(self.session.vk.client.messages.markAsRead, peer_id=self.kwargs["peer_id"]) - [i.update(read_state=1) for i in self.session.db[self.name]["items"]] - if "attachments" in msg and len(msg["attachments"]) > 0: - self.tab.attachments.list.Enable(True) - self.attachments = list() - self.tab.attachments.clear() - self.parse_attachments(msg) - else: - self.tab.attachments.list.Enable(False) - self.tab.attachments.clear() - event.Skip() - - def create_tab(self, parent): - self.tab = home.chatTab(parent) - self.attachments = list() - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - - def connect_events(self): - widgetUtils.connect_event(self.tab.send, widgetUtils.BUTTON_PRESSED, self.send_chat_to_user) - widgetUtils.connect_event(self.tab.attachment, widgetUtils.BUTTON_PRESSED, self.add_attachment) - widgetUtils.connect_event(self.tab.text, widgetUtils.KEYPRESS, self.catch_enter) - self.tab.set_focus_function(self.onFocus) - - def catch_enter(self, event, *args, **kwargs): - shift=event.ShiftDown() - if event.GetKeyCode() == wx.WXK_RETURN and shift == False: - return self.send_chat_to_user() - t = time.time() - if event.GetUnicodeKey() != wx.WXK_NONE and t-self.last_keypress > 5: - self.last_keypress = t - call_threaded(self.session.vk.client.messages.setActivity, peer_id=self.kwargs["peer_id"], type="typing") - event.Skip() - - def get_items(self, show_nextpage=False): - """ Update buffer with newest items or get older items in the buffer.""" - if self.can_get_items == False: return - retrieved = True - try: - num = getattr(self.session, "get_page")(show_nextpage=show_nextpage, name=self.name, *self.args, **self.kwargs) - except VkApiError as err: - log.error("Error {0}: {1}".format(err.code, err.error)) - retrieved = err.code - return retrieved - except: - log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) - return False - if not hasattr(self, "tab"): - # Create GUI associated to this buffer. - self.create_tab(self.parent) - # Add name to the new control so we could look for it when needed. - self.tab.name = self.name - - if show_nextpage == False: - if self.tab.history.GetValue() != "" and num > 0: - v = [i for i in self.session.db[self.name]["items"][:num]] - [self.insert(i, False) for i in v] - else: - [self.insert(i) for i in self.session.db[self.name]["items"][:num]] - else: - if num > 0: - # At this point we save more CPU and mathematical work if we just delete everything in the chat history and readd all messages. - # Otherwise we'd have to insert new lines at the top and recalculate positions everywhere else. - # Firstly, we'd have to save the current focused object so we will place the user in the right part of the text after loading everything again. - focused_post = self.get_post() - self.chats = dict() - wx.CallAfter(self.tab.history.SetValue, "") - v = [i for i in self.session.db[self.name]["items"]] - [self.insert(i) for i in v] - # Now it's time to set back the focus in the post. - for i in self.chats.keys(): - if self.chats[i] == focused_post["id"]: - line = i[0] - wx.CallAfter(self.tab.history.SetInsertionPoint, self.tab.history.XYToPosition(0, line)) - output.speak(_("Items loaded")) - break - if self.unread == True and num > 0: - self.session.db[self.name]["items"][-1].update(read_state=0) - return retrieved - - def get_more_items(self): - output.speak(_("Getting more items...")) - call_threaded(self.get_items, show_nextpage=True) - - def add_attachment(self, *args, **kwargs): - a = presenters.attachPresenter(session=self.session, view=views.attachDialog(voice_messages=True), interactor=interactors.attachInteractor()) - if len(a.attachments) != 0: - self.attachments_to_be_sent = a.attachments - - def send_chat_to_user(self, *args, **kwargs): - text = self.tab.text.GetValue() - if text == "" and not hasattr(self, "attachments_to_be_sent"): - wx.Bell() - return - self.tab.text.SetValue("") - post_arguments = dict(random_id = random.randint(0, 100000), peer_id=self.kwargs["peer_id"]) - if len(text) > 0: - post_arguments.update(message=text) - if hasattr(self, "attachments_to_be_sent") and len(self.attachments_to_be_sent) > 0: - attachments = self.attachments_to_be_sent[::] - else: - attachments = [] - call_threaded(pub.sendMessage, "post", parent_endpoint="messages", child_endpoint="send", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) - if hasattr(self, "attachments_to_be_sent"): - del self.attachments_to_be_sent - - def __init__(self, unread=False, *args, **kwargs): - super(chatBuffer, self).__init__(*args, **kwargs) - self.unread = unread - self.chats = dict() - self.peer_typing = 0 - self.last_keypress = time.time() - - def parse_attachments(self, post): - attachments = [] - - if "attachments" in post: - for i in post["attachments"]: - # We don't need the photos_list attachment, so skip it. - if i["type"] == "photos_list": - continue - try: - rendered_object = add_attachment(i) - except: - log.exception("Error parsing the following attachment on chat: %r" % (i,)) - attachments.append(rendered_object) - self.attachments.append(i) - self.tab.attachments.list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.open_attachment) - self.tab.insert_attachments(attachments) - - def open_attachment(self, *args, **kwargs): - index = self.tab.attachments.get_selected() - attachment = self.attachments[index] - if attachment["type"] == "audio": - a = presenters.displayAudioPresenter(session=self.session, postObject=[attachment["audio"]], interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) - elif attachment["type"] == "audio_message": - link = attachment["audio_message"]["link_mp3"] - pub.sendMessage("play-message", message_url=link) - elif attachment["type"] == "link": - output.speak(_("Opening URL..."), True) - webbrowser.open_new_tab(attachment["link"]["url"]) - elif attachment["type"] == "doc": - output.speak(_("Opening document in web browser...")) - webbrowser.open(attachment["doc"]["url"]) - elif attachment["type"] == "video": - # it seems VK doesn't like to attach video links as normal URLS, so we'll have to - # get the full video object and use its "player" key which will open a webbrowser in their site with a player for the video. - # see https://vk.com/dev/attachments_w and and https://vk.com/dev/video.get - # However, the flash player isn't good for visually impaired people (when you press play you won't be able to close the window with alt+f4), so it could be good to use the HTML5 player. - # For firefox, see https://addons.mozilla.org/ru/firefox/addon/force-html5-video-player-at-vk/ - # May be I could use a dialogue here for inviting people to use this addon in firefox. It seems it isn't possible to use this html5 player from the player URL. - object_id = "{0}_{1}".format(attachment["video"]["owner_id"], attachment["video"]["id"]) - video_object = self.session.vk.client.video.get(owner_id=attachment["video"]["owner_id"], videos=object_id) - video_object = video_object["items"][0] - output.speak(_("Opening video in web browser..."), True) - webbrowser.open_new_tab(video_object["player"]) - elif attachment["type"] == "photo": - output.speak(_("Opening photo in web browser..."), True) - # Possible photo sizes for looking in the attachment information. Try to use the biggest photo available. - possible_sizes = [1280, 604, 130, 75] - url = "" - for i in possible_sizes: - if "photo_{0}".format(i,) in attachment["photo"]: - url = attachment["photo"]["photo_{0}".format(i,)] - break - if url != "": - webbrowser.open_new_tab(url) - if attachment["type"] == "wall": - pub.sendMessage("open-post", post_object=attachment["wall"], controller_="displayPost") - else: - log.debug("Unhandled attachment: %r" % (attachment,)) - - def clear_reads(self): - for i in self.session.db[self.name]["items"]: - if "read_state" in i and i["read_state"] == 0: - i["read_state"] = 1 - - def remove_buffer(self, mandatory=False): - """ Remove buffer if the current buffer is not the logged user's wall.""" - if mandatory == False: - dlg = commonMessages.remove_buffer() - else: - dlg = widgetUtils.YES - if dlg == widgetUtils.YES: - self.session.db.pop(self.name) - return True - else: - return False - - def open_in_browser(self, *args, **kwargs): - peer_id = self.kwargs["peer_id"] - url = "https://vk.com/im?sel={peer_id}".format(peer_id=peer_id) - webbrowser.open_new_tab(url) - -class peopleBuffer(feedBuffer): - - def post(self, *args, **kwargs): - user = self.get_post() - if "can_post" not in user: # retrieve data if not present in the object. - user = self.session.vk.client.users.get(user_ids=user["id"], fields="can_post")[0] - if user.get("can_post") == True: - user_str = self.session.get_user(user["id"], key="user1") - title = _("Post to {user1_nom}'s wall").format(**user_str) - p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=title, message="", text="")) - if hasattr(p, "text") or hasattr(p, "privacy"): - post_arguments=dict(privacy=p.privacy, message=p.text, owner_id=user["id"]) - attachments = [] - if hasattr(p, "attachments"): - attachments = p.attachments - call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) - - def create_tab(self, parent): - self.tab = home.peopleTab(parent) - self.connect_events() - self.tab.name = self.name - if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): - self.tab.post.Enable(False) - - def connect_events(self): - super(peopleBuffer, self).connect_events() - widgetUtils.connect_event(self.tab.new_chat, widgetUtils.BUTTON_PRESSED, self.new_chat) - - def new_chat(self, *args, **kwargs): - user = self.get_post() - if user == None: - return - user_id = user["id"] - pub.sendMessage("new-chat", user_id=user_id) - - def onFocus(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - if post.get("can_post") == True: - self.tab.post.Enable(True) - else: - self.tab.post.Enable(False) - if ("last_seen" in post) == False: return - original_date = arrow.get(post["last_seen"]["time"]) - now = arrow.now() - original_date.to(now.tzinfo) - diffdate = now-original_date - if diffdate.days == 0 and diffdate.seconds <= 360: - online_status = _("Online") - else: - # Translators: This is the date of last seen - online_status = _("Last seen {0}").format(original_date.humanize(locale=languageHandler.curLang[:2]),) - self.tab.list.list.SetItem(self.tab.list.get_selected(), 1, online_status) - - def open_timeline(self, *args, **kwargs): - user = self.get_post() - if user == None: - return - a = timeline.timelineDialog([self.session.get_user(user["id"])["user1_gen"]], show_selector=False) - if a.get_response() == widgetUtils.OK: - buffer_type = a.get_buffer_type() - user_id = user["id"] - pub.sendMessage("create-timeline", user_id=user_id, buffer_type=buffer_type) - - def get_menu(self, *args, **kwargs): - """ display menu for people buffers (friends and requests)""" - # If this is an incoming requests buffer, there is a flag in the peopleMenu that shows a few new options. - # So let's make sure we call it accordingly. - if self.name == "friend_requests": - m = menus.peopleMenu(is_request=True) - # Connect the accept and decline methods from here. - widgetUtils.connect_event(m, widgetUtils.MENU, self.accept_friendship, menuitem=m.accept) - widgetUtils.connect_event(m, widgetUtils.MENU, self.decline_friendship, menuitem=m.decline) - widgetUtils.connect_event(m, widgetUtils.MENU, self.keep_as_follower, menuitem=m.keep_as_follower) - elif self.name == "subscribers": - m = menus.peopleMenu(is_subscriber=True) - widgetUtils.connect_event(m, widgetUtils.MENU, self.accept_friendship, menuitem=m.add) - else: - m = menus.peopleMenu(is_request=False) - widgetUtils.connect_event(m, widgetUtils.MENU, self.decline_friendship, menuitem=m.decline) - widgetUtils.connect_event(m, widgetUtils.MENU, self.block_person, menuitem=m.block) - # It is not allowed to send messages to people who is not your friends, so let's disable it if we're in a pending or outgoing requests buffer. - if "friend_requests" in self.name: - m.message.Enable(False) - widgetUtils.connect_event(m, widgetUtils.MENU, self.new_chat, menuitem=m.message) - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_timeline, menuitem=m.timeline) - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_person_profile, menuitem=m.view_profile) - widgetUtils.connect_event(m, widgetUtils.MENU, self.open_in_browser, menuitem=m.open_in_browser) - return m - - def open_post(self, *args, **kwargs): pass - - def play_audio(self, *args, **kwargs): return False - - def pause_audio(self, *args, **kwargs): pass - - def accept_friendship(self, *args, **kwargs): - pass - - def decline_friendship(self, *args, **kwargs): - person = self.get_post() - if person == None: - return - user = self.session.get_user(person["id"]) - question = commonMessages.remove_friend(user) - if question == widgetUtils.NO: - return - result = self.session.vk.client.friends.delete(user_id=person["id"]) - if "friend_deleted" in result: - msg = _("You've removed {user1_nom} from your friends.").format(**user,) - pub.sendMessage("notify", message=msg) - self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) - self.tab.list.remove_item(self.tab.list.get_selected()) - - def block_person(self, *args, **kwargs): - person = self.get_post() - if person == None: - return - user = self.session.get_user(person["id"]) - question = commonMessages.block_person(user) - if question == widgetUtils.NO: - return - result = self.session.vk.client.account.ban(owner_id=person["id"]) - if result == 1: - msg = _("You've blocked {user1_nom} from your friends.").format(**user,) - pub.sendMessage("notify", message=msg) - self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) - self.tab.list.remove_item(self.tab.list.get_selected()) - - def keep_as_follower(self, *args, **kwargs): - pass - - def add_person(self, person): - # This tracks if the user already exists here, in such case we just will update the last_seen variable - existing = False - for i in self.session.db[self.name]["items"]: - if person["id"] == i["id"]: - existing = True - i["last_seen"]["time"] = person["last_seen"]["time"] - break - # Add the new user to the buffer just if it does not exists previously. - if existing == False: - # Ensure the user won't loose the focus after the new item is added. - focused_item = self.tab.list.get_selected()+1 - self.session.db[self.name]["items"].insert(0, person) - self.insert(person, True) - # Selects back the previously focused item. - self.tab.list.select_item(focused_item) - - def remove_person(self, user_id): - # Make sure the user is present in the buffer, otherwise don't attempt to remove a None Value from the list. - user = None - focused_user = self.get_post() - for i in self.session.db[self.name]["items"]: - if i["id"] == user_id: - user = i - break - if user != None: - person_index = self.session.db[self.name]["items"].index(user) - focused_item = self.tab.list.get_selected() - self.session.db[self.name]["items"].pop(person_index) - self.tab.list.remove_item(person_index) - if user != focused_user: - # Let's find the position of the previously focused user. - focus = None - for i in range(0, len(self.session.db[self.name]["items"])): - if focused_user["id"] == self.session.db[self.name]["items"][i]["id"]: - self.tab.list.select_item(i) - return - elif user == focused_user and person_index < self.tab.list.get_count(): - self.tab.list.select_item(person_index) - else: - self.tab.list.select_item(self.tab.list.get_count()-1) - - def get_friend(self, user_id): - for i in self.session.db["friends_"]["items"]: - if i["id"] == user_id: - return i - log.exception("Getting user manually...") - user = self.session.vk.client.users.get(user_ids=event.user_id, fields="last_seen")[0] - return user - - def update_online(self): - online_users = self.session.vk.client.friends.getOnline() - now = time.time() - for i in self.session.db[self.name]["items"]: - if i["id"] in online_users: - i["last_seen"]["time"] = now - else: - log.exception("Removing an user from online status manually... %r" % (i)) - self.remove_person(i["id"]) - - def open_in_browser(self, *args, **kwargs): - post = self.get_post() - if post == None: - return - url = "https://vk.com/id{user_id}".format(user_id=post["id"]) - webbrowser.open_new_tab(url) - -class requestsBuffer(peopleBuffer): - - def get_items(self, show_nextpage=False): - if self.can_get_items == False: return - retrieved = True - try: - ids = self.session.vk.client.friends.getRequests(*self.args, **self.kwargs) - except VkApiError as err: - log.error("Error {0}: {1}".format(err.code, err.error)) - retrieved = err.code - return retrieved - except: - log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) - return False - num = self.session.get_page(name=self.name, show_nextpage=show_nextpage, endpoint="get", parent_endpoint="users", count=1000, user_ids=", ".join([str(i) for i in ids["items"]]), fields="uid, first_name, last_name, last_seen") - if not hasattr(self, "tab"): - # Create GUI associated to this buffer. - self.create_tab(self.parent) - # Add name to the new control so we could look for it when needed. - self.tab.name = self.name - if show_nextpage == False: - if self.tab.list.get_count() > 0 and num > 0: - v = [i for i in self.session.db[self.name]["items"][:num]] - v.reverse() - [wx.CallAfter(self.insert, i, True) for i in v] - else: - [wx.CallAfter(self.insert, i) for i in self.session.db[self.name]["items"][:num]] - return retrieved - - def accept_friendship(self, *args, **kwargs): - """ Adds a person to a list of friends. This method is done for accepting someone else's friend request. - https://vk.com/dev/friends.add - """ - person = self.get_post() - if person == None: - return - result = self.session.vk.client.friends.add(user_id=person["id"]) - if result == 2: - msg = _("{0} {1} now is your friend.").format(person["first_name"], person["last_name"]) - pub.sendMessage("notify", message=msg) - self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) - self.tab.list.remove_item(self.tab.list.get_selected()) - - def decline_friendship(self, *args, **kwargs): - """ Declines a freind request. - https://vk.com/dev/friends.delete - """ - person = self.get_post() - if person == None: - return - result = self.session.vk.client.friends.delete(user_id=person["id"]) - if "out_request_deleted" in result: - msg = _("You've deleted the friends request to {0} {1}.").format(person["first_name"], person["last_name"]) - elif "in_request_deleted" in result: - msg = _("You've declined the friend request of {0} {1}.").format(person["first_name"], person["last_name"]) - pub.sendMessage("notify", message=msg) - self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) - self.tab.list.remove_item(self.tab.list.get_selected()) - - def keep_as_follower(self, *args, **kwargs): - """ Adds a person to The followers list of the current user. - https://vk.com/dev/friends.add - """ - person = self.get_post() - if person == None: - return - result = self.session.vk.client.friends.add(user_id=person["id"], follow=1) - if result == 2: - msg = _("{0} {1} is following you.").format(person["first_name"], person["last_name"]) - pub.sendMessage("notify", message=msg) - self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) - self.tab.list.remove_item(self.tab.list.get_selected()) - -class notificationBuffer(feedBuffer): - - def create_tab(self, parent): - self.tab = home.notificationTab(parent) - self.connect_events() - self.tab.name = self.name - - def onFocus(self, event, *args, **kwargs): - event.Skip() diff --git a/src/controller/buffers/__init__.py b/src/controller/buffers/__init__.py new file mode 100644 index 0000000..9d097dd --- /dev/null +++ b/src/controller/buffers/__init__.py @@ -0,0 +1,14 @@ +from .empty import emptyBuffer +from .home import homeBuffer as baseBuffer +from .wall import wallBuffer as feedBuffer +from .communityWall import communityWallBuffer as communityBuffer +from .communityBoard import communityBoardBuffer as topicBuffer +from .documents import documentsBuffer as documentBuffer +from .communityDocuments import communityDocumentsBuffer as documentCommunityBuffer +from .audio import audioBuffer +from .audioAlbum import audioAlbumBuffer as audioAlbum +from .video import videoBuffer +from .videoAlbum import videoAlbumBuffer as videoAlbum +from .chat import chatBuffer +from .people import peopleBuffer +from .friendRequests import friendRequestsBuffer as requestsBuffer diff --git a/src/controller/buffers/audio.py b/src/controller/buffers/audio.py new file mode 100644 index 0000000..c45fae5 --- /dev/null +++ b/src/controller/buffers/audio.py @@ -0,0 +1,309 @@ +# -*- coding: utf-8 -*- +import os +import logging +import webbrowser +import wx +import presenters +import views +import interactors +import widgetUtils +from pubsub import pub +from vk_api import upload +from mutagen.id3 import ID3 +from presenters import player +from wxUI.tabs import home +from sessionmanager import utils +from mysc.thread_utils import call_threaded +from wxUI import commonMessages, menus +from controller import selector +from .wall import wallBuffer + +log = logging.getLogger("controller.buffers.audio") + +class audioBuffer(wallBuffer): + def create_tab(self, parent): + self.tab = home.audioTab(parent) + self.tab.name = self.name + self.connect_events() + if self.name == "me_audio": + self.tab.post.Enable(True) + + def get_event(self, ev): + if ev.GetKeyCode() == wx.WXK_RETURN: + if len(self.tab.list.get_multiple_selection()) < 2: + event = "play_all" + else: + event = "play_audio" + else: + event = None + ev.Skip() + if event != None: + try: + getattr(self, event)(skip_pause=True) + except AttributeError: + pass + + def connect_events(self): + widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio) + widgetUtils.connect_event(self.tab.play_all, widgetUtils.BUTTON_PRESSED, self.play_all) + pub.subscribe(self.change_label, "playback-changed") + super(audioBuffer, self).connect_events() + + def play_audio(self, *args, **kwargs): + if player.player.check_is_playing() and not "skip_pause" in kwargs: + return pub.sendMessage("pause") + selected = self.tab.list.get_multiple_selection() + if len(selected) == 0: + return + elif len(selected) == 1: + pub.sendMessage("play", object=self.session.db[self.name]["items"][selected[0]]) + else: + selected_audios = [self.session.db[self.name]["items"][item] for item in selected] + pub.sendMessage("play-all", list_of_songs=selected_audios) + return True + + def play_next(self, *args, **kwargs): + selected = self.tab.list.get_selected() + if selected < 0: + selected = 0 + if self.tab.list.get_count() <= selected+1: + newpos = 0 + else: + newpos = selected+1 + self.tab.list.select_item(newpos) + self.play_audio() + + def play_previous(self, *args, **kwargs): + selected = self.tab.list.get_selected() + if selected <= 0: + selected = self.tab.list.get_count() + newpos = selected-1 + self.tab.list.select_item(newpos) + self.play_audio() + + def open_post(self, *args, **kwargs): + selected = self.tab.list.get_multiple_selection() + if len(selected) < 1: + return + audios = [self.session.db[self.name]["items"][audio] for audio in selected] + a = presenters.displayAudioPresenter(session=self.session, postObject=audios, interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) + + def play_all(self, *args, **kwargs): + selected = self.tab.list.get_selected() + if selected == -1: + selected = 0 + if self.name not in self.session.db: + return + audios = [i for i in self.session.db[self.name]["items"][selected:]] + pub.sendMessage("play-all", list_of_songs=audios) + return True + + def remove_buffer(self, mandatory=False): + if "me_audio" == self.name or "popular_audio" == self.name or "recommended_audio" == self.name: + output.speak(_("This buffer can't be deleted")) + return False + else: + if mandatory == False: + dlg = commonMessages.remove_buffer() + else: + dlg = widgetUtils.YES + if dlg == widgetUtils.YES: + self.session.db.pop(self.name) + return True + else: + return False + + def get_more_items(self, *args, **kwargs): + # Translators: Some buffers can't use the get previous item feature due to API limitations. + output.speak(_("This buffer doesn't support getting more items.")) + + def onFocus(self, event, *args, **kwargs): + event.Skip() + + def add_to_library(self, *args, **kwargs): + call_threaded(self._add_to_library, *args, **kwargs) + + def _add_to_library(self, *args, **kwargs): + selected = self.tab.list.get_multiple_selection() + if len(selected) < 1: + return + audios = [self.session.db[self.name]["items"][audio] for audio in selected] + errors_detected = 0 + for i in audios: + args = {} + args["audio_id"] = i["id"] + if "album_id" in i: + args["album_id"] = i["album_id"] + args["owner_id"] = i["owner_id"] + try: + audio = self.session.vk.client.audio.add(**args) + except VkApiError: + errors_detected = errors_detected + 1 + continue + if audio != None and int(audio) < 21: + errors_detected = errors_detected + 1 + if errors_detected == 0: + if len(selected) == 1: + msg = _("Audio added to your library") + elif len(selected) > 1 and len(selected) < 5: + msg = _("{0} audios were added to your library.").format(len(selected),) + else: + msg = _("{audios} audios were added to your library.").format(audios=len(selected),) + output.speak(msg) + else: + output.speak(_("{0} errors occurred while attempting to add {1} audios to your library.").format(errors_detected, len(selected))) + + def remove_from_library(self, *args, **kwargs): + call_threaded(self._remove_from_library, *args, **kwargs) + + def _remove_from_library(self, *args, **kwargs): + selected = self.tab.list.get_multiple_selection() + if len(selected) < 1: + return + audios = [self.session.db[self.name]["items"][audio] for audio in selected] + errors_detected = 0 + audios_removed = 0 + for i in range(0, len(selected)): + args = {} + args["audio_id"] = audios[i]["id"] + args["owner_id"] = self.session.user_id + result = self.session.vk.client.audio.delete(**args) + if int(result) != 1: + errors_dtected = errors_detected + 1 + else: + self.session.db[self.name]["items"].pop(selected[i]-audios_removed) + self.tab.list.remove_item(selected[i]-audios_removed) + audios_removed = audios_removed + 1 + if errors_detected == 0: + if len(selected) == 1: + msg = _("Audio removed.") + elif len(selected) > 1 and len(selected) < 5: + msg = _("{0} audios were removed.").format(len(selected),) + else: + msg = _("{audios} audios were removed.").format(audios=len(selected),) + output.speak(msg) + else: + output.speak(_("{0} errors occurred while attempting to remove {1} audios.").format(errors_detected, len(selected))) + + def move_to_album(self, *args, **kwargs): + if len(self.session.audio_albums) == 0: + return commonMessages.no_audio_albums() + album = selector.album(_("Select the album where you want to move this song"), self.session) + if album.item == None: + return + call_threaded(self._move_to_album, album.item, *args, **kwargs) + + def _move_to_album(self, album, *args, **kwargs): + selected = self.tab.list.get_multiple_selection() + if len(selected) < 1: + return + audios = [self.session.db[self.name]["items"][audio] for audio in selected] + errors_detected = 0 + for i in audios: + id = i["id"] + try: + response = self.session.vk.client.audio.add(playlist_id=album, audio_id=id, owner_id=i["owner_id"]) + except VkApiError: + errors_detected = errors_detected + 1 + if errors_detected == 0: + if len(selected) == 1: + msg = _("Audio added to playlist.") + elif len(selected) > 1 and len(selected) < 5: + msg = _("{0} audios were added to playlist.").format(len(selected),) + else: + msg = _("{audios} audios were added to playlist.").format(audios=len(selected),) + output.speak(msg) + else: + output.speak(_("{0} errors occurred while attempting to add {1} audios to your playlist.").format(errors_detected, len(selected))) + + def get_menu(self): + p = self.get_post() + if p == None: + return + m = menus.audioMenu() + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_post, menuitem=m.open) + widgetUtils.connect_event(m, widgetUtils.MENU, self.play_audio, menuitem=m.play) + widgetUtils.connect_event(m, widgetUtils.MENU, self.move_to_album, menuitem=m.move) + widgetUtils.connect_event(m, widgetUtils.MENU, self.download, menuitem=m.download) + widgetUtils.connect_event(m, widgetUtils.MENU, self.select_all, menuitem=m.select) + widgetUtils.connect_event(m, widgetUtils.MENU, self.deselect_all, menuitem=m.deselect) + # if owner_id is the current user, the audio is added to the user's audios. + if p["owner_id"] == self.session.user_id: + m.library.SetItemLabel(_("&Remove")) + widgetUtils.connect_event(m, widgetUtils.MENU, self.remove_from_library, menuitem=m.library) + else: + widgetUtils.connect_event(m, widgetUtils.MENU, self.add_to_library, menuitem=m.library) + return m + + def post(self, *args, **kwargs): + """ Uploads an audio to the current user's library from the computer. """ + file = self.tab.get_file_to_upload() + if file == None: + return + audio_tags = ID3(file) + if "TIT2" in audio_tags: + title = audio_tags["TIT2"].text[0] + else: + title = _("Untitled") + if "TPE1" in audio_tags: + artist = audio_tags["TPE1"].text[0] + else: + artist = _("Unknown artist") + uploader = upload.VkUpload(self.session.vk.session_object) + call_threaded(uploader.audio, file, title=title, artist=artist) + + def open_in_browser(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + url = "https://vk.com/audio{user_id}_{post_id}".format(user_id=post["owner_id"], post_id=post["id"]) + webbrowser.open_new_tab(url) + + def change_label(self, stopped): + if hasattr(self.tab, "play"): + if stopped == False: + self.tab.play.SetLabel(_("P&ause")) + else: + self.tab.play.SetLabel(_("P&lay")) + + def __del__(self): + pub.unsubscribe(self.change_label, "playback-changed") + + def download(self, *args, **kwargs): + selected = self.tab.list.get_multiple_selection() + if len(selected) < 1: + return + audios = [self.session.db[self.name]["items"][audio] for audio in selected] + if len(audios) == 0: + return + elif len(audios) == 1: + multiple = False + filename = utils.safe_filename("{0} - {1}.mp3".format(audios[0]["title"], audios[0]["artist"])) + else: + multiple = True + filename = "" # No default filename for multiple files. + path = self.tab.get_download_path(filename=filename, multiple=multiple) + self.download_threaded(path, multiple, audios) + + def download_threaded(self, path, multiple, audios): + if multiple == False: + url = audios[0]["url"] + pub.sendMessage("download-file", url=url, filename=path) + return + else: + downloads = [] + for i in audios: + filename = utils.safe_filename("{0} - {1}.mp3".format(i["title"], i["artist"])) + filepath = os.path.join(path, filename) + downloads.append((utils.transform_audio_url(i["url"]), filepath)) + pub.sendMessage("download-files", downloads=downloads) + + def select_all(self, *args, **kwargs): + items = self.tab.list.list.GetItemCount() + for i in range(0, items): + self.tab.list.list.SetItemImage(i, 1) + + def deselect_all(self, *args, **kwargs): + items = self.tab.list.list.GetItemCount() + for i in range(0, items): + self.tab.list.list.SetItemImage(i, 0) \ No newline at end of file diff --git a/src/controller/buffers/audioAlbum.py b/src/controller/buffers/audioAlbum.py new file mode 100644 index 0000000..8278c61 --- /dev/null +++ b/src/controller/buffers/audioAlbum.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +import logging +import wx +import widgetUtils +import output +from wxUI.tabs import home +from .audio import audioBuffer + +log = logging.getLogger("controller.buffers.audioPlaylist") + +class audioAlbumBuffer(audioBuffer): + """ this buffer was supposed to be used with audio albums + but is deprecated as VK removed its audio support for third party apps.""" + + def create_tab(self, parent): + self.tab = home.audioAlbumTab(parent) + self.tab.play.Enable(False) + self.tab.play_all.Enable(False) + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) + + def connect_events(self): + super(audioAlbumBuffer, self).connect_events() + widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_album) + + def load_album(self, *args, **kwargs): + output.speak(_("Loading album...")) + self.can_get_items = True + self.tab.load.Enable(False) + wx.CallAfter(self.get_items) + self.tab.play.Enable(True) + self.tab.play_all.Enable(True) \ No newline at end of file diff --git a/src/controller/buffers/chat.py b/src/controller/buffers/chat.py new file mode 100644 index 0000000..d6fa6a3 --- /dev/null +++ b/src/controller/buffers/chat.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +import time +import random +import logging +import webbrowser +import wx +import presenters +import views +import interactors +import widgetUtils +import output +from pubsub import pub +from vk_api.exceptions import VkApiError +from wxUI.tabs import home +from mysc.thread_utils import call_threaded +from wxUI import commonMessages, menus +from sessionmanager import renderers +from .home import homeBuffer + +log = logging.getLogger("controller.buffers.chat") + +class chatBuffer(homeBuffer): + + def insert(self, item, reversed=False): + """ Add a new item to the list. Uses session.composefunc for parsing the dictionary and create a valid result for putting it in the list.""" + # as this tab is based in a text control, we have to overwrite the defaults. + item_ = getattr(renderers, self.compose_function)(item, self.session) + # the self.chat dictionary will have (first_line, last_line) as keys and message ID as a value for looking into it when needed. + # Here we will get first and last line of a chat message appended to the history. + values = self.tab.add_message(item_[0], reverse=reversed) + self.chats[values] = item["id"] + + def get_focused_post(self): + """ Gets chat message currently in focus""" + # this function replaces self.get_post for this buffer, as we rely in a TextCtrl control for getting chats. + # Instead of the traditional method to do the trick. + # Get text position here. + position = self.tab.history.PositionToXY(self.tab.history.GetInsertionPoint()) + id_ = None + # The dictionary keys should be looked in reverse order as we are interested in the last result only. + for i in reversed(list(self.chats.keys())): + # Check if position[2] (line position) matches with something in self.chats + # (All messages, except the last one, should be able to be matched here). + # position[2]+1 is added because line may start with 0, while in wx.TextCtrl.GetNumberOfLines() it returns a value counting from 1. + if position[2]+1 >= i[0]: + id_ = self.chats[i] + # If we find our chat message, let's skip the rest of the loop. + break + # Retrieve here the object based in id_ + if id_ != None: + for i in self.session.db[self.name]["items"]: + if i["id"] == id_: + return i + return False + + get_post = get_focused_post + + def onFocus(self, event, *args, **kwargs): + if event.GetKeyCode() == wx.WXK_UP or event.GetKeyCode() == wx.WXK_DOWN or event.GetKeyCode() == wx.WXK_START or event.GetKeyCode() == wx.WXK_PAGEUP or event.GetKeyCode() == wx.WXK_PAGEDOWN or event.GetKeyCode() == wx.WXK_END: + msg = self.get_focused_post() + if msg == False: # Handle the case where the last line of the control cannot be matched to anything. + return + # Mark unread conversations as read. + if "read_state" in msg and msg["read_state"] == 0 and "out" in msg and msg["out"] == 0: + self.session.soundplayer.play("message_unread.ogg") + call_threaded(self.session.vk.client.messages.markAsRead, peer_id=self.kwargs["peer_id"]) + [i.update(read_state=1) for i in self.session.db[self.name]["items"]] + if "attachments" in msg and len(msg["attachments"]) > 0: + self.tab.attachments.list.Enable(True) + self.attachments = list() + self.tab.attachments.clear() + self.parse_attachments(msg) + else: + self.tab.attachments.list.Enable(False) + self.tab.attachments.clear() + event.Skip() + + def create_tab(self, parent): + self.tab = home.chatTab(parent) + self.attachments = list() + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) + + def connect_events(self): + widgetUtils.connect_event(self.tab.send, widgetUtils.BUTTON_PRESSED, self.send_chat_to_user) + widgetUtils.connect_event(self.tab.attachment, widgetUtils.BUTTON_PRESSED, self.add_attachment) + widgetUtils.connect_event(self.tab.text, widgetUtils.KEYPRESS, self.catch_enter) + self.tab.set_focus_function(self.onFocus) + + def catch_enter(self, event, *args, **kwargs): + shift=event.ShiftDown() + if event.GetKeyCode() == wx.WXK_RETURN and shift == False: + return self.send_chat_to_user() + t = time.time() + if event.GetUnicodeKey() != wx.WXK_NONE and t-self.last_keypress > 5: + self.last_keypress = t + call_threaded(self.session.vk.client.messages.setActivity, peer_id=self.kwargs["peer_id"], type="typing") + event.Skip() + + def get_items(self, show_nextpage=False): + """ Update buffer with newest items or get older items in the buffer.""" + if self.can_get_items == False: return + retrieved = True + try: + num = getattr(self.session, "get_page")(show_nextpage=show_nextpage, name=self.name, *self.args, **self.kwargs) + except VkApiError as err: + log.error("Error {0}: {1}".format(err.code, err.error)) + retrieved = err.code + return retrieved + except: + log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) + return False + if not hasattr(self, "tab"): + # Create GUI associated to this buffer. + self.create_tab(self.parent) + # Add name to the new control so we could look for it when needed. + self.tab.name = self.name + + if show_nextpage == False: + if self.tab.history.GetValue() != "" and num > 0: + v = [i for i in self.session.db[self.name]["items"][:num]] + [self.insert(i, False) for i in v] + else: + [self.insert(i) for i in self.session.db[self.name]["items"][:num]] + else: + if num > 0: + # At this point we save more CPU and mathematical work if we just delete everything in the chat history and readd all messages. + # Otherwise we'd have to insert new lines at the top and recalculate positions everywhere else. + # Firstly, we'd have to save the current focused object so we will place the user in the right part of the text after loading everything again. + focused_post = self.get_post() + self.chats = dict() + wx.CallAfter(self.tab.history.SetValue, "") + v = [i for i in self.session.db[self.name]["items"]] + [self.insert(i) for i in v] + # Now it's time to set back the focus in the post. + for i in self.chats.keys(): + if self.chats[i] == focused_post["id"]: + line = i[0] + wx.CallAfter(self.tab.history.SetInsertionPoint, self.tab.history.XYToPosition(0, line)) + output.speak(_("Items loaded")) + break + if self.unread == True and num > 0: + self.session.db[self.name]["items"][-1].update(read_state=0) + return retrieved + + def get_more_items(self): + output.speak(_("Getting more items...")) + call_threaded(self.get_items, show_nextpage=True) + + def add_attachment(self, *args, **kwargs): + a = presenters.attachPresenter(session=self.session, view=views.attachDialog(voice_messages=True), interactor=interactors.attachInteractor()) + if len(a.attachments) != 0: + self.attachments_to_be_sent = a.attachments + + def send_chat_to_user(self, *args, **kwargs): + text = self.tab.text.GetValue() + if text == "" and not hasattr(self, "attachments_to_be_sent"): + wx.Bell() + return + self.tab.text.SetValue("") + post_arguments = dict(random_id = random.randint(0, 100000), peer_id=self.kwargs["peer_id"]) + if len(text) > 0: + post_arguments.update(message=text) + if hasattr(self, "attachments_to_be_sent") and len(self.attachments_to_be_sent) > 0: + attachments = self.attachments_to_be_sent[::] + else: + attachments = [] + call_threaded(pub.sendMessage, "post", parent_endpoint="messages", child_endpoint="send", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) + if hasattr(self, "attachments_to_be_sent"): + del self.attachments_to_be_sent + + def __init__(self, unread=False, *args, **kwargs): + super(chatBuffer, self).__init__(*args, **kwargs) + self.unread = unread + self.chats = dict() + self.peer_typing = 0 + self.last_keypress = time.time() + + def parse_attachments(self, post): + attachments = [] + + if "attachments" in post: + for i in post["attachments"]: + # We don't need the photos_list attachment, so skip it. + if i["type"] == "photos_list": + continue + try: + rendered_object = add_attachment(i) + except: + log.exception("Error parsing the following attachment on chat: %r" % (i,)) + attachments.append(rendered_object) + self.attachments.append(i) + self.tab.attachments.list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.open_attachment) + self.tab.insert_attachments(attachments) + + def open_attachment(self, *args, **kwargs): + index = self.tab.attachments.get_selected() + attachment = self.attachments[index] + if attachment["type"] == "audio": + a = presenters.displayAudioPresenter(session=self.session, postObject=[attachment["audio"]], interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) + elif attachment["type"] == "audio_message": + link = attachment["audio_message"]["link_mp3"] + pub.sendMessage("play-message", message_url=link) + elif attachment["type"] == "link": + output.speak(_("Opening URL..."), True) + webbrowser.open_new_tab(attachment["link"]["url"]) + elif attachment["type"] == "doc": + output.speak(_("Opening document in web browser...")) + webbrowser.open(attachment["doc"]["url"]) + elif attachment["type"] == "video": + # it seems VK doesn't like to attach video links as normal URLS, so we'll have to + # get the full video object and use its "player" key which will open a webbrowser in their site with a player for the video. + # see https://vk.com/dev/attachments_w and and https://vk.com/dev/video.get + # However, the flash player isn't good for visually impaired people (when you press play you won't be able to close the window with alt+f4), so it could be good to use the HTML5 player. + # For firefox, see https://addons.mozilla.org/ru/firefox/addon/force-html5-video-player-at-vk/ + # May be I could use a dialogue here for inviting people to use this addon in firefox. It seems it isn't possible to use this html5 player from the player URL. + object_id = "{0}_{1}".format(attachment["video"]["owner_id"], attachment["video"]["id"]) + video_object = self.session.vk.client.video.get(owner_id=attachment["video"]["owner_id"], videos=object_id) + video_object = video_object["items"][0] + output.speak(_("Opening video in web browser..."), True) + webbrowser.open_new_tab(video_object["player"]) + elif attachment["type"] == "photo": + output.speak(_("Opening photo in web browser..."), True) + # Possible photo sizes for looking in the attachment information. Try to use the biggest photo available. + possible_sizes = [1280, 604, 130, 75] + url = "" + for i in possible_sizes: + if "photo_{0}".format(i,) in attachment["photo"]: + url = attachment["photo"]["photo_{0}".format(i,)] + break + if url != "": + webbrowser.open_new_tab(url) + if attachment["type"] == "wall": + pub.sendMessage("open-post", post_object=attachment["wall"], controller_="displayPost") + else: + log.debug("Unhandled attachment: %r" % (attachment,)) + + def clear_reads(self): + for i in self.session.db[self.name]["items"]: + if "read_state" in i and i["read_state"] == 0: + i["read_state"] = 1 + + def remove_buffer(self, mandatory=False): + """ Remove buffer if the current buffer is not the logged user's wall.""" + if mandatory == False: + dlg = commonMessages.remove_buffer() + else: + dlg = widgetUtils.YES + if dlg == widgetUtils.YES: + self.session.db.pop(self.name) + return True + else: + return False + + def open_in_browser(self, *args, **kwargs): + peer_id = self.kwargs["peer_id"] + url = "https://vk.com/im?sel={peer_id}".format(peer_id=peer_id) + webbrowser.open_new_tab(url) \ No newline at end of file diff --git a/src/controller/buffers/communityBoard.py b/src/controller/buffers/communityBoard.py new file mode 100644 index 0000000..3d3ddfe --- /dev/null +++ b/src/controller/buffers/communityBoard.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +""" A buffer is a (virtual) list of items. All items belong to a category (wall posts, messages, persons...)""" +import logging +import webbrowser +import wx +import presenters +import views +import interactors +import widgetUtils +from pubsub import pub +from wxUI.tabs import home +from mysc.thread_utils import call_threaded +from .wall import wallBuffer + +log = logging.getLogger("controller.buffers.communityBoard") + +class communityBoardBuffer(wallBuffer): + + def create_tab(self, parent): + self.tab = home.topicTab(parent) + self.connect_events() + self.tab.name = self.name + if "can_create_topic" not in self.session.db["group_info"][self.kwargs["group_id"]*-1] or ("can_create_topic" in self.session.db["group_info"][self.kwargs["group_id"]*-1] and self.session.db["group_info"][self.kwargs["group_id"]*-1]["can_create_topic"] != True): + self.tab.post.Enable(False) + + def onFocus(self, event, *args, **kwargs): + event.Skip() + + def open_post(self, *args, **kwargs): + """ Opens the currently focused post.""" + post = self.get_post() + if post == None: + return + a = presenters.displayTopicPresenter(session=self.session, postObject=post, group_id=self.kwargs["group_id"], interactor=interactors.displayPostInteractor(), view=views.displayTopic()) + + def open_in_browser(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + # In order to load the selected topic we firstly have to catch the group_id, which is present in self.kwargs + # After getting the group_id we should make it negative + group_id = self.kwargs["group_id"]*-1 + url = "https://vk.com/topic{group_id}_{topic_id}".format(group_id=group_id, topic_id=post["id"]) + webbrowser.open_new_tab(url) + + def post(self, *args, **kwargs): + menu = wx.Menu() + user1 = self.session.get_user(self.session.user_id) + user2 = self.session.get_user(-1*self.kwargs["group_id"]) + user = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user1)) + group = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user2)) + menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 1), group) + menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 0), user) + self.tab.post.PopupMenu(menu, self.tab.post.GetPosition()) + + def _post(self, event, from_group): + owner_id = self.kwargs["group_id"] + user = self.session.get_user(-1*owner_id, key="user1") + title = _("Create topic in {user1_nom}").format(**user) + p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createTopicDialog(title=title, message="", text="", topic_title="")) + if hasattr(p, "text") or hasattr(p, "privacy"): + title = p.view.title.GetValue() + msg = p.text + post_arguments = dict(title=title, text=msg, group_id=owner_id, from_group=from_group) + attachments = [] + if hasattr(p, "attachments"): + attachments = p.attachments + call_threaded(pub.sendMessage, "post", parent_endpoint="board", child_endpoint="addTopic", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) \ No newline at end of file diff --git a/src/controller/buffers/communityDocuments.py b/src/controller/buffers/communityDocuments.py new file mode 100644 index 0000000..aa54398 --- /dev/null +++ b/src/controller/buffers/communityDocuments.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +import logging +from wxUI.tabs import home +from .documents import documentsBuffer + +log = logging.getLogger("controller.buffers.communityDocuments") + +class communityDocumentsBuffer(documentsBuffer): + can_get_items = True + + def create_tab(self, parent): + self.tab = home.documentCommunityTab(parent) + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) \ No newline at end of file diff --git a/src/controller/buffers/communityWall.py b/src/controller/buffers/communityWall.py new file mode 100644 index 0000000..0475d6f --- /dev/null +++ b/src/controller/buffers/communityWall.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" A buffer is a (virtual) list of items. All items belong to a category (wall posts, messages, persons...)""" +import logging +import wx +import presenters +import views +import interactors +import widgetUtils +import output +from pubsub import pub +from wxUI.tabs import home +from mysc.thread_utils import call_threaded +from .wall import wallBuffer + +log = logging.getLogger("controller.buffers.communityWall") + +class communityWallBuffer(wallBuffer): + + def __init__(self, *args, **kwargs): + super(communityWallBuffer, self).__init__(*args, **kwargs) + self.group_id = self.kwargs["owner_id"] + + def create_tab(self, parent): + self.tab = home.communityTab(parent) + self.connect_events() + self.tab.name = self.name + self.tab.post.Enable(False) + + def connect_events(self): + super(communityWallBuffer, self).connect_events() + widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_community) + + def load_community(self, *args, **kwargs): + output.speak(_("Loading community...")) + self.can_get_items = True + self.tab.load.Enable(False) + wx.CallAfter(self.get_items) + + def get_items(self, *args, **kwargs): + """ This method retrieves community information, useful to show different parts of the community itself.""" + if self.can_get_items: + # Strangely, groups.get does not return counters so we need those to show options for loading specific posts for communities. + group_info = self.session.vk.client.groups.getById(group_ids=-1*self.kwargs["owner_id"], fields="counters")[0] + self.session.db["group_info"][self.group_id].update(group_info) + if "can_post" in self.session.db["group_info"][self.group_id] and self.session.db["group_info"][self.group_id]["can_post"] == True: + self.tab.post.Enable(True) + super(communityWallBuffer, self).get_items(*args, **kwargs) + + def post(self, *args, **kwargs): + menu = wx.Menu() + user1 = self.session.get_user(self.session.user_id) + user2 = self.session.get_user(self.kwargs["owner_id"]) + user = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user1)) + group = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user2)) + menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 1), group) + menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 0), user) + self.tab.post.PopupMenu(menu, self.tab.post.GetPosition()) + + def _post(self, event, from_group): + owner_id = self.kwargs["owner_id"] + user = self.session.get_user(owner_id, key="user1") + title = _("Post to {user1_nom}'s wall").format(**user) + p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=title, message="", text="")) + if hasattr(p, "text") or hasattr(p, "privacy"): + post_arguments=dict(privacy=p.privacy, message=p.text, owner_id=owner_id, from_group=from_group) + attachments = [] + if hasattr(p, "attachments"): + attachments = p.attachments + call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) \ No newline at end of file diff --git a/src/controller/buffers/documents.py b/src/controller/buffers/documents.py new file mode 100644 index 0000000..0e9c49f --- /dev/null +++ b/src/controller/buffers/documents.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +""" A buffer is a (virtual) list of items. All items belong to a category (wall posts, messages, persons...)""" +import logging +import webbrowser +import arrow +import wx +import languageHandler +import widgetUtils +import output +from pubsub import pub +from wxUI.tabs import home +from sessionmanager import utils +from mysc.thread_utils import call_threaded +from wxUI import menus +from .wall import wallBuffer + +log = logging.getLogger("controller.buffers.documents") + +class documentsBuffer(wallBuffer): + can_get_items = False + + def create_tab(self, parent): + self.tab = home.documentTab(parent) + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) + + def onFocus(self, event, *args,**kwargs): + post = self.get_post() + if post == None: + return + original_date = arrow.get(post["date"]) + created_at = original_date.humanize(locale=languageHandler.curLang[:2]) + self.tab.list.list.SetItem(self.tab.list.get_selected(), 4, created_at) + event.Skip() + + def connect_events(self): + super(documentsBuffer, self).connect_events() + # Check if we have a load button in the tab, because documents community buffers don't include it. + if hasattr(self.tab, "load"): + widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_documents) + + def load_documents(self, *args, **kwargs): + output.speak(_("Loading documents...")) + self.can_get_items = True + self.tab.load.Enable(False) + wx.CallAfter(self.get_items) + + def get_menu(self): + p = self.get_post() + if p == None: + return + if p["owner_id"] == self.session.user_id: + added = True + else: + added = False + m = menus.documentMenu(added) + widgetUtils.connect_event(m, widgetUtils.MENU, self.add_remove_document, menuitem=m.action) + widgetUtils.connect_event(m, widgetUtils.MENU, self.download, menuitem=m.download) + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_in_browser, menuitem=m.open_in_browser) + return m + + def add_remove_document(self, *args, **kwargs): + p = self.get_post() + if p == None: + return + if p["owner_id"] == self.session.user_id: + result = self.session.vk.client.docs.delete(owner_id=p["owner_id"], doc_id=p["id"]) + if result == 1: + output.speak(_("The document has been successfully deleted.")) + self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) + self.tab.list.remove_item(self.tab.list.get_selected()) + else: + result = self.session.vk.client.docs.add(owner_id=p["owner_id"], doc_id=p["id"]) + output.speak(_("The document has been successfully added.")) + + def download(self, *args, **kwargs): + post = self.get_post() + filename = utils.safe_filename(post["title"]) + # If document does not end in .extension we must fix it so the file dialog will save it properly later. + if filename.endswith(post["ext"]) == False: + filename = filename+ "."+post["ext"] + filepath = self.tab.get_download_path(filename) + if filepath != None: + pub.sendMessage("download-file", url=post["url"], filename=filepath) + + def open_in_browser(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + url = "https://vk.com/doc{user_id}_{post_id}".format(user_id=post["owner_id"], post_id=post["id"]) + webbrowser.open_new_tab(url) \ No newline at end of file diff --git a/src/controller/buffers/empty.py b/src/controller/buffers/empty.py new file mode 100644 index 0000000..bcf7c12 --- /dev/null +++ b/src/controller/buffers/empty.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" A buffer is a (virtual) list of items. All items belong to a category (wall posts, messages, persons...)""" +import logging +import output +from wxUI.tabs import home + +log = logging.getLogger("controller.buffers.empty") + +class emptyBuffer(object): + + def __init__(self, name=None, parent=None, *args, **kwargs): + self.tab = home.empty(parent=parent, name=name) + self.name = name + + def get_items(self, *args, **kwargs): + if not hasattr(self, "tab"): + # Create GUI associated to this buffer. + self.create_tab(self.parent) + # Add name to the new control so we could look for it when needed. + self.tab.name = self.name + pass + + def get_more_items(self, *args, **kwargs): + output.speak(_("This buffer doesn't support getting more items.")) + + def remove_buffer(self, mandatory=False): + return False \ No newline at end of file diff --git a/src/controller/buffers/friendRequests.py b/src/controller/buffers/friendRequests.py new file mode 100644 index 0000000..cfb061d --- /dev/null +++ b/src/controller/buffers/friendRequests.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +import logging +import wx +from pubsub import pub +from vk_api.exceptions import VkApiError +from .people import peopleBuffer + +log = logging.getLogger("controller.buffers.friendRequests") + +class friendRequestsBuffer(peopleBuffer): + + def get_items(self, show_nextpage=False): + if self.can_get_items == False: return + retrieved = True + try: + ids = self.session.vk.client.friends.getRequests(*self.args, **self.kwargs) + except VkApiError as err: + log.error("Error {0}: {1}".format(err.code, err.error)) + retrieved = err.code + return retrieved + except: + log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) + return False + num = self.session.get_page(name=self.name, show_nextpage=show_nextpage, endpoint="get", parent_endpoint="users", count=1000, user_ids=", ".join([str(i) for i in ids["items"]]), fields="uid, first_name, last_name, last_seen") + if not hasattr(self, "tab"): + # Create GUI associated to this buffer. + self.create_tab(self.parent) + # Add name to the new control so we could look for it when needed. + self.tab.name = self.name + if show_nextpage == False: + if self.tab.list.get_count() > 0 and num > 0: + v = [i for i in self.session.db[self.name]["items"][:num]] + v.reverse() + [wx.CallAfter(self.insert, i, True) for i in v] + else: + [wx.CallAfter(self.insert, i) for i in self.session.db[self.name]["items"][:num]] + return retrieved + + def accept_friendship(self, *args, **kwargs): + """ Adds a person to a list of friends. This method is done for accepting someone else's friend request. + https://vk.com/dev/friends.add + """ + person = self.get_post() + if person == None: + return + result = self.session.vk.client.friends.add(user_id=person["id"]) + if result == 2: + msg = _("{0} {1} now is your friend.").format(person["first_name"], person["last_name"]) + pub.sendMessage("notify", message=msg) + self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) + self.tab.list.remove_item(self.tab.list.get_selected()) + + def decline_friendship(self, *args, **kwargs): + """ Declines a freind request. + https://vk.com/dev/friends.delete + """ + person = self.get_post() + if person == None: + return + result = self.session.vk.client.friends.delete(user_id=person["id"]) + if "out_request_deleted" in result: + msg = _("You've deleted the friends request to {0} {1}.").format(person["first_name"], person["last_name"]) + elif "in_request_deleted" in result: + msg = _("You've declined the friend request of {0} {1}.").format(person["first_name"], person["last_name"]) + pub.sendMessage("notify", message=msg) + self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) + self.tab.list.remove_item(self.tab.list.get_selected()) + + def keep_as_follower(self, *args, **kwargs): + """ Adds a person to The followers list of the current user. + https://vk.com/dev/friends.add + """ + person = self.get_post() + if person == None: + return + result = self.session.vk.client.friends.add(user_id=person["id"], follow=1) + if result == 2: + msg = _("{0} {1} is following you.").format(person["first_name"], person["last_name"]) + pub.sendMessage("notify", message=msg) + self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) + self.tab.list.remove_item(self.tab.list.get_selected()) \ No newline at end of file diff --git a/src/controller/buffers/home.py b/src/controller/buffers/home.py new file mode 100644 index 0000000..f213f2d --- /dev/null +++ b/src/controller/buffers/home.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- +import logging +import webbrowser +import arrow +import wx +import presenters +import views +import interactors +import languageHandler +import widgetUtils +import output +from pubsub import pub +from vk_api.exceptions import VkApiError +from presenters import player +from wxUI.tabs import home +from sessionmanager import session, renderers +from mysc.thread_utils import call_threaded +from wxUI import commonMessages, menus + +log = logging.getLogger("controller.buffers.home") + +class homeBuffer(object): + """ a basic representation of a buffer. Other buffers should be derived from this class. This buffer represents the "news feed" """ + + def get_post(self): + """ Return the currently focused post.""" + # Handle case where there are no items in the buffer. + if self.tab.list.get_count() == 0: + wx.Bell() + return None + return self.session.db[self.name]["items"][self.tab.list.get_selected()] + + def __init__(self, parent=None, name="", session=None, composefunc=None, create_tab=True, *args, **kwargs): + """ Constructor: + @parent wx.Treebook: parent for the buffer panel, + @name str: Name for saving this buffer's data in the local storage variable, + @session sessionmanager.session.vkSession: Session for performing operations in the Vk API. This session should be logged in when this class is instanciated. + @composefunc str: This function will be called for composing the result which will be put in the listCtrl. Composefunc should exist in the sessionmanager.renderers module. + args and kwargs will be passed to get_items() without any filtering. Be careful there. + """ + super(homeBuffer, self).__init__() + self.parent = parent + self.args = args + self.kwargs = kwargs + self.session = session + self.compose_function = composefunc + self.name = name + if create_tab: + self.create_tab(self.parent) + #Update_function will be called every 3 minutes and it should be able to + # Get all new items in the buffer and sort them properly in the CtrlList. + # ToDo: Shall we allow dinamically set for update_function? + self.update_function = "get_page" + self.name = name + # source_key and post_key will point to the keys for sender and posts in VK API objects. + # They can be changed in the future for other item types in different buffers. + self.user_key = "source_id" + self.post_key = "post_id" + # When set to False, update_function won't be executed here. + self.can_get_items = True + + def create_tab(self, parent): + """ Create the Wx panel.""" + self.tab = home.homeTab(parent) + # Bind local events (they will respond to events happened in the buffer). + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) + + def insert(self, item, reversed=False): + """ Add a new item to the list. Uses renderers.composefunc for parsing the dictionary and create a valid result for putting it in the list.""" + try: + item_ = getattr(renderers, self.compose_function)(item, self.session) + wx.CallAfter(self.tab.list.insert_item, reversed, *item_) + except: + log.exception(item) + + def get_items(self, show_nextpage=False): + """ Retrieve items from the VK API. This function is called repeatedly by the main controller and users could call it implicitly as well with the update buffer option. + @show_nextpage boolean: If it's true, it will try to load previous results. + """ + if self.can_get_items == False: return + retrieved = True # Control variable for handling unauthorised/connection errors. + try: + num = getattr(self.session, "get_newsfeed")(show_nextpage=show_nextpage, name=self.name, *self.args, **self.kwargs) + except VkApiError as err: + log.error("Error {0}: {1}".format(err.code, err.error)) + retrieved = err.code + return retrieved + except: + log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) + return False + if not hasattr(self, "tab"): + # Create GUI associated to this buffer. + self.create_tab(self.parent) + # Add name to the new control so we could look for it when needed. + if show_nextpage == False: + if self.tab.list.get_count() > 0 and num > 0: + v = [i for i in self.session.db[self.name]["items"][:num]] + v.reverse() + [wx.CallAfter(self.insert, i, True) for i in v] + else: + [wx.CallAfter(self.insert, i) for i in self.session.db[self.name]["items"][:num]] + else: + if num > 0: + [wx.CallAfter(self.insert, i, False) for i in self.session.db[self.name]["items"][-num:]] + return retrieved + + def get_more_items(self): + """ Returns previous items in the buffer.""" + self.get_items(show_nextpage=True) + + def post(self, *args, **kwargs): + """ Create a post in the current user's wall. + This process is handled in two parts. This is the first part, where the GUI is created and user can send the post. + During the second part (threaded), the post will be sent to the API.""" + p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=_("Write your post"), message="", text="")) + if hasattr(p, "text") or hasattr(p, "privacy"): + post_arguments=dict(privacy=p.privacy, message=p.text) + attachments = [] + if hasattr(p, "attachments"): + attachments = p.attachments + call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) + + def connect_events(self): + """ Bind all events to this buffer""" + widgetUtils.connect_event(self.tab.post, widgetUtils.BUTTON_PRESSED, self.post) + widgetUtils.connect_event(self.tab.list.list, widgetUtils.KEYPRESS, self.get_event) + widgetUtils.connect_event(self.tab.list.list, wx.EVT_CONTEXT_MENU, self.show_menu) + self.tab.set_focus_function(self.onFocus) + + def show_menu(self, ev, pos=0, *args, **kwargs): + """ Show contextual menu when pressing menu key or right mouse click in a list item.""" + if self.tab.list.get_count() == 0: return + menu = self.get_menu() + if pos != 0: + self.tab.PopupMenu(menu, pos) + else: + self.tab.PopupMenu(menu, self.tab.list.list.GetPosition()) + + def show_menu_by_key(self, ev): + """ Show contextual menu when menu key is pressed""" + if self.tab.list.get_count() == 0: + return + if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU: + self.show_menu(widgetUtils.MENU, pos=self.tab.list.list.GetPosition()) + + def get_menu(self): + """ Returns contextual menu options. They will change according to the focused item""" + p = self.get_post() + if p == None: + return + # determine if the current user is able to delete the object. + if "can_delete" in p: + can_delete = True==p["can_delete"] + else: + can_delete = False + m = menus.postMenu(can_delete=can_delete) + if ("likes" in p) == False: + m.like.Enable(False) + elif p["likes"]["user_likes"] == 1: + m.like.Enable(False) + m.dislike.Enable(True) + if ("comments" in p) == False: + m.comment.Enable(False) + m.open_in_browser.Enable(False) + if "type" in p and p["type"] != "friend" and p["type"] != "audio" and p["type"] != "video" and p["type"] != "playlist" or self.name != "home_timeline": + m.open_in_browser.Enable(True) + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_post, menuitem=m.open) + widgetUtils.connect_event(m, widgetUtils.MENU, self.do_like, menuitem=m.like) + widgetUtils.connect_event(m, widgetUtils.MENU, self.do_dislike, menuitem=m.dislike) + widgetUtils.connect_event(m, widgetUtils.MENU, self.do_comment, menuitem=m.comment) + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_in_browser, menuitem=m.open_in_browser) + if hasattr(m, "view_profile"): + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_person_profile, menuitem=m.view_profile) + if hasattr(m, "delete"): + widgetUtils.connect_event(m, widgetUtils.MENU, self.delete, menuitem=m.delete) + return m + + def do_like(self, *args, **kwargs): + """ Set like in the currently focused post.""" + post = self.get_post() + if post == None: + return + user = post[self.user_key] + id = post[self.post_key] + if "type" in post: + type_ = post["type"] + else: + type_ = "post" + l = self.session.vk.client.likes.add(owner_id=user, item_id=id, type=type_) + self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["count"] = l["likes"] + self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["user_likes"] = 1 + # Translators: This will be used when user presses like. + output.speak(_("You liked this")) + + def do_dislike(self, *args, **kwargs): + """ Set dislike (undo like) in the currently focused post.""" + post = self.get_post() + if post == None: + return + user = post[self.user_key] + id = post[self.post_key] + if "type" in post: + type_ = post["type"] + else: + type_ = "post" + l = self.session.vk.client.likes.delete(owner_id=user, item_id=id, type=type_) + self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["count"] = l["likes"] + self.session.db[self.name]["items"][self.tab.list.get_selected()]["likes"]["user_likes"] = 2 + # Translators: This will be user in 'dislike' + output.speak(_("You don't like this")) + + def do_comment(self, *args, **kwargs): + """ Make a comment into the currently focused post.""" + post = self.get_post() + if post == None: + return + comment = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=_("Add a comment"), message="", text="", mode="comment")) + if hasattr(comment, "text") or hasattr(comment, "privacy"): + msg = comment.text + try: + user = post[self.user_key] + id = post[self.post_key] + self.session.vk.client.wall.addComment(owner_id=user, post_id=id, text=msg) + output.speak(_("You've posted a comment")) + except Exception as msg: + log.error(msg) + + def delete(self, *args, **kwargs): + post = self.get_post() + if ("type" in post and post["type"] == "post") or self.name != "newsfeed": + question = commonMessages.remove_post() + if question == widgetUtils.NO: + return + if "owner_id" in self.kwargs: + result = self.session.vk.client.wall.delete(owner_id=self.kwargs["owner_id"], post_id=post[self.post_key]) + else: + result = self.session.vk.client.wall.delete(post_id=post[self.post_key]) + pub.sendMessage("post_deleted", post_id=post[self.post_key]) + self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) + self.tab.list.remove_item(self.tab.list.get_selected()) + + def get_event(self, ev): + """ Parses keyboard input in the ListCtrl and executes the event associated with user keypresses.""" + if ev.GetKeyCode() == wx.WXK_RETURN: event = "open_post" + else: + event = None + ev.Skip() + if event != None: + try: + getattr(self, event)() + except AttributeError: + pass + + def volume_down(self): + """ Decreases player volume by 2%""" + player.player.volume = player.player.volume-2 + + def volume_up(self): + """ Increases player volume by 2%""" + player.player.volume = player.player.volume+2 + + def play_audio(self, *args, **kwargs): + """ Play audio in currently focused buffer, if possible.""" + post = self.get_post() + if post == None: + return + if "type" in post and post["type"] == "audio": + pub.sendMessage("play", object=post["audio"]["items"][0]) + return True + + def open_person_profile(self, *args, **kwargs): + """ Views someone's profile.""" + selected = self.get_post() + if selected == None: + return + # Check all possible keys for an user object in VK API. + keys = ["from_id", "source_id", "id"] + for i in keys: + if i in selected: + pub.sendMessage("user-profile", person=selected[i]) + break + + def open_post(self, *args, **kwargs): + """ Opens the currently focused post.""" + post = self.get_post() + if post == None: + return + if "type" in post and post["type"] == "audio": + a = presenters.displayAudioPresenter(session=self.session, postObject=post["audio"]["items"], interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) + elif "type" in post and post["type"] == "friend": + pub.sendMessage("open-post", post_object=post, controller_="displayFriendship", vars=dict(caption=_("{user1_nom} added the following friends"))) + else: + pub.sendMessage("open-post", post_object=post, controller_="displayPost") + + def pause_audio(self, *args, **kwargs): + """ pauses audio playback.""" + pub.sendMessage("pause") + + def remove_buffer(self, mandatory): + """ Function for removing a buffer. Returns True if removal is successful, False otherwise""" + return False + + def get_users(self): + """ Returns source user in the post.""" + post = self.get_post() + if post == None: + return + if ("type" in post) == False: + return [post["from_id"]] + else: + return [post["source_id"]] + + def onFocus(self, event, *args,**kwargs): + """ Function executed when the item in a list is selected. + For this buffer it updates the date of posts in the list.""" + post = self.get_post() + if post == None: + return + original_date = arrow.get(post["date"]) + created_at = original_date.humanize(locale=languageHandler.curLang[:2]) + self.tab.list.list.SetItem(self.tab.list.get_selected(), 2, created_at) + event.Skip() + + def open_in_browser(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + url = "https://vk.com/wall{user_id}_{post_id}".format(user_id=post["source_id"], post_id=post["post_id"]) + webbrowser.open_new_tab(url) \ No newline at end of file diff --git a/src/controller/buffers/notification.py b/src/controller/buffers/notification.py new file mode 100644 index 0000000..3e04e14 --- /dev/null +++ b/src/controller/buffers/notification.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +import logging +from wxUI.tabs import home +from .wall import wallBuffer + +log = logging.getLogger("controller.buffers") + +class notificationBuffer(wallBuffer): + + def create_tab(self, parent): + self.tab = home.notificationTab(parent) + self.connect_events() + self.tab.name = self.name + + def onFocus(self, event, *args, **kwargs): + event.Skip() diff --git a/src/controller/buffers/people.py b/src/controller/buffers/people.py new file mode 100644 index 0000000..d98d4dd --- /dev/null +++ b/src/controller/buffers/people.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +""" A buffer is a (virtual) list of items. All items belong to a category (wall posts, messages, persons...)""" +import time +import logging +import webbrowser +import arrow +import wx +import presenters +import views +import interactors +import languageHandler +import widgetUtils +from pubsub import pub +from wxUI.tabs import home +from wxUI.dialogs import timeline +from mysc.thread_utils import call_threaded +from wxUI import commonMessages, menus +from .wall import wallBuffer + +log = logging.getLogger("controller.buffers.friends") + +class peopleBuffer(wallBuffer): + + def post(self, *args, **kwargs): + user = self.get_post() + if "can_post" not in user: # retrieve data if not present in the object. + user = self.session.vk.client.users.get(user_ids=user["id"], fields="can_post")[0] + if user.get("can_post") == True: + user_str = self.session.get_user(user["id"], key="user1") + title = _("Post to {user1_nom}'s wall").format(**user_str) + p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=title, message="", text="")) + if hasattr(p, "text") or hasattr(p, "privacy"): + post_arguments=dict(privacy=p.privacy, message=p.text, owner_id=user["id"]) + attachments = [] + if hasattr(p, "attachments"): + attachments = p.attachments + call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) + + def create_tab(self, parent): + self.tab = home.peopleTab(parent) + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) + + def connect_events(self): + super(peopleBuffer, self).connect_events() + widgetUtils.connect_event(self.tab.new_chat, widgetUtils.BUTTON_PRESSED, self.new_chat) + + def new_chat(self, *args, **kwargs): + user = self.get_post() + if user == None: + return + user_id = user["id"] + pub.sendMessage("new-chat", user_id=user_id) + + def onFocus(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + if post.get("can_post") == True: + self.tab.post.Enable(True) + else: + self.tab.post.Enable(False) + if ("last_seen" in post) == False: return + original_date = arrow.get(post["last_seen"]["time"]) + now = arrow.now() + original_date.to(now.tzinfo) + diffdate = now-original_date + if diffdate.days == 0 and diffdate.seconds <= 360: + online_status = _("Online") + else: + # Translators: This is the date of last seen + online_status = _("Last seen {0}").format(original_date.humanize(locale=languageHandler.curLang[:2]),) + self.tab.list.list.SetItem(self.tab.list.get_selected(), 1, online_status) + + def open_timeline(self, *args, **kwargs): + user = self.get_post() + if user == None: + return + a = timeline.timelineDialog([self.session.get_user(user["id"])["user1_gen"]], show_selector=False) + if a.get_response() == widgetUtils.OK: + buffer_type = a.get_buffer_type() + user_id = user["id"] + pub.sendMessage("create-timeline", user_id=user_id, buffer_type=buffer_type) + + def get_menu(self, *args, **kwargs): + """ display menu for people buffers (friends and requests)""" + # If this is an incoming requests buffer, there is a flag in the peopleMenu that shows a few new options. + # So let's make sure we call it accordingly. + if self.name == "friend_requests": + m = menus.peopleMenu(is_request=True) + # Connect the accept and decline methods from here. + widgetUtils.connect_event(m, widgetUtils.MENU, self.accept_friendship, menuitem=m.accept) + widgetUtils.connect_event(m, widgetUtils.MENU, self.decline_friendship, menuitem=m.decline) + widgetUtils.connect_event(m, widgetUtils.MENU, self.keep_as_follower, menuitem=m.keep_as_follower) + elif self.name == "subscribers": + m = menus.peopleMenu(is_subscriber=True) + widgetUtils.connect_event(m, widgetUtils.MENU, self.accept_friendship, menuitem=m.add) + else: + m = menus.peopleMenu(is_request=False) + widgetUtils.connect_event(m, widgetUtils.MENU, self.decline_friendship, menuitem=m.decline) + widgetUtils.connect_event(m, widgetUtils.MENU, self.block_person, menuitem=m.block) + # It is not allowed to send messages to people who is not your friends, so let's disable it if we're in a pending or outgoing requests buffer. + if "friend_requests" in self.name: + m.message.Enable(False) + widgetUtils.connect_event(m, widgetUtils.MENU, self.new_chat, menuitem=m.message) + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_timeline, menuitem=m.timeline) + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_person_profile, menuitem=m.view_profile) + widgetUtils.connect_event(m, widgetUtils.MENU, self.open_in_browser, menuitem=m.open_in_browser) + return m + + def open_post(self, *args, **kwargs): pass + + def play_audio(self, *args, **kwargs): return False + + def pause_audio(self, *args, **kwargs): pass + + def accept_friendship(self, *args, **kwargs): + pass + + def decline_friendship(self, *args, **kwargs): + person = self.get_post() + if person == None: + return + user = self.session.get_user(person["id"]) + question = commonMessages.remove_friend(user) + if question == widgetUtils.NO: + return + result = self.session.vk.client.friends.delete(user_id=person["id"]) + if "friend_deleted" in result: + msg = _("You've removed {user1_nom} from your friends.").format(**user,) + pub.sendMessage("notify", message=msg) + self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) + self.tab.list.remove_item(self.tab.list.get_selected()) + + def block_person(self, *args, **kwargs): + person = self.get_post() + if person == None: + return + user = self.session.get_user(person["id"]) + question = commonMessages.block_person(user) + if question == widgetUtils.NO: + return + result = self.session.vk.client.account.ban(owner_id=person["id"]) + if result == 1: + msg = _("You've blocked {user1_nom} from your friends.").format(**user,) + pub.sendMessage("notify", message=msg) + self.session.db[self.name]["items"].pop(self.tab.list.get_selected()) + self.tab.list.remove_item(self.tab.list.get_selected()) + + def keep_as_follower(self, *args, **kwargs): + pass + + def add_person(self, person): + # This tracks if the user already exists here, in such case we just will update the last_seen variable + existing = False + for i in self.session.db[self.name]["items"]: + if person["id"] == i["id"]: + existing = True + i["last_seen"]["time"] = person["last_seen"]["time"] + break + # Add the new user to the buffer just if it does not exists previously. + if existing == False: + # Ensure the user won't loose the focus after the new item is added. + focused_item = self.tab.list.get_selected()+1 + self.session.db[self.name]["items"].insert(0, person) + self.insert(person, True) + # Selects back the previously focused item. + self.tab.list.select_item(focused_item) + + def remove_person(self, user_id): + # Make sure the user is present in the buffer, otherwise don't attempt to remove a None Value from the list. + user = None + focused_user = self.get_post() + for i in self.session.db[self.name]["items"]: + if i["id"] == user_id: + user = i + break + if user != None: + person_index = self.session.db[self.name]["items"].index(user) + focused_item = self.tab.list.get_selected() + self.session.db[self.name]["items"].pop(person_index) + self.tab.list.remove_item(person_index) + if user != focused_user: + # Let's find the position of the previously focused user. + focus = None + for i in range(0, len(self.session.db[self.name]["items"])): + if focused_user["id"] == self.session.db[self.name]["items"][i]["id"]: + self.tab.list.select_item(i) + return + elif user == focused_user and person_index < self.tab.list.get_count(): + self.tab.list.select_item(person_index) + else: + self.tab.list.select_item(self.tab.list.get_count()-1) + + def get_friend(self, user_id): + for i in self.session.db["friends_"]["items"]: + if i["id"] == user_id: + return i + log.exception("Getting user manually...") + user = self.session.vk.client.users.get(user_ids=event.user_id, fields="last_seen")[0] + return user + + def update_online(self): + online_users = self.session.vk.client.friends.getOnline() + now = time.time() + for i in self.session.db[self.name]["items"]: + if i["id"] in online_users: + i["last_seen"]["time"] = now + else: + log.exception("Removing an user from online status manually... %r" % (i)) + self.remove_person(i["id"]) + + def open_in_browser(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + url = "https://vk.com/id{user_id}".format(user_id=post["id"]) + webbrowser.open_new_tab(url) \ No newline at end of file diff --git a/src/controller/buffers/video.py b/src/controller/buffers/video.py new file mode 100644 index 0000000..a9c66a7 --- /dev/null +++ b/src/controller/buffers/video.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +import logging +import webbrowser +import wx +import widgetUtils +import output +from wxUI.tabs import home +from wxUI import commonMessages, menus +from controller import selector +from .wall import wallBuffer + +log = logging.getLogger("controller.buffers.video") + +class videoBuffer(wallBuffer): + """ This buffer represents video elements, and it can be used for showing videos for the logged user or someone else.""" + + def create_tab(self, parent): + self.tab = home.videoTab(parent) + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) + + def connect_events(self): + widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio) + super(videoBuffer, self).connect_events() + + def play_audio(self, *args, **kwargs): + """ Due to inheritance this method should be called play_audio, but play the currently focused video. + Opens a webbrowser pointing to the video's URL.""" + selected = self.tab.list.get_selected() + if self.tab.list.get_count() == 0: + return + if selected == -1: + selected = 0 + output.speak(_("Opening video in webbrowser...")) + webbrowser.open_new_tab(self.session.db[self.name]["items"][selected]["player"]) +# print self.session.db[self.name]["items"][selected] + return True + + def open_post(self, *args, **kwargs): + pass + + def remove_buffer(self, mandatory=False): + if "me_video" == self.name: + output.speak(_("This buffer can't be deleted")) + return False + else: + if mandatory == False: + dlg = commonMessages.remove_buffer() + else: + dlg = widgetUtils.YES + if dlg == widgetUtils.YES: + self.session.db.pop(self.name) + return True + else: + return False + + def get_more_items(self, *args, **kwargs): + # Translators: Some buffers can't use the get previous item feature due to API limitations. + output.speak(_("This buffer doesn't support getting more items.")) + + def onFocus(self, event, *args, **kwargs): + event.Skip() + + def add_to_library(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + args = {} + args["video_id"] = post["id"] + if "album_id" in post: + args["album_id"] = post["album_id"] + args["owner_id"] = post["owner_id"] + video = self.session.vk.client.video.add(**args) + if video != None and int(video) > 21: + output.speak(_("Video added to your library")) + + def remove_from_library(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + args = {} + args["video_id"] = post["id"] + args["owner_id"] = self.session.user_id + result = self.session.vk.client.video.delete(**args) + if int(result) == 1: + output.speak(_("Removed video from library")) + self.tab.list.remove_item(self.tab.list.get_selected()) + + def move_to_album(self, *args, **kwargs): + if len(self.session.video_albums) == 0: + return commonMessages.no_video_albums() + post= self.get_post() + if post == None: + return + album = selector.album(_("Select the album where you want to move this video"), self.session, "video_albums") + if album.item == None: return + id = post["id"] + response = self.session.vk.client.video.addToAlbum(album_ids=album.item, video_id=id, target_id=self.session.user_id, owner_id=self.get_post()["owner_id"]) + if response == 1: + # Translators: Used when the user has moved an video to an album. + output.speak(_("Moved")) + + def get_menu(self): + """ We'll use the same menu that is used for audio items, as the options are exactly the same""" + p = self.get_post() + if p == None: + return + m = menus.audioMenu() + widgetUtils.connect_event(m, widgetUtils.MENU, self.move_to_album, menuitem=m.move) + # if owner_id is the current user, the audio is added to the user's audios. + if p["owner_id"] == self.session.user_id: + m.library.SetItemLabel(_("&Remove")) + widgetUtils.connect_event(m, widgetUtils.MENU, self.remove_from_library, menuitem=m.library) + else: + widgetUtils.connect_event(m, widgetUtils.MENU, self.add_to_library, menuitem=m.library) + return m + + def open_in_browser(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + url = "https://vk.com/video{user_id}_{video_id}".format(user_id=post["owner_id"], video_id=post["id"]) + webbrowser.open_new_tab(url) \ No newline at end of file diff --git a/src/controller/buffers/videoAlbum.py b/src/controller/buffers/videoAlbum.py new file mode 100644 index 0000000..3b7c590 --- /dev/null +++ b/src/controller/buffers/videoAlbum.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +import logging +import wx +import widgetUtils +import output +from wxUI.tabs import home +from .video import videoBuffer + +log = logging.getLogger("controller.buffers") + +class videoAlbumBuffer(videoBuffer): + + def create_tab(self, parent): + self.tab = home.videoAlbumTab(parent) + self.tab.play.Enable(False) + self.connect_events() + self.tab.name = self.name + if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): + self.tab.post.Enable(False) + + def connect_events(self): + super(videoAlbumBuffer, self).connect_events() + widgetUtils.connect_event(self.tab.load, widgetUtils.BUTTON_PRESSED, self.load_album) + + def load_album(self, *args, **kwargs): + output.speak(_("Loading album...")) + self.can_get_items = True + self.tab.load.Enable(False) + wx.CallAfter(self.get_items) + self.tab.play.Enable(True) \ No newline at end of file diff --git a/src/controller/buffers/wall.py b/src/controller/buffers/wall.py new file mode 100644 index 0000000..5e8c054 --- /dev/null +++ b/src/controller/buffers/wall.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +""" A buffer is a (virtual) list of items. All items belong to a category (wall posts, messages, persons...)""" +import logging +import webbrowser +import wx +import presenters +import views +import interactors +import widgetUtils +import output +from pubsub import pub +from vk_api.exceptions import VkApiError +from wxUI.tabs import home +from mysc.thread_utils import call_threaded +from wxUI import commonMessages +from .home import homeBuffer + +log = logging.getLogger("controller.buffers.wall") + +class wallBuffer(homeBuffer): + """ This buffer represents an user's wall. It may be used either for the current user or someone else.""" + + def get_items(self, show_nextpage=False): + """ Update buffer with newest items or get older items in the buffer.""" + if self.can_get_items == False: return + retrieved = True + try: + num = getattr(self.session, "get_page")(show_nextpage=show_nextpage, name=self.name, *self.args, **self.kwargs) + except VkApiError as err: + log.error("Error {0}: {1}".format(err.code, err.error)) + retrieved = err.code + return retrieved + except: + log.exception("Connection error when updating buffer %s. Will try again in 2 minutes" % (self.name,)) + return False + if not hasattr(self, "tab"): + # Create GUI associated to this buffer. + self.create_tab(self.parent) + # Add name to the new control so we could look for it when needed. + self.tab.name = self.name + if show_nextpage == False: + if self.tab.list.get_count() > 0 and num > 0: + v = [i for i in self.session.db[self.name]["items"][:num]] + v.reverse() + [wx.CallAfter(self.insert, i, True) for i in v] + else: + [wx.CallAfter(self.insert, i) for i in self.session.db[self.name]["items"][:num]] + else: + if num > 0: + [wx.CallAfter(self.insert, i, False) for i in self.session.db[self.name]["items"][-num:]] + return retrieved + + def remove_buffer(self, mandatory=False): + """ Remove buffer if the current buffer is not the logged user's wall.""" + if "me_feed" == self.name: + output.speak(_("This buffer can't be deleted")) + return False + else: + if mandatory == False: + dlg = commonMessages.remove_buffer() + else: + dlg = widgetUtils.YES + if dlg == widgetUtils.YES: + self.session.db.pop(self.name) + return True + else: + return False + + def __init__(self, *args, **kwargs): + super(wallBuffer, self).__init__(*args, **kwargs) + self.user_key = "from_id" + self.post_key = "id" + self.can_post = True + self.can_write_private_message = True + # if this is an user timeline we must check permissions to hide buttons when needed. + if "owner_id" in self.kwargs and self.kwargs["owner_id"] > 0 and "feed" in self.name: + permissions = self.session.vk.client.users.get(user_ids=self.kwargs["owner_id"], fields="can_post, can_see_all_posts, can_write_private_message") + self.can_post = permissions[0]["can_post"] + self.can_see_all_posts = permissions[0]["can_see_all_posts"] + self.can_write_private_message = permissions[0]["can_write_private_message"] + log.debug("Checked permissions on buffer {0}, permissions were {1}".format(self.name, permissions)) + + def post(self, *args, **kwargs): + """ Create a post in the wall for the specified user + This process is handled in two parts. This is the first part, where the GUI is created and user can send the post. + During the second part (threaded), the post will be sent to the API.""" + if "owner_id" not in self.kwargs: + return super(wallBuffer, self).post() + owner_id = self.kwargs["owner_id"] + user = self.session.get_user(owner_id, key="user1") + title = _("Post to {user1_nom}'s wall").format(**user) + p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=title, message="", text="")) + if hasattr(p, "text") or hasattr(p, "privacy"): + post_arguments=dict(privacy=p.privacy, message=p.text, owner_id=owner_id) + attachments = [] + if hasattr(p, "attachments"): + attachments = p.attachments + call_threaded(pub.sendMessage, "post", parent_endpoint="wall", child_endpoint="post", from_buffer=self.name, attachments_list=attachments, post_arguments=post_arguments) + + def open_in_browser(self, *args, **kwargs): + post = self.get_post() + if post == None: + return + url = "https://vk.com/wall{user_id}_{post_id}".format(user_id=post["from_id"], post_id=post["id"]) + webbrowser.open_new_tab(url) \ No newline at end of file