diff --git a/README.md b/README.md index 22e7a4ed..d99a1356 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![](https://img.shields.io/badge/version-1.0-green)](https://github.com/Datalux/Osintgram/releases/tag/1.0) +[![](https://img.shields.io/badge/version-1.1-green)](https://github.com/Datalux/Osintgram/releases/tag/1.1) [![](https://img.shields.io/badge/license-GPLv3-blue)](https://img.shields.io/badge/license-GPLv3-blue) [![](https://img.shields.io/badge/language-Python3-red)](https://img.shields.io/badge/language-Python3-red) [![](https://img.shields.io/badge/Telegram-Channel-blue.svg)](https://t.me/osintgram) @@ -17,6 +17,8 @@ Osintgram offers an interactive shell to perform analysis on Instagram account o - followings Get users followed by target - fwersemail Get email of target followers - fwingsemail Get email of users followed by target +- fwersnumber Get phone number of target followers +- fwingsnumber Get phone number of users followed by target - hashtags Get hashtags used by target - info Get target info - likes Get total likes of target's posts @@ -31,7 +33,7 @@ Osintgram offers an interactive shell to perform analysis on Instagram account o ``` You can find detailed commands usage [here](doc/COMMANDS.md). -[**Latest version**](https://github.com/Datalux/Osintgram/releases/tag/1.0.1) | +[**Latest version**](https://github.com/Datalux/Osintgram/releases/tag/1.1) | [CHANGELOG](doc/CHANGELOG.md) ## Tools diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index dfbc4164..3453bbe9 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [1.1](https://github.com/Datalux/Osintgram/releases/tag/1.1) +**Enhancements** +- Improved command parser (#86) +- Improved errors handling (8bd1abc) +- Add new line when input command is empty (f5211eb) +- Added new commands to catch phone number of users (#111) +- Added support for Windows (#100) + + +**Bug fixes** +- Fix commands output limit bug (#87) +- Fix setting target with "." in username (9082990) +- Readline installing error (#94 ) + + ## [1.0.1](https://github.com/Datalux/Osintgram/releases/tag/1.0.1) **Bug fixes** - Set itself as target by param @@ -86,7 +101,7 @@ **Bug fixes** -- added a check if target has a private profile to avoid tool crash (#10) +- added a check if the target has a private profile to avoid tool crash (#10) - fixed `tagged` bug (#5) ## [0.3](https://github.com/Datalux/Osintgram/releases/tag/0.3) diff --git a/doc/COMMANDS.md b/doc/COMMANDS.md index 37336530..622ba738 100644 --- a/doc/COMMANDS.md +++ b/doc/COMMANDS.md @@ -52,6 +52,12 @@ Return a list of emails of target followers ### fwingsemail Return a list of emails of user followed by target +### fwersnumber +Return a list of phone number of target followers + +### fwingsnumber +Return a list of phone number of user followed by target + ### hashtags Return a list with all hashtag used by target in his photos diff --git a/main.py b/main.py index 5889cb60..a3977e9c 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- from src.Osintgram import Osintgram @@ -6,12 +6,14 @@ from src import printcolors as pc import sys import signal -import readline -commands = ["quit", "exit", "list", "help", "addrs", "captions", "comments", "followers", - "followings", "fwersemail", "fwingsemail", "hashtags", "info", "likes", - "mediatype", "photodes", "photos", "propic", "stories", "tagged", "target", - "wcommented", "wtagged"] +is_windows = False + +try: + import gnureadline +except: + is_windows = True + import pyreadline def printlogo(): @@ -22,7 +24,7 @@ def printlogo(): pc.printout("\_______ /____ >__|___| /__| \___ /|__| (____ /__|_| /\n", pc.YELLOW) pc.printout(" \/ \/ \/ /_____/ \/ \/ \n", pc.YELLOW) print('\n') - pc.printout("Version 1.0.1 - Developed by Giuseppe Criscione\n\n", pc.YELLOW) + pc.printout("Version 1.1 - Developed by Giuseppe Criscione\n\n", pc.YELLOW) pc.printout("Type 'list' to show all allowed commands\n") pc.printout("Type 'FILE=y' to save results to files like '_.txt (deafult is disabled)'\n") pc.printout("Type 'FILE=n' to disable saving to files'\n") @@ -50,6 +52,10 @@ def cmdlist(): print("Get email of target followers") pc.printout("fwingsemail\t") print("Get email of users followed by target") + pc.printout("fwersnumber\t") + print("Get phone number of target followers") + pc.printout("fwingsnumber\t") + print("Get phone number of users followed by target") pc.printout("hashtags\t") print("Get hashtags used by target") pc.printout("info\t\t") @@ -88,10 +94,18 @@ def completer(text, state): else: return None +def _quit(): + pc.printout("Goodbye!\n", pc.RED) + sys.exit(0) + signal.signal(signal.SIGINT, signal_handler) -readline.parse_and_bind("tab: complete") -readline.set_completer(completer) +if is_windows: + pyreadline.Readline().parse_and_bind("tab: complete") + pyreadline.Readline().set_completer(completer) +else: + gnureadline.parse_and_bind("tab: complete") + gnureadline.set_completer(completer) printlogo() @@ -106,52 +120,47 @@ def completer(text, state): api = Osintgram(args.id, args.file, args.json) + +commands = { + 'list': cmdlist, + 'help': cmdlist, + 'quit': _quit, + 'exit': _quit, + 'addrs': api.get_addrs, + 'captions': api.get_captions, + 'comments': api.get_total_comments, + 'followers': api.get_followers, + 'followings': api.get_followings, + 'fwersemail': api.get_fwersemail, + 'fwingsemail': api.get_fwingsemail, + 'fwersnumber': api.get_fwersnumber, + 'fwingsnumber': api.get_fwingsnumber, + 'hashtags': api.get_hashtags, + 'info': api.get_user_info, + 'likes': api.get_total_likes, + 'mediatype': api.get_media_type, + 'photodes': api.get_photo_description, + 'photos': api.get_user_photo, + 'propic': api.get_user_propic, + 'stories': api.get_user_stories, + 'tagged': api.get_people_tagged_by_user, + 'target': api.change_target, + 'wcommented': api.get_people_who_commented, + 'wtagged': api.get_people_who_tagged +} + +signal.signal(signal.SIGINT, signal_handler) +gnureadline.parse_and_bind("tab: complete") +gnureadline.set_completer(completer) + while True: pc.printout("Run a command: ", pc.YELLOW) cmd = input() - if cmd == "quit" or cmd == "exit": - pc.printout("Goodbye!\n", pc.RED) - sys.exit(0) - elif cmd == "list" or cmd == "help": - cmdlist() - elif cmd == "addrs": - api.get_addrs() - elif cmd == "captions": - api.get_captions() - elif cmd == "comments": - api.get_total_comments() - elif cmd == "followers": - api.get_followers() - elif cmd == "followings": - api.get_followings() - elif cmd == 'fwersemail': - api.get_fwersemail() - elif cmd == 'fwingsemail': - api.get_fwingsemail() - elif cmd == "hashtags": - api.get_hashtags() - elif cmd == "info": - api.get_user_info() - elif cmd == "likes": - api.get_total_likes() - elif cmd == "mediatype": - api.get_media_type() - elif cmd == "photodes": - api.get_photo_description() - elif cmd == "photos": - api.get_user_photo() - elif cmd == "propic": - api.get_user_propic() - elif cmd == "stories": - api.get_user_stories() - elif cmd == "tagged": - api.get_people_tagged_by_user() - elif cmd == "target": - api.change_target() - elif cmd == "wcommented": - api.get_people_who_commented() - elif cmd == "wtagged": - api.get_people_who_tagged() + + _cmd = commands.get(cmd) + + if _cmd: + _cmd() elif cmd == "FILE=y": api.set_write_file(True) elif cmd == "FILE=n": @@ -160,5 +169,7 @@ def completer(text, state): api.set_json_dump(True) elif cmd == "JSON=n": api.set_json_dump(False) + elif cmd == "": + print("") else: pc.printout("Unknown command\n", pc.RED) diff --git a/requirements.txt b/requirements.txt index e8e646b3..94e172be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ requests-toolbelt==0.9.1 geopy>=2.0.0 prettytable==0.7.2 instagram-private-api==1.6.0 -readline>=6.2.4 +gnureadline>=8.0.0; platform_system != "Windows" +pyreadline==2.1; platform_system == "Windows" diff --git a/src/Osintgram.py b/src/Osintgram.py index 2cb430a2..af886a18 100644 --- a/src/Osintgram.py +++ b/src/Osintgram.py @@ -9,7 +9,7 @@ from geopy.geocoders import Nominatim from instagram_private_api import Client as AppClient -from instagram_private_api import ClientCookieExpiredError, ClientLoginRequiredError, ClientError +from instagram_private_api import ClientCookieExpiredError, ClientLoginRequiredError, ClientError, ClientThrottledError from prettytable import PrettyTable @@ -278,12 +278,26 @@ def get_followers(self): pc.printout("Searching for target followers...\n") + _followers = [] followers = [] + rank_token = AppClient.generate_uuid() data = self.api.user_followers(str(self.target_id), rank_token=rank_token) - for user in data['users']: + _followers.extend(data.get('users', [])) + + next_max_id = data.get('next_max_id') + while next_max_id: + sys.stdout.write("\rCatched %i followers" % len(_followers)) + sys.stdout.flush() + results = self.api.user_followers(str(self.target_id), rank_token=rank_token, max_id=next_max_id) + _followers.extend(results.get('users', [])) + next_max_id = results.get('next_max_id') + + print("\n") + + for user in _followers: u = { 'id': user['pk'], 'username': user['username'], @@ -330,12 +344,25 @@ def get_followings(self): pc.printout("Searching for target followings...\n") + _followings = [] followings = [] rank_token = AppClient.generate_uuid() data = self.api.user_following(str(self.target_id), rank_token=rank_token) - for user in data['users']: + _followings.extend(data.get('users', [])) + + next_max_id = data.get('next_max_id') + while next_max_id: + sys.stdout.write("\rCatched %i followings" % len(_followings)) + sys.stdout.flush() + results = self.api.user_following(str(self.target_id), rank_token=rank_token, max_id=next_max_id) + _followings.extend(results.get('users', [])) + next_max_id = results.get('next_max_id') + + print("\n") + + for user in _followings: u = { 'id': user['pk'], 'username': user['username'], @@ -443,7 +470,7 @@ def get_hashtags(self): def get_user_info(self): try: - endpoint = 'users/{user_id!s}/full_detail_info/'.format(**{'user_id': self.target}) + endpoint = 'users/{user_id!s}/full_detail_info/'.format(**{'user_id': self.target_id}) content = self.api._call_api(endpoint) data = content['user_detail']['user'] @@ -516,6 +543,7 @@ def get_user_info(self): json.dump(user, f) except ClientError as e: + print(e) pc.printout("Oops... " + str(self.target) + " non exist, please enter a valid username.", pc.RED) pc.printout("\n") exit(2) @@ -846,7 +874,7 @@ def get_user_photo(self): def get_user_propic(self): try: - endpoint = 'users/{user_id!s}/full_detail_info/'.format(**{'user_id': self.target}) + endpoint = 'users/{user_id!s}/full_detail_info/'.format(**{'user_id': self.target_id}) content = self.api._call_api(endpoint) data = content['user_detail']['user'] @@ -867,8 +895,9 @@ def get_user_propic(self): pc.printout("Sorry! No results found :-(\n", pc.RED) except ClientError as e: - print(e) - print("An error occured... exit") + error = json.loads(e.error_response) + print(error['message']) + print(error['error_title']) exit(2) def get_user_stories(self): @@ -975,25 +1004,27 @@ def get_people_tagged_by_user(self): def get_user(self, username): try: - endpoint = 'users/{user_id!s}/full_detail_info/'.format(**{'user_id': self.target}) - content = self.api._call_api(endpoint) - + content = self.api.username_info(username) if self.writeFile: file_name = "output/" + self.target + "_user_id.txt" file = open(file_name, "w") - file.write(str(content['user_detail']['user']['pk'])) + file.write(str(content['user']['pk'])) file.close() user = dict() - user['id'] = content['user_detail']['user']['pk'] - user['is_private'] = content['user_detail']['user']['is_private'] + user['id'] = content['user']['pk'] + user['is_private'] = content['user']['is_private'] return user except ClientError as e: - pc.printout("Oops... " + str(self.target) + " non exist, please enter a valid username.", pc.RED) - pc.printout("\n") - exit(2) - + error = json.loads(e.error_response) + if 'message' in error: + print(error['message']) + if 'error_title' in error: + print(error['error_title']) + if 'challenge' in error: + print("Please follow this link to complete the challenge: " + error['challenge']['url']) + sys.exit(2) def set_write_file(self, flag): @@ -1099,28 +1130,53 @@ def get_fwersemail(self): if self.check_private_profile(): return - pc.printout("Searching for emails of target followers... this can take a few minutes\n") - followers = [] + + try: - rank_token = AppClient.generate_uuid() - data = self.api.user_followers(str(self.target_id), rank_token=rank_token) + pc.printout("Searching for emails of target followers... this can take a few minutes\n") - for user in data['users']: - u = { - 'id': user['pk'], - 'username': user['username'], - 'full_name': user['full_name'] - } - followers.append(u) + rank_token = AppClient.generate_uuid() + data = self.api.user_followers(str(self.target_id), rank_token=rank_token) - results = [] + for user in data.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followers.append(u) + + next_max_id = data.get('next_max_id') + while next_max_id: + sys.stdout.write("\rCatched %i followers email" % len(followers)) + sys.stdout.flush() + results = self.api.user_followers(str(self.target_id), rank_token=rank_token, max_id=next_max_id) + + for user in results.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followers.append(u) + + next_max_id = results.get('next_max_id') + + print("\n") - for follow in followers: - user = self.api.user_info(str(follow['id'])) - if 'public_email' in user['user']: - follow['email'] = user['user']['public_email'] - results.append(follow) + results = [] + + for follow in followers: + user = self.api.user_info(str(follow['id'])) + if 'public_email' in user['user'] and user['user']['public_email']: + follow['email'] = user['user']['public_email'] + results.append(follow) + + except ClientThrottledError as e: + pc.printout("\nError: Instagram blocked the requests. Please wait a few minutes before you try again.", pc.RED) + pc.printout("\n") + return if len(results) > 0: @@ -1155,31 +1211,56 @@ def get_fwingsemail(self): if self.check_private_profile(): return - pc.printout("Searching for emails of users followed by target... this can take a few minutes\n") - followings = [] - rank_token = AppClient.generate_uuid() - data = self.api.user_following(str(self.target_id), rank_token=rank_token) + try: - for user in data['users']: - u = { - 'id': user['pk'], - 'username': user['username'], - 'full_name': user['full_name'] - } - followings.append(u) + pc.printout("Searching for emails of users followed by target... this can take a few minutes\n") - results = [] + rank_token = AppClient.generate_uuid() + data = self.api.user_following(str(self.target_id), rank_token=rank_token) - for follow in followings: - user = self.api.user_info(str(follow['id'])) - if 'public_email' in user['user']: - follow['email'] = user['user']['public_email'] - results.append(follow) + for user in data.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followings.append(u) + + next_max_id = data.get('next_max_id') + + while next_max_id: + results = self.api.user_following(str(self.target_id), rank_token=rank_token, max_id=next_max_id) + + for user in results.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followings.append(u) - if len(results) > 0: + next_max_id = results.get('next_max_id') + + results = [] + for follow in followings: + sys.stdout.write("\rCatched %i followings email" % len(results)) + sys.stdout.flush() + user = self.api.user_info(str(follow['id'])) + if 'public_email' in user['user'] and user['user']['public_email']: + follow['email'] = user['user']['public_email'] + results.append(follow) + + except ClientThrottledError as e: + pc.printout("\nError: Instagram blocked the requests. Please wait a few minutes before you try again.", pc.RED) + pc.printout("\n") + return + + print("\n") + + if len(results) > 0: t = PrettyTable(['ID', 'Username', 'Full Name', 'Email']) t.align["ID"] = "l" t.align["Username"] = "l" @@ -1206,3 +1287,227 @@ def get_fwingsemail(self): print(t) else: pc.printout("Sorry! No results found :-(\n", pc.RED) + + def get_fwingsnumber(self): + if self.check_private_profile(): + return + + results = [] + + try: + + pc.printout("Searching for phone numbers of users followed by target... this can take a few minutes\n") + + followings = [] + + rank_token = AppClient.generate_uuid() + data = self.api.user_following(str(self.target_id), rank_token=rank_token) + + for user in data.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followings.append(u) + + next_max_id = data.get('next_max_id') + + while next_max_id: + results = self.api.user_following(str(self.target_id), rank_token=rank_token, max_id=next_max_id) + + for user in results.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followings.append(u) + + next_max_id = results.get('next_max_id') + + + for follow in followings: + sys.stdout.write("\rCatched %i followings phone numbers" % len(results)) + sys.stdout.flush() + user = self.api.user_info(str(follow['id'])) + if 'contact_phone_number' in user['user'] and user['user']['contact_phone_number']: + follow['contact_phone_number'] = user['user']['contact_phone_number'] + results.append(follow) + + except ClientThrottledError as e: + pc.printout("\nError: Instagram blocked the requests. Please wait a few minutes before you try again.", pc.RED) + pc.printout("\n") + return + + print("\n") + + if len(results) > 0: + t = PrettyTable(['ID', 'Username', 'Full Name', 'Phone']) + t.align["ID"] = "l" + t.align["Username"] = "l" + t.align["Full Name"] = "l" + t.align["Phone number"] = "l" + + json_data = {} + + for node in results: + t.add_row([str(node['id']), node['username'], node['full_name'], node['contact_phone_number']]) + + if self.writeFile: + file_name = "output/" + self.target + "_fwingsnumber.txt" + file = open(file_name, "w") + file.write(str(t)) + file.close() + + if self.jsonDump: + json_data['followings_phone_numbers'] = results + json_file_name = "output/" + self.target + "_fwingsnumber.json" + with open(json_file_name, 'w') as f: + json.dump(json_data, f) + + print(t) + else: + pc.printout("Sorry! No results found :-(\n", pc.RED) + + def get_fwersnumber(self): + if self.check_private_profile(): + return + + followings = [] + + try: + + pc.printout("Searching for phone numbers of users followers... this can take a few minutes\n") + + + rank_token = AppClient.generate_uuid() + data = self.api.user_following(str(self.target_id), rank_token=rank_token) + + for user in data.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followings.append(u) + + next_max_id = data.get('next_max_id') + + while next_max_id: + results = self.api.user_following(str(self.target_id), rank_token=rank_token, max_id=next_max_id) + + for user in results.get('users', []): + u = { + 'id': user['pk'], + 'username': user['username'], + 'full_name': user['full_name'] + } + followings.append(u) + + next_max_id = results.get('next_max_id') + + results = [] + + for follow in followings: + sys.stdout.write("\rCatched %i followers phone numbers" % len(results)) + sys.stdout.flush() + user = self.api.user_info(str(follow['id'])) + if 'contact_phone_number' in user['user'] and user['user']['contact_phone_number']: + follow['contact_phone_number'] = user['user']['contact_phone_number'] + results.append(follow) + + except ClientThrottledError as e: + pc.printout("\nError: Instagram blocked the requests. Please wait a few minutes before you try again.", pc.RED) + pc.printout("\n") + return + + print("\n") + + if len(results) > 0: + t = PrettyTable(['ID', 'Username', 'Full Name', 'Phone']) + t.align["ID"] = "l" + t.align["Username"] = "l" + t.align["Full Name"] = "l" + t.align["Phone number"] = "l" + + json_data = {} + + for node in results: + t.add_row([str(node['id']), node['username'], node['full_name'], node['contact_phone_number']]) + + if self.writeFile: + file_name = "output/" + self.target + "_fwersnumber.txt" + file = open(file_name, "w") + file.write(str(t)) + file.close() + + if self.jsonDump: + json_data['followings_phone_numbers'] = results + json_file_name = "output/" + self.target + "_fwerssnumber.json" + with open(json_file_name, 'w') as f: + json.dump(json_data, f) + + print(t) + else: + pc.printout("Sorry! No results found :-(\n", pc.RED) + + def get_comments(self): + if self.check_private_profile(): + return + + pc.printout("Searching for users who commented...\n") + + data = self.__get_feed__() + users = [] + + for post in data: + comments = self.__get_comments__(post['id']) + for comment in comments: + print(comment['text']) + + # if not any(u['id'] == comment['user']['pk'] for u in users): + # user = { + # 'id': comment['user']['pk'], + # 'username': comment['user']['username'], + # 'full_name': comment['user']['full_name'], + # 'counter': 1 + # } + # users.append(user) + # else: + # for user in users: + # if user['id'] == comment['user']['pk']: + # user['counter'] += 1 + # break + + if len(users) > 0: + ssort = sorted(users, key=lambda value: value['counter'], reverse=True) + + json_data = {} + + t = PrettyTable() + + t.field_names = ['Comments', 'ID', 'Username', 'Full Name'] + t.align["Comments"] = "l" + t.align["ID"] = "l" + t.align["Username"] = "l" + t.align["Full Name"] = "l" + + for u in ssort: + t.add_row([str(u['counter']), u['id'], u['username'], u['full_name']]) + + print(t) + + if self.writeFile: + file_name = "output/" + self.target + "_users_who_commented.txt" + file = open(file_name, "w") + file.write(str(t)) + file.close() + + if self.jsonDump: + json_data['users_who_commented'] = ssort + json_file_name = "output/" + self.target + "_users_who_commented.json" + with open(json_file_name, 'w') as f: + json.dump(json_data, f) + else: + pc.printout("Sorry! No results found :-(\n", pc.RED)