Skip to content

Commit

Permalink
Merge pull request #2 from niuware/development
Browse files Browse the repository at this point in the history
Add 'StoryReel' endpoint download resource feature
  • Loading branch information
niuware authored Oct 7, 2019
2 parents 8254c3a + 7737095 commit ae2d1fd
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 8 deletions.
129 changes: 126 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A simple Instagram's web API library written in Python. No selenium or webdriver required.

Login with two-factor authentication enabled is supported.
Supports login with two-factor authentication enabled.

# Installation

Expand All @@ -22,12 +22,15 @@ instpector.login("my_username", "my_password")

# Get the profile of any user, for example 'some_username'
profile = endpoints.factory.create("profile", instpector)

insta_profile = profile.of_user("some_username")

print(insta_profile)
# id, followers_count, following_count, is_private, ...

# Iterate all followers of 'some_username'
followers = endpoints.factory.create("followers", instpector)

for follower in followers.of_user(insta_profile.id):
print(follower)
# id, username, full_name, ...
Expand All @@ -36,6 +39,10 @@ for follower in followers.of_user(insta_profile.id):
instpector.logout()
```

## Examples

Check out more examples [here](https://github.com/niuware/instpector/tree/master/examples).

## Using 2FA
For login in using two-factor authentication, generate your 2fa key on Instagram's app and provide the code when logging in with `instpector`. The following example uses `pytop` to demonstrate the usage:

Expand All @@ -50,8 +57,6 @@ totp = TOTP("my_2fa_key") # Input without spaces
instpector.login("my_username", "my_password", totp.now())
```

Check more in the `examples` directory.

# Available endpoints

- Followers
Expand All @@ -63,6 +68,124 @@ Check more in the `examples` directory.

More to come

# API

## Classes

### Instpector

|Method|Details|
|---|---|
|login(user, password, two_factor_code=None)|Login to an Instagram account. If your account is 2FA protected check 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`|

### EndpointFactory

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

## Endpoints

### Profile

Gets the profile of any public or friend user account.

|Method|Details|
|---|---|
|of_user(username)|Returns a `TProfile` instance of the provided username.|

### Followers

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

|Method|Details|
|---|---|
|of_user(user_id)|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.|

### Following

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

|Method|Details|
|---|---|
|of_user(user_id)|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.|

### Timeline

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

|Method|Details|
|---|---|
|of_user(user_id)|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.|

### StoryReel

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

|Method|Details|
|---|---|
|of_user(user_id)|Returns a generator of `TStoryReelItem` instances with all stories. Note the method receives a user id and not a username. To get the user id use the `Profile` endpoint.|
|download(story_item, low_quality=False)|Downloads and save the available resources (image and video) for the provided `TStoryReelItem`. The files are named using the story's Instagram Id and saved in the execution directory. If `low_quality` is `True` the resource will be the downloaded with the least size available.|

### Story

Endpoint for accessing the story details of a story reel item. This endpoint is only available for stories posted by the current logged in user.

|Method|Details|
|---|---|
|viewers_for(story_id)|Returns a generator of `TStoryViewer` instances with all viewers for 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|
|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|
|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 user|
|timestamp|integer|The timestamp of the post|
|is_video|bool|A flag to know if the story is a video|
|like_count|integer|The like count of the post|
|comment_count|integer|The comment count of the post|

### TStoryReelItem
|Field|Type|Details|
|---|---|---|
|id|string|The Instagram Id of the story|
|timestamp|integer|The 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`|
|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`.|
|display_resources|list|A list of images URLs associated with the story|
|video_resources|list|A list of video URLs 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|

# Development dependencies

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

def download_stories(**options):

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

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

target_profile = profile.of_user(options.get("target_username"))

for story_item in story_reel.of_user(target_profile.id):
story_reel.download(story_item)

instpector.logout()

if __name__ == '__main__':
if len(argv) < 6:
print((
"Missing arguments: "
"--user {user} "
"--password {password} "
"--target_username {username}"
))
exit(1)
download_stories(
user=argv[2],
password=argv[4],
target_username=argv[6]
)
14 changes: 13 additions & 1 deletion instpector/apis/http_request.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import Enum
from urllib.parse import urlencode
from requests import RequestException
from requests import RequestException, HTTPError

class HttpRequestMode(Enum):
JSON = 1
Expand Down Expand Up @@ -63,6 +63,18 @@ def post(self, url_path, data, **options):
print(f"POST RequestException: {req_exception}")
return None

def download_file(self, url, filename):
with self._session.get(url, stream=True) as req:
try:
req.raise_for_status()
except HTTPError:
print(f"Failed to download file at {url}")
return
with open(filename, 'wb') as downloaded_file:
for chunk in req.iter_content(chunk_size=8192):
if chunk:
downloaded_file.write(chunk)

@classmethod
def _get_headers(cls, mode, headers):
request_headers = {}
Expand Down
4 changes: 3 additions & 1 deletion instpector/apis/instagram/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

TTimelinePost = namedtuple("TTimelinePost", "id timestamp is_video like_count comment_count")

TStoryReelItem = namedtuple("TStoryReelItem", "id timestamp expire_at audience is_video view_count")
TStoryReelItem = namedtuple("TStoryReelItem", (
"id timestamp expire_at audience is_video view_count display_resources video_resources"
))

TStoryViewer = namedtuple("TStoryViewer", "id username")
4 changes: 3 additions & 1 deletion instpector/apis/instagram/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ def story_reel(data):
expire_at=item["expiring_at_timestamp"],
is_video=item["is_video"],
view_count=item["edge_story_media_viewers"]["count"],
audience=item["audience"]
audience=item["audience"],
display_resources=item["display_resources"],
video_resources=item["video_resources"]
)
yield post
except (KeyError, TypeError):
Expand Down
13 changes: 13 additions & 0 deletions instpector/apis/instagram/story_reel.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,16 @@ def of_user(self, user_id):
except ParseDataException:
print(f"Invalid story reel data for user id {user_id}")
return None

def download(self, story_item, low_quality=False):
self._download_resources(story_item, "display_resources", "jpg", low_quality)
self._download_resources(story_item, "video_resources", "mp4", low_quality)

def _download_resources(self, story_item, name, extension, low_quality):
try:
resources = getattr(story_item, name)
except AttributeError:
return
if resources:
quality = (len(resources) - 1) if not low_quality else 0
super().download_file(resources[quality].get("src"), f"{story_item.id}.{extension}")
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

setup(
name="instpector",
version="0.2.0",
version="0.2.1",
description="A simple Instagram's web API library",
author="Erik Lopez",
long_description=README,
long_description_content_type="text/markdown",
keywords="instagram web-api instagram-client",
url="https://github.com/niuware/instpector",
download_url="https://github.com/niuware/instpector/archive/0.1.6.tar.gz",
download_url="https://github.com/niuware/instpector/archive/0.2.1.tar.gz",
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
Expand Down

0 comments on commit ae2d1fd

Please sign in to comment.