diff --git a/src/controller/buffers.py b/src/controller/buffers.py index 284b8c7..1d5f750 100644 --- a/src/controller/buffers.py +++ b/src/controller/buffers.py @@ -255,3 +255,37 @@ class empty(object): output.speak(_(u"This buffer doesn't support getting more items.")) def remove_buffer(self, mandatory=False): return False + +class chatBuffer(baseBuffer): + + def create_tab(self, parent): + self.tab = home.chatTab(parent) + + def connect_events(self): + widgetUtils.connect_event(self.tab.send, widgetUtils.BUTTON_PRESSED, self.send_chat_to_user) + + def get_items(self, show_nextpage=False): + retrieved = True # Control variable for handling unauthorised/connection errors. + try: + num = getattr(self.session, "get_messages")(name=self.name, *self.args, **self.kwargs) + except VkAPIMethodError as err: + print(u"Error {0}: {1}".format(err.code, err.message)) + retrieved = err.code + return retrieved + if show_nextpage == False: + if self.tab.list.get_count() > 0 and num > 0: + print "inserting a value" + v = [i for i in self.session.db[self.name]["items"][:num]] + v.reverse() + [self.insert(i, True) for i in v] + else: + [self.insert(i) for i in self.session.db[self.name]["items"][:num]] + else: + if num > 0: + [self.insert(i, False) for i in self.session.db[self.name]["items"][:num]] + return retrieved + + def send_chat_to_user(self, *args, **kwargs): + text = self.tab.text.GetValue() + if text == "": return + response = self.session.vk.client.messages.send(user_id=self.kwargs["user_id"], message=text) diff --git a/src/controller/longpoolthread.py b/src/controller/longpoolthread.py new file mode 100644 index 0000000..bfeaafa --- /dev/null +++ b/src/controller/longpoolthread.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +import threading +from vk import longpool +from pubsub import pub + +class worker(threading.Thread): + def __init__(self, session): + super(worker, self).__init__() + self.session = session + self.l = longpool.LongPoll(self.session.vk.client) + + def run(self): + while self.session.is_logged == True: + p = self.l.check() + for i in p: + print i.message_id, i.flags, i.from_id, i.user_id, i.mask, i.byself, i.message_flags +# if i.flags == 4 or i.flags == 51 or i.flags == 49: + if i.text != None and i.from_id != None and i.flags != None and i.message_flags != None: + print i.message_id, i.flags, i.from_id, i.user_id, i.mask, i.byself, i.message_flags +# if i.from_id != None: + print "ordering sent stuff" + pub.sendMessage("order-sent-message", obj=i) diff --git a/src/controller/mainController.py b/src/controller/mainController.py index e82b2e8..b315b3b 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -10,6 +10,7 @@ import player import posts import webbrowser import logging +import longpoolthread from pubsub import pub from mysc.repeating_timer import RepeatingTimer from mysc.thread_utils import call_threaded @@ -75,6 +76,9 @@ class Controller(object): r_audio = buffers.audioBuffer(parent=self.window.tb, name="recommended_audio", composefunc="compose_audio", session=self.session, endpoint="getRecommendations", parent_endpoint="audio", full_list=True, count=self.session.settings["buffers"]["count_for_audio_buffers"]) self.buffers.append(r_audio) self.window.insert_buffer(r_audio.tab, _(u"Recommendations"), self.window.search("audios")) + chats = buffers.empty(parent=self.window.tb, name="chats") + self.buffers.append(chats) + self.window.add_buffer(chats.tab, _(u"Chats")) timelines = buffers.empty(parent=self.window.tb, name="timelines") self.buffers.append(timelines) self.window.add_buffer(timelines.tab, _(u"Timelines")) @@ -97,6 +101,7 @@ class Controller(object): widgetUtils.connect_event(self.window, widgetUtils.MENU, self.changelog, menuitem=self.window.changelog) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.configuration, menuitem=self.window.settings_dialog) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.new_timeline, menuitem=self.window.timeline) + pub.subscribe(self.get_chat, "order-sent-message") def disconnect_events(self): log.debug("Disconnecting some events...") @@ -116,6 +121,8 @@ class Controller(object): self.window.change_status(_(u"Loading items for {0}").format(i.name,)) i.get_items() self.window.change_status(_(u"Ready")) + self.longpool = longpoolthread.worker(self.session) + self.longpool.start() def in_post(self, buffer): buffer = self.search(buffer) @@ -242,4 +249,64 @@ class Controller(object): del buffer commonMessages.show_error_code(answer) return - self.window.insert_buffer(buffer.tab, name_, position) \ No newline at end of file + self.window.insert_buffer(buffer.tab, name_, position) + + def new_chat(self, *args, **kwargs): + b = self.get_current_buffer() + if not hasattr(b, "get_users"): + b = self.search("home_timeline") + d = [] + for i in self.session.db["users"]: + d.append((i, self.session.get_user_name(i))) + for i in self.session.db["groups"]: + d.append((-i, self.session.get_user_name(-i))) + a = timeline.timelineDialog([i[1] for i in d]) + if a.get_response() == widgetUtils.OK: + user = a.get_user() + buffertype = a.get_buffer_type() + user_id = "" + for i in d: + if i[1] == user: + user_id = i[0] + if user_id == None: + commonMessages.no_user_exist() + return + buffer = buffers.chatBuffer(parent=self.window.tb, name="{0}_messages".format(user_id,), composefunc="compose_message", session=self.session, count=200, user_id=user_id, rev=1) + self.buffers.append(buffer) + self.window.insert_buffer(buffer.tab, _(u"Chat with {0}").format(self.session.get_user_name(user_id,)), self.window.search("chats")) + buffer.get_items() + + def search_chat_buffer(self, user_id): + for i in self.buffers: + if "_messages" in i.name: + if i.kwargs.has_key("user_id") and i.kwargs["user_id"] == user_id: return i + return None + + def chat_from_id(self, user_id): + buffer = buffers.chatBuffer(parent=self.window.tb, name="{0}_messages".format(user_id,), composefunc="compose_message", session=self.session, count=200, user_id=user_id, rev=1) + self.buffers.append(buffer) + self.window.insert_buffer(buffer.tab, _(u"Chat with {0}").format(self.session.get_user_name(user_id,)), self.window.search("chats")) + buffer.get_items() + return True + + def get_chat(self, obj=None): + """ Searches or creates a chat buffer with the id of the user that is sending or receiving a message. + obj vk.longpool.event: an event wich defines some data from the vk's longpool server.""" + # Set user_id to the id of the friend wich is receiving or sending the message. + obj.user_id = obj.from_id + buffer = self.search_chat_buffer(obj.user_id) + if buffer == None: + wx.CallAfter(self.chat_from_id, obj.user_id) + return + # If the chat already exists, let's create a dictionary wich will contains data of the received message. + message = {"id": obj.message_id, "user_id": obj.user_id, "date": obj.timestamp, "body": obj.text, "attachments": obj.attachments} + # If outbox it's true, it means that message["from_id"] should be the current user. If not, the obj.user_id should be taken. + if obj.message_flags.has_key("outbox") == True: + message["from_id"] = self.session.user_id + else: + message["from_id"] = obj.from_id + data = [message] + # Let's add this to the buffer. + # ToDo: Clean this code and test how is the database working with this set to True. + num = self.session.order_buffer(buffer.name, data, True) + buffer.insert(self.session.db[buffer.name]["items"][-1], False) diff --git a/src/sessionmanager/session.py b/src/sessionmanager/session.py index 3c8f976..b69992d 100644 --- a/src/sessionmanager/session.py +++ b/src/sessionmanager/session.py @@ -101,6 +101,13 @@ def compose_new(status, session): if status["type"] != "post": print status return [user, message, created_at] +def compose_message(message, session): + user = session.get_user_name(message["from_id"]) + original_date = arrow.get(message["date"]) + created_at = original_date.format(_(u"dddd, MMMM D, YYYY H:m:s"), locale=languageHandler.getLanguage()) + body = message["body"] + return [u"{2}, {0} {1}".format(body, created_at, user)] + def compose_status(status, session): user = session.get_user_name(status["from_id"]) if status.has_key("copy_history"): @@ -141,7 +148,7 @@ class vkSession(object): self.db[name]["items"] = [] first_addition = True for i in data: - if i.has_key("type") and (i["type"] == "wall_photo" or i["type"] == "photo_tag"): + if i.has_key("type") and (i["type"] == "wall_photo" or i["type"] == "photo_tag" or i["type"] == "photo"): log.debug("Skipping unsupported item... %r" % (i,)) continue if find_item(self.db[name]["items"], i) == False: @@ -240,6 +247,14 @@ class vkSession(object): num = self.order_buffer(name, data, show_nextpage) return num + def get_messages(self, name="", *args, **kwargs): + data = self.vk.client.messages.getHistory(*args, **kwargs) + if data != None: + print data + num = self.order_buffer(name, data["items"], False) + return num + + def get_user_name(self, user_id): if user_id > 0: if self.db["users"].has_key(user_id): diff --git a/src/vk/longpool.py b/src/vk/longpool.py new file mode 100644 index 0000000..c023f1f --- /dev/null +++ b/src/vk/longpool.py @@ -0,0 +1,152 @@ +# encoding: utf-8 +import requests + +class LongPoll(object): + def __init__(self, vk, wait=25, use_ssl=True, mode=34): + self.vk = vk + self.wait = wait + self.use_ssl = use_ssl + self.mode = mode + self.get_longpoll_server() + self.url = 'https' if use_ssl else 'http' + self.url += '://' + self.server + + def get_longpoll_server(self, update_ts=True): + values = { + 'use_ssl': '1' if self.use_ssl else '0', + 'need_pts': '1' + } + response = self.vk.messages.getLongPollServer(**values) + self.key = response['key'] + self.server = response['server'] + if update_ts: + self.ts = response['ts'] + self.pts = response['pts'] + + def check(self): + values = { + 'act': 'a_check', + 'key': self.key, + 'ts': self.ts, + 'wait': self.wait, + 'mode': self.mode + } + response = requests.get(self.url, params=values, + timeout=self.wait + 10).json() + events = [] + + if 'failed' not in response: + self.ts = response['ts'] + self.pts = response['pts'] + + for raw_event in response['updates']: + events.append(Event(raw_event)) + # http://vk.com/dev/using_longpoll + else: + self.get_longpoll_server(update_ts=False) + + return events + + +CHAT_START_ID = int(2E9) + +EVENT_TYPES = { + 0: 'message_delete', + 1: 'message_flags_replace', + 2: 'message_flags_put', + 3: 'message_flags_reset', + 4: 'message_new', + + 8: 'user_online', + 9: 'user_offline', + + 51: 'chat_new', + 61: 'user_typing', + 62: 'user_typing_in_chat', + + 70: 'user_call', + } + +ASSOCIATIVES = { + 0: ['message_id'], + 1: ['message_id', 'flags'], + 2: ['message_id', 'mask', 'user_id'], + 3: ['message_id', 'mask', 'user_id', 'timestamp', 'subject', + 'text', 'attachments'], + 4: ['message_id', 'flags', 'from_id', 'timestamp', 'subject', + 'text', 'attachments'], + + 8: ['user_id', 'flags'], + 9: ['user_id', 'flags'], + + 51: ['chat_id', 'byself'], + 61: ['user_id', 'flags'], + 62: ['user_id', 'chat_id'], + + 70: ['user_id', 'call_id'], +} + +MESSAGE_FLAGS = [ + 'unread', 'outbox', 'replied', 'important', 'chat', 'friends', 'spam', + 'deleted', 'fixed', 'media' +] + + +class Event(object): + def __init__(self, raw): + self.raw = raw + + self.message_id = None + self.flags = None + self.mask = None + self.user_id = None + self.from_id = None + self.timestamp = None + self.subject = None + self.text = None + self.attachments = None + self.call_id = None + self.chat_id = None + self.byself = None + + cmd = raw[0] + + self.message_flags = {} + self.type = EVENT_TYPES.get(cmd) + self._list_to_attr(raw[1:], ASSOCIATIVES.get(cmd)) + + if cmd == 4: + self._parse_message_flags() + self.text = self.text.replace('
', '\n') + + if self.from_id > CHAT_START_ID: + self.chat_id = self.from_id - CHAT_START_ID + self.from_id = self.attachments['from'] + elif cmd in [2, 3]: + if self.user_id > CHAT_START_ID: + self.chat_id = self.user_id - CHAT_START_ID + self.user_id = None + elif cmd in [8, 9]: + self.user_id = abs(self.user_id) + + def _parse_message_flags(self): + x = 1 + for i in MESSAGE_FLAGS: + + if self.flags & x: + self.message_flags.update({i: True}) + + x *= 2 + + def _list_to_attr(self, l, associative): + if not associative: + return + + for i in range(len(l)): + try: + name = associative[i] + except IndexError: + return True + + value = l[i] + self.__setattr__(name, value) diff --git a/src/wxUI/tabs/home.py b/src/wxUI/tabs/home.py index d3b34a7..8b42eec 100644 --- a/src/wxUI/tabs/home.py +++ b/src/wxUI/tabs/home.py @@ -114,3 +114,28 @@ class empty(wx.Panel): self.name = name sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) + +class chatTab(wx.Panel): + + def __init__(self, parent): + super(chatTab, self).__init__(parent=parent) + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.create_controls()) + self.send = wx.Button(self, -1, _(u"Send")) + sizer.Add(self.send, 0, wx.ALL, 5) + self.SetSizer(sizer) + + def create_controls(self): + lbl1 = wx.StaticText(self, wx.NewId(), _(u"History")) + self.list = widgetUtils.list(self, *[_(u"post")], style=wx.LC_REPORT) + self.list.set_windows_size(0, 190) +# self.list.list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnKeyDown) + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(lbl1, 0, wx.ALL, 5) + box.Add(self.list.list, 0, wx.ALL, 5) + lbl2 = wx.StaticText(self, -1, _(u"Write a message")) + self.text = wx.TextCtrl(self, -1) + box.Add(lbl2, 0, wx.ALL, 20) + box.Add(self.text, 0, wx.ALL, 5) + return box +