From d739c04925ce8d39ab360fd45738317fb66e9f84 Mon Sep 17 00:00:00 2001 From: "Mikhail Andreev (adw0rd)" Date: Sat, 7 Nov 2020 00:55:31 +0300 Subject: [PATCH] Editing Bio [#8] Added .account_info, .account_edit, .account_change_picture and tests for it --- README.md | 42 ++++++++++++++++++++++------------------ instagrapi/account.py | 37 +++++++++++++++++++++++++++++++++-- instagrapi/extractors.py | 6 +++++- instagrapi/types.py | 16 +++++++++++++++ instagrapi/user.py | 4 ++-- tests.py | 35 ++++++++++++++++++++++++++++++++- 6 files changed, 115 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index c5232dde..301d2c38 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,9 @@ The current types are in [types.py](/instagrapi/types.py): | Media | Media (Photo, Video, Album, IGTV or Story) | | Resource | Part of Media (for albums) | | MediaOembed | Short version of Media | -| User | User data | -| UserShort | Short user data (stored in Usertag, Comment, Media, Direct) | +| Account | Full private info for your account (e.g. email, phone_number) | +| User | Full public user data | +| UserShort | Short public user data (stored in Usertag, Comment, Media, Direct) | | Usertag | Tag user in Media (coordinates + UserShort) | | Location | GEO location (GEO coordinates, name, address) | | Collection | Collection of medias (name, picture and list of medias) | @@ -84,20 +85,23 @@ The current types are in [types.py](/instagrapi/types.py): This is your authorized account -| Method | Return | Description | -| -------------------------------------------------------- | -------- | ------------------------------------------------------------- | -| Client(settings: dict = {}, proxy: str = "") | bool | Login by username and password | -| login(username: str, password: str) | bool | Login by username and password | -| relogin() | bool | Relogin with clean cookies (required cl.username/cl.password) | -| login_by_sessionid(sessionid: str) | bool | Login by sessionid from Instagram site | -| get_settings() | dict | Return settings dict (more details below) | -| set_proxy(dsn: str) | dict | Support socks and http/https proxy | -| cookie_dict | dict | Return cookies | -| user_id | int | Return your user_id (after login) | -| device | dict | Return device dict which we pass to Instagram | -| set_device(device: dict) | bool | Change device settings | -| set_user_agent(user_agent: str = "") | bool | Change User-Agent header | -| base_headers | dict | Base headers for Instagram | +| Method | Return | Description | +| -------------------------------------------------------- | --------- | ------------------------------------------------------------- | +| Client(settings: dict = {}, proxy: str = "") | bool | Login by username and password | +| login(username: str, password: str) | bool | Login by username and password | +| relogin() | bool | Relogin with clean cookies (required cl.username/cl.password) | +| login_by_sessionid(sessionid: str) | bool | Login by sessionid from Instagram site | +| get_settings() | dict | Return settings dict (more details below) | +| set_proxy(dsn: str) | dict | Support socks and http/https proxy | +| cookie_dict | dict | Return cookies | +| user_id | int | Return your user_id (after login) | +| device | dict | Return device dict which we pass to Instagram | +| set_device(device: dict) | bool | Change device settings | +| set_user_agent(user_agent: str = "") | bool | Change User-Agent header | +| base_headers | dict | Base headers for Instagram | +| account_info() | Account | Get private info for your account (e.g. email, phone_number) | +| account_edit(**data) | Account | Change profile data (e.g. external_url, phone_number, username, first_name (full_name), biography, email) | +| account_change_picture(path: path) | UserShort | Change Profile picture | Example: @@ -163,7 +167,7 @@ Viewing and editing publications (medias) | media_delete(media_pk: int) | bool | Delete media | | media_edit(media_pk: int, caption: str, title: str, usertags: List[Usertag], location: Location) | dict | Change caption for media | | media_user(media_pk: int) | User | Get user info for media | -| media_oembed(url: str) | ShortMedia | Return short media info by media URL | +| media_oembed(url: str) | MediaOembed | Return short media info by media URL | | media_comment(media_id: str, message: str) | bool | Write message to media | | media_comments(media_id: str) | List\[Comment] | Get all comments | @@ -208,8 +212,8 @@ View a list of a user's medias, following and followers | Method | Return | Description | | -------------------------------------------------- | ------------------- | ------------------------------------------------ | | user_medias(user_id: int, amount: int = 20) | List\[Media] | Get list of medias by user_id | -| user_followers(user_id: int) | Dict\[int: User] | Get dict of followers users | -| user_following(user_id: int) | Dict\[int: User] | Get dict of following users | +| user_followers(user_id: int) | Dict\[int, User] | Get dict of followers users | +| user_following(user_id: int) | Dict\[int, User] | Get dict of following users | | user_info(user_id: int) | User | Get user info | | user_info_by_username(username: str) | User | Get user info by username | | user_follow(user_id: int) | bool | Follow user | diff --git a/instagrapi/account.py b/instagrapi/account.py index bd16a2b5..033b9438 100644 --- a/instagrapi/account.py +++ b/instagrapi/account.py @@ -1,8 +1,10 @@ import requests - +from pathlib import Path from json.decoder import JSONDecodeError from .exceptions import ClientLoginRequired, ClientError +from .extractors import extract_account, extract_user_short +from .types import Account, UserShort from .utils import gen_csrftoken @@ -22,7 +24,7 @@ def reset_password(self, username): "Accept": "*/*", "Accept-Encoding": "gzip,deflate", "Accept-Language": "en-US", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15" }, proxies=self.public.proxies ) @@ -32,3 +34,34 @@ def reset_password(self, username): if "/login/" in response.url: raise ClientLoginRequired(e, response=response) raise ClientError(e, response=response) + + def account_info(self) -> Account: + result = self.private_request('accounts/current_user/?edit=true') + return extract_account(result['user']) + + def account_edit(self, **data) -> Account: + """Edit your profile (authorized account) + """ + fields = ("external_url", "phone_number", "username", "first_name", "biography", "email") + data = {key: val for key, val in data.items() if key in fields} + if 'email' not in data and 'phone_number' not in data: + # Instagram Error: You need an email or confirmed phone number. + user_data = self.account_info().dict() + user_data['first_name'] = user_data.pop('full_name') # instagram :) + user_data = {field: user_data[field] for field in fields} + data = dict(user_data, **data) + result = self.private_request( + "accounts/edit_profile/", + self.with_default_data(data) + ) + return extract_account(result["user"]) + + def account_change_picture(self, path: Path) -> UserShort: + """Change photo for your profile (authorized account) + """ + upload_id, _, _ = self.photo_rupload(Path(path)) + result = self.private_request( + "accounts/change_profile_picture/", + self.with_default_data({'use_fbuploader': True, 'upload_id': upload_id}) + ) + return extract_user_short(result["user"]) diff --git a/instagrapi/extractors.py b/instagrapi/extractors.py index 3b610210..52e484bc 100644 --- a/instagrapi/extractors.py +++ b/instagrapi/extractors.py @@ -3,7 +3,7 @@ from .types import ( Media, Resource, User, UserShort, Usertag, Location, Collection, Comment, MediaOembed, - DirectThread, DirectMessage + DirectThread, DirectMessage, Account ) @@ -217,3 +217,7 @@ def extract_direct_message(data): if 'media_share' in data: data['media_share'] = extract_media_v1(data['media_share']) return DirectMessage(**data) + + +def extract_account(data): + return Account(**data) diff --git a/instagrapi/types.py b/instagrapi/types.py index bcaae8f8..5da04c71 100644 --- a/instagrapi/types.py +++ b/instagrapi/types.py @@ -25,6 +25,22 @@ class User(BaseModel): is_business: bool +class Account(BaseModel): + pk: int + username: str + full_name: str + is_private: bool + profile_pic_url: HttpUrl + is_verified: bool + biography: Optional[str] = '' + external_url: Optional[HttpUrl] + is_business: bool + birthday: Optional[str] + phone_number: Optional[str] + gender: Optional[int] + email: Optional[str] + + class UserShort(BaseModel): pk: int username: str diff --git a/instagrapi/user.py b/instagrapi/user.py index 5e20eeb2..911a7b10 100644 --- a/instagrapi/user.py +++ b/instagrapi/user.py @@ -177,7 +177,7 @@ def user_following_v1(self, user_id: int, amount: int = 0) -> list: users = users[:amount] return users - def user_following(self, user_id: int, use_cache: bool = True, amount: int = 0) -> Dict[int: User]: + def user_following(self, user_id: int, use_cache: bool = True, amount: int = 0) -> Dict[int, User]: """Return dict {user_id: user} of following users """ user_id = int(user_id) @@ -213,7 +213,7 @@ def user_followers_v1(self, user_id: int, amount: int = 0) -> list: break return users - def user_followers(self, user_id: int, use_cache: bool = True, amount: int = 0) -> Dict[int: User]: + def user_followers(self, user_id: int, use_cache: bool = True, amount: int = 0) -> Dict[int, User]: """Return dict {user_id: user} of followers users """ user_id = int(user_id) diff --git a/tests.py b/tests.py index bc1187ec..ea02d23d 100644 --- a/tests.py +++ b/tests.py @@ -10,7 +10,7 @@ from instagrapi import Client from instagrapi.types import ( User, UserShort, Media, MediaOembed, Comment, Collection, - DirectThread, DirectMessage, Usertag, Location + DirectThread, DirectMessage, Usertag, Location, Account ) @@ -644,5 +644,38 @@ def test_direct_thread(self): self.assertEqual(ping.thread_id, pong.thread_id) +class ClientAccountTestCase(ClientPrivateTestCase): + + def test_account_edit(self): + # current + one = self.api.user_info(self.api.user_id) + self.assertIsInstance(one, User) + # change + url = 'https://trotiq.com/' + two = self.api.account_edit(external_url=url) + self.assertIsInstance(two, Account) + self.assertEqual(str(two.external_url), url) + # return back + three = self.api.account_edit(external_url=one.external_url) + self.assertIsInstance(three, Account) + self.assertEqual(one.external_url, three.external_url) + + def test_account_change_picture(self): + # current + one = self.api.user_info(self.api.user_id) + self.assertIsInstance(one, User) + dhbastards = self.api.user_info_by_username('dhbastards') + # change + two = self.api.account_change_picture( + self.api.photo_download_by_url(dhbastards.profile_pic_url) + ) + self.assertIsInstance(two, UserShort) + # return back + three = self.api.account_change_picture( + self.api.photo_download_by_url(one.profile_pic_url) + ) + self.assertIsInstance(three, UserShort) + + if __name__ == '__main__': unittest.main()