Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions vk_api/credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
:authors: python273, kyzima-spb
:license: Apache License, Version 2.0, see LICENSE file

:copyright: (c) 2019 python273
"""

import json
import re
import typing as t

import requests

from .exceptions import ParseError
from .utils import (
set_cookies_from_list,
generate_device_id,
search_re,
)


__all__ = (
'WebLoginCredentials',
)


class WebLoginCredentials:
"""
Парсит и хранит данные новой формы входа,
нужные для процесса аутентификации и авторизации пользователя.
"""

SIGNIN_URL = 'https://m.vk.com/join?vkid_auth_type=sign_in'
DEFAULT_COOKIES = [
{ # если не установлено, то не будет редирект на страницу с данными аутентификации
'version': 0,
'name': 'remixmdevice',
'value': '1920/1080/2/!!-!!!!',
'port': None,
'port_specified': False,
'domain': '.vk.com',
'domain_specified': True,
'domain_initial_dot': True,
'path': '/',
'path_specified': True,
'secure': True,
'expires': None,
'discard': False,
'comment': None,
'comment_url': None,
'rfc2109': False,
'rest': {}
}
]

def __init__(
self,
session: requests.Session,
api_version: str = '5.207',
) -> None:
set_cookies_from_list(session.cookies, self.DEFAULT_COOKIES)

response = session.get(self.SIGNIN_URL)
pattern = re.compile(r'window\.init\s*=\s*({.*?});', re.DOTALL)
json_config = search_re(pattern, response.text)

if json_config is None:
raise ParseError('Failed to get the value of variable window.init.')

self._config = json.loads(json_config)

self.sid = ''
self.can_skip_password = False

self.device_id = generate_device_id()
self.api_version = api_version

@property
def app_id(self) -> str:
"""ID приложения, через которое выполняется вход."""
return self._config['auth']['host_app_id']

@property
def uuid(self) -> str:
"""
Уникальный идентификатор запроса.

(вероятно, временный для конкретного входа).
"""
return self._config['data']['uuid']

@property
def access_token(self) -> str:
"""Токен доступа."""
return self._config['auth']['access_token']

@property
def anonymous_token(self) -> str:
"""Анонимный токен доступа."""
return self._config['auth']['anonymous_token']
21 changes: 19 additions & 2 deletions vk_api/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,27 @@
:copyright: (c) 2019 python273
"""

from enum import IntEnum
import enum


class VkUserPermissions(IntEnum):
class VerificationMethod(enum.StrEnum):
"""
Перечисление способов подтверждения входа в аккаунт.

EMAIL, SMS и PUSH требуют вызова метода API для отправки.
"""
PUSH = enum.auto()
EMAIL = enum.auto()
QR_CODE = enum.auto()
CODEGEN = enum.auto()
SMS = enum.auto()
CALLRESET = enum.auto()
PASSWORD = enum.auto()
RESERVE_CODE = enum.auto()
PASSKEY = enum.auto()


class VkUserPermissions(enum.IntEnum):
"""
Перечисление прав пользователя.
Список прав получается побитовым сложением (x | y) каждого права.
Expand Down
19 changes: 19 additions & 0 deletions vk_api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
:copyright: (c) 2019 python273
"""

import typing as t

TWOFACTOR_CODE = -2
HTTP_ERROR_CODE = -1
TOO_MANY_RPS_CODE = 6
CAPTCHA_ERROR_CODE = 14
NEED_VALIDATION_CODE = 17
CONFIRMATION_ERROR_CODE = 1110


class VkApiError(Exception):
Expand Down Expand Up @@ -46,6 +48,23 @@ class TwoFactorError(AuthError):
pass


class ParseError(AuthError):
"""Любая ошибка при парсинге исходных кодов сайта."""


class AuthorizeError(AuthError):
"""
Любая ошибка, которая может возникнуть в момент авторизации
существующего пользователя с проверенным кодом подтверждения или паролем.
(https://login.vk.com/?act=connect_authorize)
"""
def __init__(self, error: t.Dict[str, t.Any]) -> None:
self.error = error

def __str__(self) -> str:
return '[{error_code}] {error_info}'.format(**self.error)
Comment on lines +51 to +65
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не думаю нужно отдельные ошибки, можно просто AuthError. Смысл отдельные добавлять только если их обрабатывать как-то можно

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuthError базовый тип, к нему по логике не возможно добавить какую-то дополнительную информацию об ошибке. Вызовы к login возвращают стандартизированный ответ, в котором есть error_code и другая информация об ошибке. Я думаю могут быть ситуации, когда пользователю библиотеки эта информация пригодится =)

ParseError возможно действительно лишний. В любом случае позже я хочу добавить вход по qr-коду и посмотрю свежим взглядом, пусть пока "полежит" =)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Такое можно просто в текст ошибки, обрабатывать это все равно норм не получится. Можно конечно в AuthError добавить поле для какой-то общей error_metadata и туда сувать всякое, но тоже не уверен есть ли смысл



class SecurityCheck(AuthError):

def __init__(self, phone_prefix=None, phone_postfix=None, response=None):
Expand Down
8 changes: 8 additions & 0 deletions vk_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from __future__ import print_function

import random
import secrets
import string

try:
import simplejson as json
Expand Down Expand Up @@ -166,3 +168,9 @@ def send(self, request, **kwargs):

vk_session.logger.setLevel(logging.INFO)
vk_session.logger.addHandler(logging.StreamHandler(sys.stdout))


def generate_device_id(n: int = 21) -> str:
"""Generates a random string of length n from a given set of characters."""
charset = f'{string.digits}{string.ascii_letters}-_'
return ''.join(secrets.choice(charset) for _ in range(n))
Loading