1414import time
1515
1616DELAY = 1.0 / 3 # 3 requests per second
17+ CAPTCHA_ERROR_CODE = 14
18+ NEED_VALIDATION_CODE = 17
1719
1820
1921class VkApi (object ):
20- def __init__ (self ,
21- login = None , password = None , number = None ,
22- token = None ,
23- proxies = None ,
24- version = '5.5' , app_id = 2895443 , scope = 2097151 ):
22+ def __init__ (self , login = None , password = None , number = None , token = None ,
23+ proxies = None , captcha_handler = None ,
24+ api_version = '5.21' , app_id = 2895443 , scope = 2097151 ):
2525 '''
2626 :param login: Логин ВКонтакте
2727 :param password: Пароль ВКонтакте
28- :param number: Номер при проверке безопасности
29- Номер: +7 12345678 90
30- number = 12345678
28+ :param number: Номер для проверке безопасности (указывать, если
29+ в качестве логина используется не номер)
30+
3131 :param token: access_token
3232 :param proxies: proxy server
3333 {'http': 'http://127.0.0.1:8888/',
34- 'https' : 'https://127.0.0.1:8888/'}
35- :param version : Версия API (default: '5.0 ')
34+ 'https': 'https://127.0.0.1:8888/'}
35+ :param api_version : Версия API (default: '5.21 ')
3636 :param app_id: Standalone-приложение (default: 2895443)
3737 :param scope: Запрашиваемые права (default: 2097151)
3838 '''
@@ -44,7 +44,7 @@ def __init__(self,
4444 self .sid = None
4545 self .token = {'access_token' : token }
4646
47- self .version = version
47+ self .api_version = api_version
4848 self .app_id = app_id
4949 self .scope = scope
5050
@@ -53,19 +53,26 @@ def __init__(self,
5353 self .http = requests .Session ()
5454 self .http .proxies = proxies # Ставим прокси
5555 self .http .headers = { # Притворимся браузером
56- 'User-agent' : 'Mozilla/5.0 (Windows NT 6.1 ; WOW64; rv:20 .0) ' \
57- 'Gecko/20100101 Firefox/20 .0'
56+ 'User-agent' : 'Mozilla/5.0 (Windows NT 6.2 ; WOW64; rv:28 .0) ' \
57+ 'Gecko/20100101 Firefox/28 .0'
5858 }
5959 self .http .verify = False
6060
6161 self .last_request = 0.0
6262
63+ self .error_handlers = {
64+ NEED_VALIDATION_CODE : self .need_validation_handler ,
65+ CAPTCHA_ERROR_CODE : captcha_handler or self .captcha_handler
66+ }
67+
6368 if login and password :
6469 self .sid = self .settings ['remixsid' ]
6570 self .token = self .settings ['access_token' ]
6671
6772 if not self .check_sid ():
6873 self .vk_login ()
74+ else :
75+ self .security_check ('http://vk.com' )
6976
7077 if not self .check_token ():
7178 self .api_login ()
@@ -103,39 +110,47 @@ def vk_login(self, captcha_sid=None, captcha_key=None):
103110 self .sid = remixsid
104111
105112 elif 'sid=' in response .url :
106- raise authorization_error ('Authorization error (capcha)' )\
107- # TODO: write me
108- # Capcha handler
109- # Capcha object
113+ captcha_sid = regexp (r'sid=(\d+)' , response .url )[0 ]
114+ captcha = Captcha (self , captcha_sid , self .vk_login )
115+
116+ if self .error_handlers [CAPTCHA_ERROR_CODE ]:
117+ self .error_handlers [CAPTCHA_ERROR_CODE ](captcha )
118+ else :
119+ raise AuthorizationError ('Authorization error (capcha)' )
110120 else :
111- raise authorization_error ( 'Authorization error (bad password) ' )
121+ raise BadPassword ( 'Bad password' )
112122
113123 if 'security_check' in response .url :
114- self .security_check (response )
124+ self .security_check (response = response )
115125
116126 def security_check (self , url = None , response = None ):
117127 if url :
118128 response = self .http .get (url )
129+ if not 'security_check' in response .url :
130+ return
119131
120- # Проверяем, является ли логин номером
121- if not self .number :
122- phone_postfix = regexp (r'class="phone_postfix">(.*?)</span>' ,
123- response .text )
124-
125- phone_postfix = phone_postfix [0 ].strip ()
132+ phone_prefix = regexp (r'label ta_r">(.*?)<' ,
133+ response .text )
134+ phone_prefix = phone_prefix [0 ].strip ()
126135
127- if self .login [- len (phone_postfix ):] == phone_postfix :
128- self .number = self .login
136+ phone_postfix = regexp (r'phone_postfix">(.*?)<' ,
137+ response .text )
138+ phone_postfix = phone_postfix [0 ].strip ()
129139
130140 if self .number :
141+ code = code_from_number (phone_prefix , phone_postfix , self .number )
142+ else :
143+ code = code_from_number (phone_prefix , phone_postfix , self .login )
144+
145+ if code :
131146 number_hash = regexp (r'security_check.*?hash: \'(.*?)\'\};' ,
132147 response .text )[0 ]
133148
134149 values = {
135150 'act' : 'security_check' ,
136151 'al' : '1' ,
137152 'al_page' : '3' ,
138- 'code' : self . number ,
153+ 'code' : code ,
139154 'hash' : number_hash ,
140155 'to' : ''
141156 }
@@ -145,8 +160,7 @@ def security_check(self, url=None, response=None):
145160 if response .text .split ('<!>' )[4 ] == '4' :
146161 return True
147162
148- raise authorization_error ('Security check (enter number)' )
149-
163+ raise SecurityCheck ('Enter number' )
150164
151165 def check_sid (self ):
152166 ''' Прверка Cookies remixsid на валидность '''
@@ -194,28 +208,38 @@ def api_login(self):
194208 self .settings ['access_token' ] = token
195209 self .token = token
196210 else :
197- raise authorization_error ('Authorization error (api)' )
211+ raise AuthorizationError ('Authorization error (api)' )
198212
199213 def check_token (self ):
200214 ''' Прверка access_token на валидность '''
201215
202216 if self .token :
203217 try :
204218 self .method ('isAppUser' )
205- except apiError :
219+ except ApiError :
206220 return False
207221
208222 return True
209223
210- def method (self , method , values = None ):
211- ''' Использование методов API
224+ def captcha_handler (self , captcha ):
225+ ''' http://vk.com/dev/captcha_error '''
226+ pass
212227
213- param: method - название метода
214- 'users.get'
228+ def need_validation_handler (self , error ):
229+ ''' http://vk.com/dev/need_validation '''
230+ # TODO: write me
231+ pass
232+
233+ def method (self , method , values = None , captcha_sid = None , captcha_key = None ):
234+ '''
235+ Использование методов API
215236
216- param: values - параметры
217- {'uids': 1}
237+ :param method: метод
238+ :param values: параметры
239+ :param captcha_sid:
240+ :param captcha_key:
218241 '''
242+
219243 url = 'https://api.vk.com/method/%s' % method
220244
221245 if values :
@@ -224,30 +248,46 @@ def method(self, method, values=None):
224248 values = {}
225249
226250 if not 'v' in values :
227- values .update ({'v' : self .version })
251+ values .update ({'v' : self .api_version })
228252
229253 if self .token :
230254 values .update ({'access_token' : self .token ['access_token' ]})
231255
256+ if captcha_sid and captcha_key :
257+ values .update ({
258+ 'captcha_sid' : captcha_sid ,
259+ 'captcha_key' : captcha_key
260+ })
261+
232262 # Ограничение 3 запроса в секунду
233- sleep = DELAY - (time .time () - self .last_request )
263+ delay = DELAY - (time .time () - self .last_request )
234264
235- if sleep > 0 :
236- time .sleep (sleep )
265+ if delay > 0 :
266+ time .sleep (delay )
237267
238268 response = self .http .post (url , values ).json ()
239269 self .last_request = time .time ()
240270
241271 if 'error' in response :
242- # TODO: write me
243- # Capcha handler
244- # Capcha object
245-
246- error = apiError (response ['error' ], self , method , values )
247-
248- if error .code == 17 :
249- print 'HANDLER 17'
250- # TODO: number handler
272+ error = ApiError (self , method , values , response ['error' ])
273+ error_code = error .code
274+
275+ if error_code in self .error_handlers :
276+ if error_code == CAPTCHA_ERROR_CODE :
277+ # TODO: wtf
278+ error = Captcha (
279+ self ,
280+ error .error ['captcha_sid' ],
281+ self .method ,
282+ (method ,),
283+ {'values' : values },
284+ error .error ['captcha_img' ]
285+ )
286+
287+ response = self .error_handlers [error_code ](error )
288+
289+ if response is not None :
290+ return response
251291
252292 raise error
253293 else :
@@ -262,29 +302,79 @@ def regexp(reg, string):
262302 return reg
263303
264304
265- class vk_api_error (Exception ):
305+ def code_from_number (phone_prefix , phone_postfix , number ):
306+ prefix_len = len (phone_prefix )
307+ postfix_len = len (phone_postfix )
308+
309+ if (prefix_len + postfix_len ) > len (number ):
310+ return
311+
312+ # Сравниваем начало номера
313+ if not number [:prefix_len ] == phone_prefix :
314+ return
315+
316+ # Сравниваем конец номера
317+ if not number [- postfix_len :] == phone_postfix :
318+ return
319+
320+ return number [prefix_len :- postfix_len ]
321+
322+
323+ class AuthorizationError (Exception ):
266324 pass
267325
268326
269- class authorization_error ( vk_api_error ):
327+ class BadPassword ( AuthorizationError ):
270328 pass
271329
272330
273- class apiError (Exception ):
274- def __init__ (self , error , vk , method , values ):
275- self .error = error
331+ class SecurityCheck (AuthorizationError ):
332+ pass
333+
334+
335+ class ApiError (Exception ):
336+ def __init__ (self , vk , method , values , error ):
276337 self .vk = vk
277338 self .method = method
278339 self .values = values
279340 self .code = error ['error_code' ]
341+ self .error = error
280342
281343 def try_method (self ):
282344 return self .vk .method (self .method , self .values )
283345
284346 def __str__ (self ):
285- return self .error ['error_msg' ]
347+ return '[{}] {}' .format (self .error ['error_code' ],
348+ self .error ['error_msg' ])
286349
287350
288- class Capcha ():
289- pass
351+ class Captcha (Exception ):
352+ def __init__ (self , vk , captcha_sid ,
353+ func , args = None , kwargs = None , url = None ):
354+ self .vk = vk
355+ self .sid = captcha_sid
356+ self .func = func
357+ self .args = args or ()
358+ self .kwargs = kwargs or {}
359+
360+ self .key = None
361+ self .url = url
362+
363+ def get_url (self ):
364+ if not self .url :
365+ self .url = 'http://api.vk.com/captcha.php?sid={}' .format (self .sid )
366+
367+ return self .url
290368
369+ def try_again (self , key ):
370+ self .key = key
371+
372+ self .kwargs .update ({
373+ 'captcha_sid' : self .sid ,
374+ 'captcha_key' : self .key
375+ })
376+
377+ return self .func (* self .args , ** self .kwargs )
378+
379+ def __str__ (self ):
380+ return 'Captcha needed'
0 commit comments