2020CAPTCHA_ERROR_CODE = 14
2121NEED_VALIDATION_CODE = 17
2222HTTP_ERROR_CODE = - 1
23+ TWOFACTOR_CODE = - 2
2324
2425RE_LOGIN_HASH = re .compile (r'name="lg_h" value="([a-z0-9]+)"' )
2526RE_CAPTCHAID = re .compile (r'sid=(\d+)' )
2627RE_NUMBER_HASH = re .compile (r"al_page: '3', hash: '([a-z0-9]+)'" )
28+ RE_AUTH_HASH = re .compile (r"hash: '([a-z_0-9]+)'" )
2729RE_TOKEN_URL = re .compile (r'location\.href = "(.*?)"\+addr;' )
2830
2931RE_PHONE_PREFIX = re .compile (r'phone_number">(.*?)<' )
3335
3436class VkApi (object ):
3537 def __init__ (self , login = None , password = None , number = None , sec_number = None ,
36- token = None , proxies = None , captcha_handler = None ,
38+ token = None ,
39+ proxies = None ,
40+ auth_handler = None , captcha_handler = None ,
3741 config_filename = 'vk_config.json' ,
38- api_version = '5.35 ' , app_id = 2895443 , scope = 33554431 ,
42+ api_version = '5.44 ' , app_id = 2895443 , scope = 33554431 ,
3943 client_secret = None ):
4044 """
4145 :param login: Логин ВКонтакте
@@ -51,6 +55,9 @@ def __init__(self, login=None, password=None, number=None, sec_number=None,
5155 :param proxies: proxy server
5256 {'http': 'http://127.0.0.1:8888/',
5357 'https': 'https://127.0.0.1:8888/'}
58+ :param auth_handler: Функция для обработки двухфакторной аутентификации,
59+ обязана возвращать строку с кодом для
60+ прохождения аутентификации
5461 :param captcha_handler: Функция для обработки капчи
5562 :param config_filename: Расположение config файла
5663
@@ -88,7 +95,8 @@ def __init__(self, login=None, password=None, number=None, sec_number=None,
8895 self .error_handlers = {
8996 NEED_VALIDATION_CODE : self .need_validation_handler ,
9097 CAPTCHA_ERROR_CODE : captcha_handler or self .captcha_handler ,
91- TOO_MANY_RPS_CODE : self .too_many_rps_handler
98+ TOO_MANY_RPS_CODE : self .too_many_rps_handler ,
99+ TWOFACTOR_CODE : auth_handler or self .auth_handler
92100 }
93101
94102 def authorization (self , reauth = False ):
@@ -139,6 +147,10 @@ def vk_login(self, captcha_sid=None, captcha_key=None):
139147
140148 remixsid = None
141149
150+ if 'act=authcheck' in response .url :
151+ code = self .error_handlers [TWOFACTOR_CODE ]()
152+ response = self .twofactor (response , code )
153+
142154 if 'remixsid' in self .http .cookies :
143155 remixsid = self .http .cookies ['remixsid' ]
144156 elif 'remixsid6' in self .http .cookies : # ipv6?
@@ -162,7 +174,7 @@ def vk_login(self, captcha_sid=None, captcha_key=None):
162174 captcha = Captcha (self , captcha_sid , self .vk_login )
163175
164176 if self .error_handlers [CAPTCHA_ERROR_CODE ]:
165- self .error_handlers [CAPTCHA_ERROR_CODE ](captcha )
177+ return self .error_handlers [CAPTCHA_ERROR_CODE ](captcha )
166178 else :
167179 raise AuthorizationError ('Authorization error (capcha)' )
168180 elif 'm=1' in response .url :
@@ -176,6 +188,28 @@ def vk_login(self, captcha_sid=None, captcha_key=None):
176188 if 'act=blocked' in response .url :
177189 raise AccountBlocked ('Account is blocked' )
178190
191+ def twofactor (self , response , code ):
192+ """ Двухфакторная аутентификация
193+ :param reponse: запрос, содержащий страницу с приглашением к аутентификации
194+ :param code: код, который необходимо ввести для успешной аутентификации
195+ """
196+ assert code != None , "Empty code doesn't acceptable"
197+ assert len (code ) == 6 , "Length of code cannot be other than 6."
198+
199+ auth_hash = search_re (RE_AUTH_HASH , response .text )
200+ url = 'https://vk.com/al_login.php'
201+ if auth_hash :
202+ values = {
203+ 'act' : 'a_authcheck_code' ,
204+ 'code' : code ,
205+ 'remember' : 0 , # TODO: Fix me(device remembering)
206+ 'hash' : auth_hash ,
207+ }
208+ response = self .http .post (url , values , cookies = response .cookies )
209+ if url not in response .url :
210+ return response
211+ raise TwoFactorError ('Incorrect code: %s' % code )
212+
179213 def security_check (self , url = None , response = None ):
180214 if url :
181215 response = self .http .get (url )
@@ -315,6 +349,12 @@ def too_many_rps_handler(self, error):
315349 time .sleep (0.5 )
316350 return error .try_method ()
317351
352+ def auth_handler (self ):
353+ raise AuthorizationError ("No handler for two-factor authorization." )
354+
355+ def get_api (self ):
356+ return VkApiMethod (self )
357+
318358 def method (self , method , values = None , captcha_sid = None , captcha_key = None ):
319359 """ Использование методов API
320360
@@ -387,6 +427,25 @@ def method(self, method, values=None, captcha_sid=None, captcha_key=None):
387427 return response ['response' ]
388428
389429
430+ class VkApiMethod :
431+ def __init__ (self , vk , method = None ):
432+ self ._vk = vk
433+ self ._method = method
434+
435+ def __getattr__ (self , method ):
436+ if self ._method :
437+ self ._method += '.' + method
438+ return self
439+
440+ return VkApiMethod (self ._vk , method )
441+
442+ def __call__ (self , * args , ** kwargs ):
443+ return self ._vk .method (self ._method , kwargs )
444+
445+ def get_doc (self ):
446+ doc (self ._method )
447+
448+
390449def doc (method = None ):
391450 """ Открывает документацию на метод или список всех методов
392451
@@ -444,6 +503,10 @@ class AccountBlocked(AuthorizationError):
444503 pass
445504
446505
506+ class TwoFactorError (AuthorizationError ):
507+ pass
508+
509+
447510class SecurityCheck (AuthorizationError ):
448511 def __init__ (self , phone_prefix = None , phone_postfix = None , response = None ):
449512 self .phone_prefix = phone_prefix
0 commit comments