From 7e6006e9e3f98d2868b292d0ccdfd31b03e3e915 Mon Sep 17 00:00:00 2001 From: mrDoctorWho Date: Thu, 5 Sep 2013 21:34:42 +0700 Subject: Added attachments support, maybe fixed bug when roster wasn't filled by friends Added forwarded messages support. --- README | 13 ++ gateway.py | 65 +++++++--- library/vk_api.py | 377 +++++++++++++++++++++++++++--------------------------- locales/locale.ru | 2 + 4 files changed, 252 insertions(+), 205 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..b7bd726 --- /dev/null +++ b/README @@ -0,0 +1,13 @@ +Транспорт. Из VK в XMPP и обратно. + +Написан на языке программирования Python, все используемые библиотеки содержит в себе. +Находится в состоянии тестирования. Требует Python 2.7. + +Установка стандартна как и для любого другого транспорта. Только вот переименуйте Config_example.txt в Config.txt и заполните его. +На время тестирования рекомендуется заглядывать в папку транспорта и сообщать разработчику об ошибках (тела ошибок хранятся в папке crash). + +Запуск: +python ./gateway.py + +Обратиться к разработчику в сети xmpp можно в конференции xmpp:simpleapps@conference.jabber.ru?join +© simpleApps, 2013. \ No newline at end of file diff --git a/gateway.py b/gateway.py index 3db3c89..ba92ff1 100644 --- a/gateway.py +++ b/gateway.py @@ -6,6 +6,7 @@ # Program published under MIT license. import os, sys, time, json, signal, urllib, socket, traceback, threading +from datetime import datetime from math import ceil if not hasattr(sys, "argv") or not sys.argv[0]: sys.argv = ["."] @@ -29,6 +30,7 @@ import xmpp ## other. from itypes import Database, Number +from webtools import * from writer import * from stext import * from stext import _ @@ -67,7 +69,7 @@ setVars(DefLang, __file__) import logging logger = logging.getLogger("vk4xmpp") -logger.setLevel(logging.INFO) +logger.setLevel(logging.DEBUG) loggerHandler = logging.FileHandler("vk4xmpp.log") Formatter = logging.Formatter("%(asctime)s:%(levelname)s:%(name)s %(message)s", "[%d.%m.%Y %H:%M:%S]") loggerHandler.setFormatter(Formatter) @@ -179,9 +181,9 @@ class VKLogin(object): try: return func(method, args) except api.apiError as e: - logger.error("VKLogin: apiError %s for user %s" % (e.message, self.jidFrom)) if e.message == "Logged out": - pass + return {} + logger.error("VKLogin: apiError %s for user %s" % (e.message, self.jidFrom)) except api.captchaNeeded: logger.error("VKLogin: running captcha challenge for %s" % self.jidFrom) self.captchaChallenge() @@ -213,11 +215,10 @@ class VKLogin(object): def getFriends(self, fields = "screen_name"): friendsRaw = self.method("friends.get", {"fields": fields}) # friends.getOnline friendsDict = {} - logger.debug("VKLogin: requesing friends for %s" % self.jidFrom) if friendsRaw: for friend in friendsRaw: id = friend["uid"] - name = " ".join([friend["first_name"], friend["last_name"]]) + name = u"%s %s" % (friend["first_name"], friend["last_name"]) try: friendsDict[id] = {"name": name, "online": friend["online"]} friendsDict[id]["photo"] = friend.get("photo_200_orig", URL_VCARD_NO_IMAGE) @@ -231,7 +232,6 @@ class VKLogin(object): self.method("messages.markAsRead", {"message_ids": list}) def getMessages(self, lastjoinTime = 0, count = 5, lastMsgID = 0): - logger.debug("VKLogin: requesing messages for %s" % self.jidFrom) values = {"out": 0, "time_offset": lastjoinTime, "filters": 1, "count": count} if lastMsgID: @@ -253,10 +253,10 @@ class tUser(object): if data: self.username, self.password = data self.cl = cl + self.friends = {} self.auth = False self.token = False self.fullJID = False - self.friends = False self.lastStatus = False self.lastMsgID = False self.rosterSet = False @@ -340,10 +340,10 @@ class tUser(object): def init(self, force = False): logger.debug("tUser: called init for user %s" % self.jUser) - friends = self.vk.getFriends() - if friends and not self.rosterSet or force: + self.friends = self.vk.getFriends() + if self.friends and not self.rosterSet or force: logger.debug("tUser: calling subscribe with force:%s for %s" % (force, self.jUser)) - self.rosterSubscribe(friends) + self.rosterSubscribe(self.friends) threadRun(self.sendInitPresence) threadRun(self.sendMessages) # is it valid? @@ -371,7 +371,30 @@ class tUser(object): for message in messages: read.append(str(message.get("mid", 0))) fromjid = "%s@%s" % (message["uid"], TransportID) - body = message["body"].replace("
", "\n") + body = uHTML(message["body"]) + if message.has_key("attachments"): + body += _("\nAttachments:") + attachments = message["attachments"] + for att in attachments: + key = att.get("type", {}) + if att[key].has_key("url"): + body += "\n" + att[key]["url"] + if message.has_key("fwd_messages"): + body += _("\nForward messages") + fwd_messages = sorted(message["fwd_messages"], lambda a, b: a["date"] - b["date"]) + for fwd in fwd_messages: + idFrom = fwd["uid"] + date = fwd["date"] + fwdBody = uHTML(fwd["body"]) + date = datetime.fromtimestamp(date).strftime("%d.%m.%Y %H:%M:%S") + if idFrom not in self.friends: + name = self.vk.method("users.get", {"fields": "screen_name", "user_ids": idFrom}) + if name: + name = name.pop() + name = u"%s %s" % (name["first_name"], name["last_name"]) + else: + name = self.friends[idFrom] + body += "\n[%s] <%s> %s" % (date, name, fwdBody) msgSend(self.cl, self.jUser, body, fromjid, message["date"]) self.vk.msgMarkAsRead(read) if UseLastMessageID: @@ -381,15 +404,18 @@ class tUser(object): def rosterSubscribe(self, dist = {}): Presence = xmpp.Presence(self.jUser, "subscribe") Presence.setTag("nick", namespace = xmpp.NS_NICK) - for id in dist.keys() + [TransportID]: - nickName = self.friends.get(id, {}).get("name") + for id in dist.keys(): + nickName = self.friends[id]["name"] Presence.setTagData("nick", nickName) Presence.setFrom(vk2xmpp(id)) Sender(self.cl, Presence) time.sleep(0.2) - self.rosterSet = True - with Database(DatabaseFile, Semaphore) as db: - db("update users set rosterSet=? where jid=?", (self.rosterSet, self.jUser)) + Presence.setFrom(TransportID) + Sender(self.cl, Presence) + if dist: + self.rosterSet = True + with Database(DatabaseFile, Semaphore) as db: + db("update users set rosterSet=? where jid=?", (self.rosterSet, self.jUser)) def tryAgain(self): if not self.auth: @@ -403,6 +429,7 @@ class tUser(object): def Sender(cl, stanza): try: cl.send(stanza) + time.sleep(0.001) except IOError: logger.error("Panic: Couldn't send stanza: %s" % str(stanza)) except: @@ -509,7 +536,7 @@ def prsHandler(cl, prs): else: raise xmpp.NodeProcessed() - elif pType == "unavailable": + elif pType == "unavailable" and Class.lastStatus != pType: Sender(cl, xmpp.Presence(jidFromStr, "unavailable", frm = TransportID)) Class.vk.disconnect() @@ -524,8 +551,6 @@ def prsHandler(cl, prs): if id in Class.friends: if Class.friends[id]["online"]: Sender(cl, xmpp.Presence(jidFrom, frm = jidTo)) -# else: -# raise xmpp.NodeProcessed() Class.lastStatus = pType def iqBuildError(stanza, error = None, text = None): @@ -955,7 +980,7 @@ def exit(signal = None, frame = None): # LETS BURN CPU AT LAST TIME! for id in friends: jid = vk2xmpp(id) Presence.setFrom(jid) - Send(Component, Presence) + Sender(Component, Presence) Print(".", False) Print("\n") os._exit(1) diff --git a/library/vk_api.py b/library/vk_api.py index 8759948..e1025db 100644 --- a/library/vk_api.py +++ b/library/vk_api.py @@ -1,185 +1,192 @@ -# /* coding: utf-8 */ -# based on VKApi module by Kirill Python -# Modifications © simpleApps. - -import re, time -import requests, webtools - -class VkApi: - def __init__(self, number, password = None, - sid = None, token= None, app_id = 3789129, - scope = 69634, proxies = None): - self.password = password - self.number = number - - self.sid = sid - self.token = token - self.captcha = {} - self.settings = {} - self.lastMethod = None - - self.app_id = app_id - self.scope = scope - - self.http = requests.Session() - self.http.proxies = proxies - self.http.headers = {"User-agent": "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:21.0) Gecko/20130309 Firefox/21.0", - "Accept-Language":"ru-RU, utf-8"} - self.http.verify = False - - - def vk_login(self, cSid=None, cKey = None): - url = "https://login.vk.com/" - values = {"act": "login", - "utf8": "1", - "email": self.number, - "pass": self.password} - - if cSid and cKey: - values["captcha_sid"] = cSid - values["captcha_key"] = cKey - - self.http.cookies.clear() - response = self.http.post(url, values) - if "remixsid" in self.http.cookies: - remixsid = self.http.cookies["remixsid"] - self.settings["remixsid"] = remixsid - - self.settings["forapilogin"] = {"p": self.http.cookies["p"], - "l": self.http.cookies["l"] } - - self.sid = remixsid - - elif "sid=" in response.url: - raise authError("Authorization error (captcha)") - else: - raise authError("Authorization error (bad password)") - - if "security_check" in response.url: - number_hash = regexp(r"security_check.*?hash: '(.*?)'\};", response.text)[0] - - code = self.number[2:-2] - if len(self.number) == 12: - if not self.number.startswith("+"): - code = self.number[3:-2] # may be +375123456789 - - elif len(self.number) == 13: # so we need 1234567 - if self.number.startswith("+"): - code = self.number[4:-2] - - values = {"act": "security_check", - "al": "1", - "al_page": "3", - "code": code, - "hash": number_hash, - "to": ""} - response = self.http.post("https://vk.com/login.php", values) - if response.text.split("")[4] == "4": - return - - raise authError("Incorrect number") - - def check_sid(self): - """Valiating cookies remixsid""" - if self.sid: - url = "https://vk.com/feed2.php" - self.http.cookies.update({ - "remixsid": self.sid, - "remixlang": "0", - "remixsslsid": "1" - }) - - response = self.http.get(url).json() - - if response["user"]["id"] != -1: - return response - - def api_login(self): - url = "https://oauth.vk.com/authorize" - values = {"display": "mobile", - "scope": self.scope, - "client_id": self.app_id, - "response_type": "token", - "redirect_uri": "https://oauth.vk.com/blank.html"} - - token = None - GET = self.http.get(url, params = values) - getUrl = GET.url - if "access_token" in getUrl: - token = getUrl.split("=")[1].split("&")[0] - else: - POST = webtools.getTagArg("form method=\"post\"", "action", GET.text, "form") - if POST: - response = self.http.post(POST) - token = response.url.split("=")[1].split("&")[0] - else: - raise authError("ApiError") - self.token = token - - - def method(self, method, values={}): - url = "https://api.vk.com/method/%s" % method - values["access_token"] = self.token - if self.captcha and self.captcha.has_key("key"): - values["captcha_sid"] = self.captcha["sid"] - values["captcha_key"] = self.captcha["key"] - self.lastMethod = (method, values) -## print "method %s with values %s" % (method, str(values)) - ## This code can be useful when we're loaded too high - try: - json = self.http.post(url, values).json() - except requests.ConnectionError: - try: - time.sleep(1) - json = self.http.post(url, values).json() - except: - return {} -## print "response:%s"% str(json) - if json.has_key("response"): - return json["response"] - - elif json.has_key("error"): - error = json["error"] - eCode = error["error_code"] - if eCode == 6: # too fast - time.sleep(3) - return self.method(method, values) - elif eCode == 5: # auth failed - raise apiError("Logged out") - if eCode == 14: - if "captcha_sid" in error: # maybe we need check if exists self.captcha - self.captcha = {"sid": error["captcha_sid"], "img": error["captcha_img"]} - raise captchaNeeded - raise apiError(json["error"]) - - def retry(self): - if self.lastMethod: - return self.method(*self.lastMethod) - -def regexp(reg, string, findall = 1): - u""" Поиск по регулярке """ - - reg = re.compile(reg, re.IGNORECASE | re.DOTALL) - if findall: - reg = reg.findall(string) - else: - return reg.search(string) - return reg - - -class vkApiError(Exception): - pass - - -class authError(vkApiError): - pass - - -class apiError(vkApiError): - pass - -class tokenError(vkApiError): - pass - -class captchaNeeded(vkApiError): - pass \ No newline at end of file +# /* coding: utf-8 */ +# based on VKApi module by Kirill Python +# Modifications © simpleApps. + +import re, time +import requests, urllib, webtools + +class VkApi: + def __init__(self, number, password = None, + sid = None, token= None, app_id = 3789129, + scope = 69634, proxies = None): + self.password = password + self.number = number + + self.sid = sid + self.token = token + self.captcha = {} + self.settings = {} + self.last = [] + self.lastMethod = None + + self.app_id = app_id + self.scope = scope + + self.http = requests.Session() + self.http.proxies = proxies + self.http.headers = {"User-agent": "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:21.0) Gecko/20130309 Firefox/21.0", + "Accept-Language":"ru-RU, utf-8"} + self.http.verify = False + + + def vk_login(self, cSid=None, cKey = None): + url = "https://login.vk.com/" + values = {"act": "login", + "utf8": "1", + "email": self.number, + "pass": self.password} + + if cSid and cKey: + values["captcha_sid"] = cSid + values["captcha_key"] = cKey + + self.http.cookies.clear() + response = self.http.post(url, values) + if "remixsid" in self.http.cookies: + remixsid = self.http.cookies["remixsid"] + self.settings["remixsid"] = remixsid + + self.settings["forapilogin"] = {"p": self.http.cookies["p"], + "l": self.http.cookies["l"] } + + self.sid = remixsid + + elif "sid=" in response.url: + raise authError("Authorization error (captcha)") + else: + raise authError("Authorization error (bad password)") + + if "security_check" in response.url: + number_hash = regexp(r"security_check.*?hash: '(.*?)'\};", response.text)[0] + + code = self.number[2:-2] + if len(self.number) == 12: + if not self.number.startswith("+"): + code = self.number[3:-2] # may be +375123456789 + + elif len(self.number) == 13: # so we need 1234567 + if self.number.startswith("+"): + code = self.number[4:-2] + + values = {"act": "security_check", + "al": "1", + "al_page": "3", + "code": code, + "hash": number_hash, + "to": ""} + response = self.http.post("https://vk.com/login.php", values) + if response.text.split("")[4] == "4": + return + + raise authError("Incorrect number") + + def check_sid(self): + """Valiating cookies remixsid""" + if self.sid: + url = "https://vk.com/feed2.php" + self.http.cookies.update({ + "remixsid": self.sid, + "remixlang": "0", + "remixsslsid": "1" + }) + + response = self.http.get(url).json() + + if response["user"]["id"] != -1: + return response + + def api_login(self): + url = "https://oauth.vk.com/authorize" + values = {"display": "mobile", + "scope": self.scope, + "client_id": self.app_id, + "response_type": "token", + "redirect_uri": "https://oauth.vk.com/blank.html"} + + token = None + GET = self.http.get(url, data = values) + getUrl = GET.url + if "access_token" in getUrl: + token = getUrl.split("=")[1].split("&")[0] + else: + POST = webtools.getTagArg("form method=\"post\"", "action", GET.text, "form") + if POST: + response = self.http.post(POST) + token = response.url.split("=")[1].split("&")[0] + else: + raise authError("ApiError") + self.token = token + + + def method(self, method, values={}): + url = "https://api.vk.com/method/%s" % method + values["access_token"] = self.token + if self.captcha and self.captcha.has_key("key"): + values["captcha_sid"] = self.captcha["sid"] + values["captcha_key"] = self.captcha["key"] + self.lastMethod = (method, values) + ## This code can be useful when we're loaded too high + self.last.append(time.time()) + if len(self.last) > 2: + if (self.last.pop() - self.last.pop(0)) < 1.1: +## print "sleeping because too fat %s" % str(self.last) + time.sleep(1) + try: + json = self.http.post(url, values).json() + except requests.ConnectionError: + try: + time.sleep(1) + json = self.http.post(url, values).json() + except: + return {} +## if method == "messages.get": +## print "method %s with values %s" % (method, str(values)) +## print "response for method %s: %s" % (method, str(json)) + if json.has_key("response"): + return json["response"] + + elif json.has_key("error"): + error = json["error"] + eCode = error["error_code"] + if eCode == 6: # too fast + time.sleep(3) + return self.method(method, values) + elif eCode == 5: # auth failed + raise apiError("Logged out") + if eCode == 14: + if "captcha_sid" in error: # maybe we need check if exists self.captcha + self.captcha = {"sid": error["captcha_sid"], "img": error["captcha_img"]} + raise captchaNeeded + raise apiError(json["error"]) + + def retry(self): + if self.lastMethod: + return self.method(*self.lastMethod) + +def regexp(reg, string, findall = 1): + u""" Поиск по регулярке """ + + reg = re.compile(reg, re.IGNORECASE | re.DOTALL) + if findall: + reg = reg.findall(string) + else: + return reg.search(string) + return reg + + +class vkApiError(Exception): + pass + + +class authError(vkApiError): + pass + + +class apiError(vkApiError): + pass + +class tokenError(vkApiError): + pass + +class captchaNeeded(vkApiError): + pass diff --git a/locales/locale.ru b/locales/locale.ru index 2767f37..7e6f707 100644 --- a/locales/locale.ru +++ b/locales/locale.ru @@ -19,3 +19,5 @@ User is not your friend.=Пользователь не ваш друг! Your friend-list is null.=Ваш список друзей пуст! You're not registered for this action.=Вы должны быть зарегистрированы для этого дейстия. Auth failed! Please register again. This incident will be reported.=Авторизация не удалась. Пожалуйста, зарегистрируйтесь заново. Об этом инциденте будет сообщено. +\LForward messages:=\LПересланные сообщения: +\LAttachments:=\LВложения: \ No newline at end of file -- cgit v1.2.3