2020CAPTCHA_ERROR_CODE = 14
2121NEED_VALIDATION_CODE = 17
2222HTTP_ERROR_CODE = - 1
23+ TWOFACTOR_CODE = 666
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">(.*?)<' )
3436class VkApi (object ):
3537 def __init__ (self , login = None , password = None , number = None , sec_number = None ,
3638 token = None ,
37- proxies = None , captcha_handler = None , config_filename = 'vk_config.json' ,
39+ proxies = None , auth_handler = None , captcha_handler = None , config_filename = 'vk_config.json' ,
3840 api_version = '5.35' , app_id = 2895443 , scope = 33554431 ,
3941 client_secret = None ):
4042 """
@@ -51,6 +53,9 @@ def __init__(self, login=None, password=None, number=None, sec_number=None,
5153 :param proxies: proxy server
5254 {'http': 'http://127.0.0.1:8888/',
5355 'https': 'https://127.0.0.1:8888/'}
56+ :param auth_handler: Функция для обработки двухфакторной аутентификации,
57+ обязана возвращать строку с кодом для
58+ прохождения аутентификации
5459 :param captcha_handler: Функция для обработки капчи
5560 :param config_filename: Расположение config файла
5661
@@ -88,7 +93,8 @@ def __init__(self, login=None, password=None, number=None, sec_number=None,
8893 self .error_handlers = {
8994 NEED_VALIDATION_CODE : self .need_validation_handler ,
9095 CAPTCHA_ERROR_CODE : captcha_handler or self .captcha_handler ,
91- TOO_MANY_RPS_CODE : self .too_many_rps_handler
96+ TOO_MANY_RPS_CODE : self .too_many_rps_handler ,
97+ TWOFACTOR_CODE : auth_handler or self .auth_handler
9298 }
9399
94100 def authorization (self , reauth = False ):
@@ -139,6 +145,10 @@ def vk_login(self, captcha_sid=None, captcha_key=None):
139145
140146 remixsid = None
141147
148+ if 'act=authcheck' in response .url :
149+ code = self .error_handlers [TWOFACTOR_CODE ]()
150+ response = self .twofactor (response , code )
151+
142152 if 'remixsid' in self .http .cookies :
143153 remixsid = self .http .cookies ['remixsid' ]
144154 elif 'remixsid6' in self .http .cookies : # ipv6?
@@ -176,6 +186,28 @@ def vk_login(self, captcha_sid=None, captcha_key=None):
176186 if 'act=blocked' in response .url :
177187 raise AccountBlocked ('Account is blocked' )
178188
189+ def twofactor (self , response , code ):
190+ """ Двухфакторная аутентификация
191+ :param reponse: запрос, содержащий страницу с приглашением к аутентификации
192+ :param code: код, который необходимо ввести для успешной аутентификации
193+ """
194+ assert code != None , "Empty code doesn't acceptable"
195+ assert len (code ) == 6 , "Length of code cannot be other than 6."
196+
197+ auth_hash = search_re (RE_AUTH_HASH , response .text )
198+ url = 'https://vk.com/al_login.php'
199+ if auth_hash :
200+ values = {
201+ 'act' : 'a_authcheck_code' ,
202+ 'code' : code ,
203+ 'remember' : 0 , # TODO: Fix me(device remembering)
204+ 'hash' : auth_hash ,
205+ }
206+ response = self .http .post (url , values , cookies = response .cookies )
207+ if url not in response .url :
208+ return response
209+ raise TwoFactorError ('Incorrect code: %s' % code )
210+
179211 def security_check (self , url = None , response = None ):
180212 if url :
181213 response = self .http .get (url )
@@ -314,6 +346,9 @@ def too_many_rps_handler(self, error):
314346 time .sleep (0.5 )
315347 error .try_method ()
316348
349+ def auth_handler (self ):
350+ raise AuthorizationError ("No handler for two-factor authorization." )
351+
317352 def method (self , method , values = None , captcha_sid = None , captcha_key = None ):
318353 """ Использование методов API
319354
@@ -442,6 +477,8 @@ class BadPassword(AuthorizationError):
442477class AccountBlocked (AuthorizationError ):
443478 pass
444479
480+ class TwoFactorError (AuthorizationError ):
481+ pass
445482
446483class SecurityCheck (AuthorizationError ):
447484 def __init__ (self , phone_prefix , phone_postfix , response = None ):
0 commit comments