diff --git a/README.md b/README.md index f49c31e..48a868e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Лицензия](https://img.shields.io/badge/Лицензия-MIT-blue) ![Совместимость с Python](https://img.shields.io/badge/Python-3.7--3.9-blue) -![Версия библиотеки](https://img.shields.io/badge/pip-3.2.2-blue) +![Версия библиотеки](https://img.shields.io/badge/pip-3.3.0-blue) # VKpyMusic ### is a Python library that provides a simple interface for interacting with the VKontakte (VK) music service API. The library allows developers to easily perform operations related to music and other functionalities available through the VK API. diff --git a/pyproject.toml b/pyproject.toml index fa49e3d..654608f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "vkpymusic" -version = "3.2.2" +version = "3.3.0" homepage = "https://github.com/issamansur/vkpymusic" repository = "https://github.com/issamansur/vkpymusic" description = "Python library for VK Audio (API)" diff --git a/vkpymusic/models/__init__.py b/vkpymusic/models/__init__.py index dad2d9f..0d7275f 100644 --- a/vkpymusic/models/__init__.py +++ b/vkpymusic/models/__init__.py @@ -4,12 +4,15 @@ Classes: Song: A class that represents a song. Playlist: A class that represents a playlist. + UserInfo: A class that represents a main user's info. """ from .song import Song from .playlist import Playlist +from .userinfo import UserInfo __all__ = [ "Song", "Playlist", + "UserInfo", ] diff --git a/vkpymusic/models/song.py b/vkpymusic/models/song.py index 6f1cd40..65b461b 100644 --- a/vkpymusic/models/song.py +++ b/vkpymusic/models/song.py @@ -33,7 +33,7 @@ def __init__( track_id: str, owner_id: str, url: str = "", - ): + ) -> None: """ Initializes a Song object. diff --git a/vkpymusic/models/userinfo.py b/vkpymusic/models/userinfo.py new file mode 100644 index 0000000..e301e8f --- /dev/null +++ b/vkpymusic/models/userinfo.py @@ -0,0 +1,69 @@ +""" +This module contains the UserInfo class. +""" + + +class UserInfo: + """ + A class that represents a user. + + Attributes: + userid (int): The ID of the user. + first_name (str): The first name of the user. + last_name (str): The last name of the user. + photo (str): The URL of the user's photo. + phone (str): The phone number of the user. + """ + + userid: int + first_name: str + last_name: str + photo: str + phone: str + + def __init__( + self, + userid: int, + first_name: str, + last_name: str, + photo: str = "", + phone: str = "", + ) -> None: + """ + Initializes a UserInfo object. + + Args: + userid (int): The ID of the user. + first_name (str): The first name of the user. + last_name (str): The last name of the user. + photo (str): The URL of the user's photo. + phone (str): The phone number of the user. + """ + self.userid = userid + self.first_name = first_name + self.last_name = last_name + self.photo = photo + self.phone = phone + + def __str__(self): + return f"{self.userid} {self.first_name} {self.last_name}" + + def to_dict(self) -> dict: + """ + Converts the user to a dictionary. + """ + return self.__dict__ + + @classmethod + def from_json(cls, item) -> "UserInfo": + """ + Converts a JSON object to a UserInfo object. + """ + userid = int(item["id"]) + first_name = str(item["first_name"]) + last_name = str(item["last_name"]) + if "photo_200" in item: + photo = str(item["photo_200"]) + if "phone" in item: + phone = str(item["phone"]) + return cls(userid, first_name, last_name, photo, phone) diff --git a/vkpymusic/service.py b/vkpymusic/service.py index abf8c40..395fd73 100644 --- a/vkpymusic/service.py +++ b/vkpymusic/service.py @@ -11,7 +11,7 @@ import requests from requests import Response, Session -from .models import Song, Playlist +from .models import Song, Playlist, UserInfo from .utils import Converter, get_logger @@ -97,7 +97,7 @@ def __get_profile_info(token: str) -> Response: ("v", "5.131"), ] with Session() as session: - response = session.post(url=url, data=parameters) + response: Response = session.post(url=url, data=parameters) return response @staticmethod @@ -118,6 +118,9 @@ def check_token(token: str) -> bool: if "error" in data: logger.error("Token is invalid!") return False + if "id" in data["response"]: + logger.info("Token is valid!") + return True except Exception as e: logger.error(e) return False @@ -133,25 +136,22 @@ def is_token_valid(self) -> bool: """ return Service.check_token(self.__token) - def get_user_info(self) -> (int, str): + def get_user_info(self) -> UserInfo: """ Get user info by token. Returns: - (int, str): Tuple of user id and first + last name. + UserInfo: Instance of 'UserInfo'. """ logger.info("Getting user info...") try: - response = Service.__get_profile_info(self.__token) - data = json.loads(response.content.decode("utf-8")) - user_id = int(data["response"]["id"]) - first_name = data["response"]["first_name"] - last_name = data["response"]["last_name"] + response: Response = Service.__get_profile_info(self.__token) + userInfo: UserInfo = Converter.response_to_userinfo(response) except Exception as e: logger.error(e) return - logger.info(f"User info: {user_id}, {first_name}, {last_name}") - return user_id, first_name + " " + last_name + logger.info(f"User info: {userInfo}") + return userInfo ####################################### # PRIVATE METHODS FOR CREATING REQUESTS diff --git a/vkpymusic/service_async.py b/vkpymusic/service_async.py index c48f28b..f263e62 100644 --- a/vkpymusic/service_async.py +++ b/vkpymusic/service_async.py @@ -10,7 +10,7 @@ import aiofiles from httpx import AsyncClient, Response -from .models import Song, Playlist +from .models import Song, Playlist, UserInfo from .utils import Converter, get_logger @@ -110,9 +110,12 @@ async def check_token(token: str) -> bool: try: response = await ServiceAsync.__get_profile_info(token) data = json.loads(response.content.decode("utf-8")) - if "error" in data: + if "error" in data or "id" not in data: logger.error("Token is invalid!") return False + if "id" in data["response"]: + logger.info("Token is valid!") + return True except Exception as e: logger.error(e) return False @@ -129,26 +132,22 @@ async def is_token_valid(self) -> bool: logger.info("Checking token...") return await ServiceAsync.check_token(self.__token) - async def get_user_info(self) -> (int, str): + async def get_user_info(self) -> UserInfo: """ - Get user id and username. + Get user info by token. Returns: - tuple[int, str]: Tuple of user id and username. + UserInfo: Instance of 'UserInfo'. """ logger.info("Getting user info...") try: - response = await ServiceAsync.__get_profile_info(self.__token) - data = json.loads(response.content.decode("utf-8")) - user_id = int(data["response"]["id"]) - first_name = data["response"]["first_name"] - last_name = data["response"]["last_name"] + response: Response = await ServiceAsync.__get_profile_info(self.__token) + userInfo = Converter.response_to_userinfo(response) except Exception as e: logger.error(e) return - logger.info(f"User info: {user_id}, {first_name}, {last_name}") - return user_id, first_name + " " + last_name - + logger.info(f"User info: {userInfo}") + return userInfo ####################################### # PRIVATE METHODS FOR CREATING REQUESTS diff --git a/vkpymusic/token_receiver.py b/vkpymusic/token_receiver.py index 3f865d4..3d9a663 100644 --- a/vkpymusic/token_receiver.py +++ b/vkpymusic/token_receiver.py @@ -106,9 +106,19 @@ def __init__(self, login: str, password: str, client: str = "Kate") -> None: self.client = clients["Kate"] self.__token = None - def __request_auth( + def request_auth( self, code: Optional[str] = None, captcha: Optional[Tuple[int, str]] = None ) -> Response: + """ + Request auth from VK. + + Args: + code (Optional[str]): Code from VK/SMS (default value = None). + captcha (Optional[Tuple[int, str]]): Captcha (default value = None). + + Returns: + Response: Response from VK. + """ query_params = [ ("grant_type", "password"), ("client_id", self.client.client_id), @@ -130,7 +140,16 @@ def __request_auth( response = session.post("https://oauth.vk.com/token", data=query_params) return response - def __request_code(self, sid: Union[str, int]): + def request_code(self, sid: Union[str, int]): + """ + Request code from VK. + + Args: + sid (Union[str, int]): Sid from VK. + + Returns: + Response: Response from VK. + """ query_params = [("sid", str(sid)), ("v", "5.131")] with Session() as session: session.headers.update({"User-Agent": self.client.user_agent}) @@ -172,7 +191,7 @@ def auth( Returns: bool: Boolean value indicating whether authorization was successful or not. """ - response_auth: requests.Response = self.__request_auth() + response_auth: requests.Response = self.request_auth() response_auth_json = json.loads(response_auth.content.decode("utf-8")) while "error" in response_auth_json: error = response_auth_json["error"] @@ -181,20 +200,20 @@ def auth( captcha_sid: str = response_auth_json["captcha_sid"] captcha_img: str = response_auth_json["captcha_img"] captcha_key: str = on_captcha(captcha_img) - response_auth = self.__request_auth(captcha=(captcha_sid, captcha_key)) + response_auth = self.request_auth(captcha=(captcha_sid, captcha_key)) response_auth_json = json.loads(response_auth.content.decode("utf-8")) elif error == "need_validation": sid = response_auth_json["validation_sid"] # response2: requests.Response = - self.__request_code(sid) + self.request_code(sid) # response2_json = json.loads(response2.content.decode('utf-8')) code: str = on_2fa() - response_auth = self.__request_auth(code=code) + response_auth = self.request_auth(code=code) response_auth_json = json.loads(response_auth.content.decode("utf-8")) elif error == "invalid_request": logger.warn("Invalid code. Try again!") code: str = on_2fa() - response_auth = self.__request_auth(code=code) + response_auth = self.request_auth(code=code) response_auth_json = json.loads(response_auth.content.decode("utf-8")) elif error == "invalid_client": del self.__login diff --git a/vkpymusic/token_receiver_async.py b/vkpymusic/token_receiver_async.py index 92322e3..bc34391 100644 --- a/vkpymusic/token_receiver_async.py +++ b/vkpymusic/token_receiver_async.py @@ -50,9 +50,19 @@ def __init__(self, login, password, client="Kate"): self.client = clients["Kate"] self.__token = None - async def __request_auth( + async def request_auth( self, code: str = None, captcha: Tuple[int, str] = None ) -> Response: + """ + Request auth from VK. + + Args: + code (Optional[str]): Code from VK/SMS (default value = None). + captcha (Optional[Tuple[int, str]]): Captcha (default value = None). + + Returns: + Response: Response from VK. + """ query_params = [ ("grant_type", "password"), ("client_id", self.client.client_id), @@ -76,7 +86,16 @@ async def __request_auth( ) return response - async def __request_code(self, sid: Union[str, int]): + async def request_code(self, sid: Union[str, int]): + """ + Request code from VK. + + Args: + sid (Union[str, int]): Sid from VK. + + Returns: + Response: Response from VK. + """ query_params = [("sid", str(sid)), ("v", "5.131")] async with AsyncClient() as session: session.headers.update({"User-Agent": self.client.user_agent}) @@ -118,7 +137,7 @@ async def auth( Returns: bool: Boolean value indicating whether authorization was successful or not. """ - response_auth = await self.__request_auth() + response_auth = await self.request_auth() response_auth_json = json.loads(response_auth.content.decode("utf-8")) while "error" in response_auth_json: error = response_auth_json["error"] @@ -126,22 +145,22 @@ async def auth( captcha_sid: str = response_auth_json["captcha_sid"] captcha_img: str = response_auth_json["captcha_img"] captcha_key: str = await on_captcha(captcha_img) - response_auth = await self.__request_auth( + response_auth = await self.request_auth( captcha=(captcha_sid, captcha_key) ) response_auth_json = json.loads(response_auth.content.decode("utf-8")) elif error == "need_validation": sid = response_auth_json["validation_sid"] # response2: requests.Response = - await self.__request_code(sid) + await self.request_code(sid) # response2_json = json.loads(response2.content.decode('utf-8')) code: str = await on_2fa() - response_auth = await self.__request_auth(code=code) + response_auth = await self.request_auth(code=code) response_auth_json = json.loads(response_auth.content.decode("utf-8")) elif error == "invalid_request": logger.warn("Invalid code. Try again!") code: str = await on_2fa() - response_auth = await self.__request_auth(code=code) + response_auth = await self.request_auth(code=code) response_auth_json = json.loads(response_auth.content.decode("utf-8")) elif error == "invalid_client": del self.__login diff --git a/vkpymusic/utils/converter.py b/vkpymusic/utils/converter.py index 0f248ca..b90f50f 100644 --- a/vkpymusic/utils/converter.py +++ b/vkpymusic/utils/converter.py @@ -8,7 +8,7 @@ from requests import Response -from ..models import Song, Playlist +from ..models import Song, Playlist, UserInfo from .logger import get_logger @@ -61,3 +61,21 @@ def response_to_playlists(response: Response) -> List[Playlist]: playlist = Playlist.from_json(item) playlists.append(playlist) return playlists + + @staticmethod + def response_to_userinfo(response: Response) -> UserInfo: + """Converts a response to a UserInfo. + + Args: + response (Response): The response object from VK. + + Returns: + UserInfo: A UserInfo converted from the response. + """ + response = json.loads(response.content.decode("utf-8")) + try: + items = response["response"] + except Exception as e: + logger.error(e) + userinfo: UserInfo = UserInfo.from_json(items) + return userinfo