Skip to content

Commit

Permalink
Merge pull request #8 from niuware/development
Browse files Browse the repository at this point in the history
Add activity, follow and unfollow methods to 'Profile' API
  • Loading branch information
niuware authored Nov 18, 2019
2 parents 3834428 + 1a5b427 commit f65773e
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 42 deletions.
95 changes: 57 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ More to come

|Method|Details|
|---|---|
|login(user: `string`, password: `string`, two_factor_code: `string` = None)|Login to an Instagram account. If your account is 2FA protected provide the 2FA code as in the [provided example](https://github.com/niuware/instpector/blob/master/examples/two_factor_auth.py).|
|logout()|Logouts from an Instagram account|
|session()|Returns the current session used by `instpector`|
|login(user: `str`, password: `str`, two_factor_code: `str` = None) -> `bool`|Login to an Instagram account. If your account is 2FA protected provide the 2FA code as in the [provided example](https://github.com/niuware/instpector/blob/master/examples/two_factor_auth.py).|
|logout()|Logouts from an Instagram account.|
|session() -> `Session`|Returns the current session used by `instpector`.|

`EndpointFactory`

|Method|Details|
|---|---|
|create(endpoint_name: `string`, instpector_instance: `Instpector`)|Creates and returns an endpoint instance based on the provided name. Available endpoint names are: `"followers"`, `"following"`, `"profile"`, `"timeline"`, `"comments"` `"story_reel"` and `"story"`|
|create(endpoint_name: `str`, instpector_instance: `Instpector`)|Creates and returns an endpoint instance based on the provided name. Available endpoint names are: `"followers"`, `"following"`, `"profile"`, `"timeline"`, `"comments"` `"story_reel"` and `"story"`.|

## Endpoints

Expand All @@ -84,55 +84,58 @@ Gets the profile of any public or friend user account.

|Method|Details|
|---|---|
|of_user(username: `string`)|Returns a `TProfile` instance for the provided username.|
|of_user(username: `str`) -> `TProfile`|Returns a `TProfile` instance for the provided username.|
|follow(user: `TProfile` \| `str`) -> `bool`|Follows a user. You can provide a `TProfile` instance or an Instagram's user Id.|
|unfollow(user: `TProfile` \| `str`) -> `bool`|Unfollows a user. You can provide a `TProfile` instance or an Instagram's user Id.|
|activity() -> `TActivity`|Yields a list of `TActivity` items for the current logged in account.|

### Followers

Endpoint for accessing the follower list of any public or friend user account.

|Method|Details|
|---|---|
|of_user(user_id: `string`)|Returns a generator of `TUser` instances with all followers. Note the method receives a user id and not a username. To get the user id use the `Profile` endpoint.|
|of_user(user_id: `str`) -> `TUser`|Yields a list of `TUser` instances with all followers. Note the method receives a user id and not a username. To get the user id use the `Profile` endpoint.|

### Following

Endpoint for accessing the followees list of any public or friend user account.

|Method|Details|
|---|---|
|of_user(user_id: `string`)|Returns a generator of `TUser` instances with all followees. Note the method receives a user id and not a username. To get the user id use the `Profile` endpoint.|
|of_user(user_id: `str`) -> `TUser` |Yields a list of `TUser` instances with all followees. Note the method receives a user id and not a username. To get the user id use the `Profile` endpoint.|

### Timeline

Endpoint for accessing the timeline of any public or friend account.

|Method|Details|
|---|---|
|of_user(user_id: `string`)|Returns a generator of `TTimelinePost` instances with all timeline posts. Note the method receives a user id and not a username. To get the user id use the `Profile` endpoint.|
|of_user(user_id: `str`) -> `TTimelinePost`|Yields a list of `TTimelinePost` instances with all timeline posts. Note the method receives a user id and not a username. To get the user id use the `Profile` endpoint.|
|download(timeline_post: `TTimelinePost`, only_image: `bool` = False, low_quality: `bool` = False)|Downloads and save the available resources (image and video) for the provided `TTimelinePost`. The file name convention is `ownerid_resourceid.extension` and saved in the execution directory. If `low_quality` is `True` the resource will be the downloaded with the lowest size available (only for image). If `only_image` is `True` a video file resource won't be downloaded.|
|like(timeline_post: `TTimelinePost`)|Likes a timeline post.|
|unlike(timeline_post: `TTimelinePost`)|Unlikes a timeline post.|
|like(timeline_post: `TTimelinePost` \| `TActivityPost`) -> `bool`|Likes a post.|
|unlike(timeline_post: `TTimelinePost` \| `TActivityPost`) -> `bool`|Unlikes a post.|

### Comments

Endponint for accessing comments and threaded comments of any public or friends post or comment.

|Method|Details|
|---|---|
|of_post(timeline_post: `TTimelinePost`)|Returns a generator of `TComment` instances with all post comments.|
|of_comment(comment: `TComment`)|Returns a generator of `TComment` instances with all threaded comments of a comment.|
|like(comment: `TComment`)|Likes a comment.|
|unlike(comment: `TComment`)|Unlikes a comment.|
|add(timeline_post: `TTimelinePost`, text: `string`, parent_comment: `TComment` = None) -> `TComment` \| `None`|Adds a new comment to a post. You can reply to a comment if `parent_comment` argument is provided. An instance of the created comment is return if succeeded otherwise `None`.|
|remove(timeline_post: `TTimelinePost`, comment: `TComment`)|Removes a comment from a post. Only comments authored by the current logged in account can be removed.|
|of_post(timeline_post: `TTimelinePost` \| `TActivityPost`) -> `TComment`|Yields a list of `TComment` instances with all post comments.|
|of_comment(comment: `TComment`) -> `TComment`|Yields a list of `TComment` instances with all threaded comments of a comment.|
|like(comment: `TComment`) -> `bool`|Likes a comment.|
|unlike(comment: `TComment`) -> `bool`|Unlikes a comment.|
|add(timeline_post: `TTimelinePost` \| `TActivityPost`, text: `str`, parent_comment: `TComment` = None) -> `TComment` \| `None`|Adds a new comment to a post. You can reply to a comment if `parent_comment` argument is provided. An instance of the created comment is return if succeeded otherwise `None`.|
|remove(timeline_post: `TTimelinePost` \| `TActivityPost`, comment: `TComment`) -> `bool`|Removes a comment from a post. Only comments authored by the current logged in account can be removed.|

### StoryReel

Endpoint for accessing the story reel (stories) of any public or friend user account.

|Method|Details|
|---|---|
|of_user(user_id: `string`)|Returns a generator of `TStoryReelItem` instances with all stories. Note the method receives a user id and not a username. To get a user id use the `Profile` endpoint.|
|of_user(user_id: `str`) -> `TStoryReelItem`|Yields a list of `TStoryReelItem` instances with all stories. Note the method receives a user id and not a username. To get a user id use the `Profile` endpoint.|
|download(story_item: `TStoryReelItem`, only_image: `bool` = False, low_quality: `bool` = False)|Downloads and save the available resources (image and video) for the provided `TStoryReelItem`. The file name convention is `ownerid_resourceid.extension` and saved in the execution directory. If `low_quality` is `True` the resource will be the downloaded with the lowest size available. If `only_image` is `True` a video file resource won't be downloaded.|

### Story
Expand All @@ -141,50 +144,50 @@ Endpoint for accessing the story details of a story reel item. This endpoint is

|Method|Details|
|---|---|
|viewers_for(story_id: `string`)|Returns a generator of `TStoryViewer` instances with all viewers of the provided story id.|
|viewers_for(story_id: `str`) -> `TStoryViewer`|Yields a list of `TStoryViewer` instances with all viewers of the provided story id.|

## Types

### TUser

|Field|Type|Details|
|---|---|---|
|id|`string`|The Instagram Id of the user|
|username|`string`|The user's name|
|full_name|`string`|The full name of the user|
|id|`str`|The Instagram Id of the user|
|username|`str`|The user's name|
|full_name|`str`|The full name of the user|
|is_private|`bool`|A flag to show if the user account is private|

### TProfile

|Field|Type|Details|
|---|---|---|
|id|`string`|The Instagram Id of the user|
|username|`string`|The user's name|
|biography|`string`|The biography of the user|
|id|`str`|The Instagram Id of the user|
|username|`str`|The user's name|
|biography|`str`|The biography of the user|
|is_private|`bool`|A flag to show if the user account is private|
|followers_count|`integer`|The follower count of the user|
|following_count|`integer`|The following count of the user|

### TTimelinePost
|Field|Type|Details|
|---|---|---|
|id|`string`|The Instagram Id of the post|
|shortcode|`string`|The Instagram shortcode Id of the post|
|owner|`string`|The post author's Instagram Id|
|id|`str`|The Instagram Id of the post|
|shortcode|`str`|The Instagram shortcode Id of the post|
|owner|`str`|The post author's Instagram Id|
|timestamp|`integer`|The created timestamp of the post|
|caption|`string`|The caption of the post|
|caption|`str`|The caption of the post|
|is_video|`bool`|A flag to know if the post is a video|
|like_count|`integer`|The like count of the post|
|comment_count|`integer`|The comment count of the post|
|display_resources|`list`|A list of image URL strings associated with the post|
|video_url|`string`|The video URL (if available) associated with the post|
|video_url|`str`|The video URL (if available) associated with the post|

### TComment
|Field|Type|Details|
|---|---|---|
|id|`string`|The Instagram Id of the comment|
|text|`string`|The text of the comment|
|username|`string`|The author's username|
|id|`str`|The Instagram Id of the comment|
|text|`str`|The text of the comment|
|username|`str`|The author's username|
|timestamp|`integer`|The timestamp of the comment|
|viewer_has_liked|`bool`|A flag to know if the viewer liked the comment|
|liked_count|`integer`|The like count of the comment|
Expand All @@ -193,21 +196,37 @@ Endpoint for accessing the story details of a story reel item. This endpoint is
### TStoryReelItem
|Field|Type|Details|
|---|---|---|
|id|`string`|The Instagram Id of the story|
|owner|`string`|The story author's Instagram Id|
|id|`str`|The Instagram Id of the story|
|owner|`str`|The story author's Instagram Id|
|timestamp|`integer`|The created timestamp of the story|
|expire_at|`integer`|The expiration timestamp of the story|
|audience|`string`|The type of audience of the story. If public the value is `MediaAudience.DEFAULT`, if private the value is `MediaAudience.BESTIES`|
|audience|`str`|The type of audience of the story. If public the value is `MediaAudience.DEFAULT`, if private the value is `MediaAudience.BESTIES`|
|is_video|`bool`|A flag to know if the story is a video|
|view_count|`integer`|The view count of the story. The count is only available for stories posted by the currently logged in user. Other accounts will have a count equal to `0`.|
|view_count|`integer`|The view count of the story. The count is only available for stories posted by the currently logged in user. Other accounts will have a count equal to `0`|
|display_resources|`list`|A list of image URL strings associated with the story|
|video_resources|`list`|A list of video URL strings associated with the story|

### TStoryViewer
|Field|Type|Details|
|---|---|---|
|id|`string`|The Instagram Id of the story viewer|
|username|`string`|The user name of the viewer|
|id|`str`|The Instagram Id of the story viewer|
|username|`str`|The user name of the viewer|

### TActivity
|Field|Type|Details|
|---|---|---|
|id|`str`|The Instagram Id of the activity|
|timestamp|`integer`|The timestamp of the activity|
|username|`str`|The user name linked to the activity|
|activity_type|`str`|The activity type. Either `NEW_LIKE` or `NEW_FOLLOW`|
|liked_post|`TActivityPost` \| `None`|If the activity type is `NEW_LIKE`, an `TActivityPost` instance is returned|

### TActivityPost
|Field|Type|Details|
|---|---|---|
|id|`str`|The Instagram Id of the post|
|shortcode|`str`|The Instagram shortcode Id of the post|
|thumbnail_resources|`list`|A list of thumbnails URL strings associated with the post|

# Development dependencies

Expand Down
31 changes: 31 additions & 0 deletions examples/follow_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from sys import argv
from context import Instpector, endpoints

def get_profile(**options):

instpector = Instpector()
if not instpector.login(user=options.get("user"), password=options.get("password")):
return

profile = endpoints.factory.create("profile", instpector)

print(profile.follow(options.get("target_username")))
### Unfollow
# profile.unfollow(options.get("target_username"))

instpector.logout()

if __name__ == '__main__':
if len(argv) < 6:
print((
"Missing arguments: "
"--user {user} "
"--password {password} "
"--target_username {username}"
))
exit(1)
get_profile(
user=argv[2],
password=argv[4],
target_username=argv[6]
)
6 changes: 6 additions & 0 deletions instpector/apis/instagram/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@
TComment = namedtuple("TComment", (
"id text timestamp username viewer_has_liked liked_count, thread_count"
))

TActivity = namedtuple("TActivity", "id timestamp username activity_type liked_post")

TActivityPost = namedtuple("TActivityPost", (
"id shortcode thumbnail_resources"
))
30 changes: 27 additions & 3 deletions instpector/apis/instagram/parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .definitions import TUser, TPageInfo, TProfile, TTimelinePost, \
TStoryReelItem, TStoryViewer, TComment
TStoryReelItem, TStoryViewer, TComment, TActivity, TActivityPost
from ..exceptions import NoDataException


Expand Down Expand Up @@ -79,9 +79,9 @@ def timeline(data):
yield post

@staticmethod
def _get_edges(data, endpoint, d_path=None):
def _get_edges(data, endpoint, d_path=None, r_path='data'):
data_path = d_path or "user"
data_root = data.get("data") or {}
data_root = data.get(r_path) or {}
root = data_root.get(data_path) or {}
endpoint_root = root.get(endpoint) or {}
edges = endpoint_root.get("edges")
Expand Down Expand Up @@ -157,3 +157,27 @@ def threaded_comments(data):
thread_count=None
)
yield comment

@staticmethod
def activity(data):
graphql = data.get("graphql") or {}
for edge in Parser._get_edges(graphql, "edge_web_activity_feed", "activity_feed", "user"):
node = edge.get("node") or {}
liked_media = node.get("media") or {}
liked_post = None
user = node.get("user") or {}
if liked_media:
liked_post = TActivityPost(
id=liked_media.get("id"),
thumbnail_resources=list(map(lambda res: res.get("src"),
liked_media.get("thumbnail_resources", []))),
shortcode=liked_media.get("shortcode")
)
activity = TActivity(
id=node.get("id", ""),
username=user.get("username"),
timestamp=node.get("timestamp"),
liked_post=liked_post,
activity_type="NEW_LIKE" if liked_post else "NEW_FOLLOW"
)
yield activity
24 changes: 24 additions & 0 deletions instpector/apis/instagram/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,27 @@ def of_user(self, username):
except ParseDataException:
print(f"Invalid data for username {username}")
return None

def activity(self):
data = super().get("/accounts/activity/?__a=1&include_reel=true")
if data:
try:
return Parser.activity(data)
except ParseDataException:
pass
return None

def _toggle_follow(self, user, action):
tuser = user
if isinstance(user, str):
tuser = self.of_user(user)
user_id = getattr(tuser, "id", "")
if user_id:
return super().quick_post(f"/web/friendships/{user_id}/{action}/")
return False

def follow(self, user):
return self._toggle_follow(user, "follow")

def unfollow(self, user):
return self._toggle_follow(user, "unfollow")
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="instpector",
version="0.2.5",
version="0.2.6",
description="A simple Instagram's web API library",
author="Erik Lopez",
long_description=README,
Expand Down

0 comments on commit f65773e

Please sign in to comment.