├── .gitignore ├── README.md ├── credentials.json ├── lib ├── InstagramAPI │ ├── ImageUtils.py │ ├── InstagramAPI.py │ ├── __init__.py │ └── exceptions.py ├── LinkedinAPI │ ├── __init__.py │ ├── client.py │ ├── cookie_repository.py │ ├── linkedin.py │ ├── settings.py │ └── utils │ │ ├── __init__.py │ │ └── helpers.py ├── PwnDB │ ├── PwnDB.py │ └── __init__.py ├── TwitterAPI │ ├── TwitterAPI.py │ └── __init__.py └── __init__.py ├── modules ├── __init__.py ├── banner.py ├── colors.py ├── instagram.py ├── linkedin.py ├── main.py └── twitter.py ├── requirements.txt └── socialosint.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vsocde/ 2 | env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # socialosint 2 | A python osint tool for getting emails, from a target, published in social networks like Instagram, Linkedin and Twitter for finding the possible credential leaks in PwnDB 3 | 4 | [![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)](https://forthebadge.com) 5 | 6 | # Installation 7 | ``` 8 | git clone https://github.com/krishpranav/socialosint 9 | cd socialosint 10 | python3 -m pip install -r requirements.txt 11 | python3 socialosint.py 12 | ``` 13 | 14 | # Usage 15 | 16 | - you need to give your credentials here 17 | 18 | ``` 19 | only for instagram & linkedin you need to give 20 | ``` 21 | 22 | ``` 23 | { 24 | "instagram":{ 25 | "username":"username", 26 | "password":"password" 27 | }, 28 | "linkedin":{ 29 | "email":"email", 30 | "password":"password" 31 | } 32 | } 33 | ``` 34 | 35 | # Examples 36 | 37 | # Instagram example: 38 | ``` 39 | python3 socialosint.py --credentials credentials.json --instagram --info somename 40 | python3 socialosint.py --credentials credentials.json --instagram --location 832578276 41 | python3 socialosint.py --credentials credentials.json --instagram --hashtag-ig someHashtag --pwndb 42 | python3 socialosint.py --credentials credentials.json --instagram --target-ig username --pwndb 43 | python3 socialosint.py --credentials credentials.json --instagram --target-ig username --followers-ig --followings-ig --pwndb 44 | ``` 45 | 46 | # Linkedin example: 47 | ``` 48 | python3 socialosint.py --credentials credentials.json --linkedin --search-companies "My Target" 49 | python3 socialosint.py --credentials credentials.json --linkedin --search-companies "My Target" --employees --pwndb 50 | python3 socialosint.py --credentials credentials.json --linkedin --company 123456789 --employees --pwndb 51 | python3 socialosint.py --credentials credentials.json --linkedin --company 123456789 --employees --add-contacts 52 | python3 socialosint.py --credentials credentials.json --linkedin --user-contacts user-id --pwndb 53 | python3 socialosint.py --credentials credentials.json --linkedin --user-contacts user-id --add-contacts 54 | ``` 55 | 56 | # Twitter example: 57 | ``` 58 | python3 socialosint.py --credentials credentials.json --twitter --hashtag-tw someHashtag --pwndb --limit 200 59 | python3 socialosint.py --credentials credentials.json --twitter --target-tw username --all-tw --pwndb 60 | python3 socialosint.py --credentials credentials.json --twitter --target-tw username --all-tw --followers-tw --followings-tw --pwndb 61 | ``` 62 | 63 | # Multiple Target: 64 | ``` 65 | python3 socialosint.py --credentials credentials.json --instagram --target-ig username --followers-ig --followings-ig --linkedin --company 123456789 --employees --twitter --target-tw username --all-tw --pwndb --output results.txt 66 | 67 | python3 socialosint.py --credentials credentials.json --instagram --target-ig username --linkedin --target-in username --twitter --target-tw username --all-tw --pwndb 68 | ``` 69 | 70 | - Disclainer: 71 | ``` 72 | Use this tool for legal purpose. Author will not be responsible for any damage!. 73 | ``` -------------------------------------------------------------------------------- /credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "instagram":{ 3 | "username":"username", 4 | "password":"password" 5 | }, 6 | "linkedin":{ 7 | "email":"email", 8 | "password":"password" 9 | } 10 | } -------------------------------------------------------------------------------- /lib/InstagramAPI/ImageUtils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # imports 5 | import struct 6 | import imghdr 7 | 8 | def getImageSize(fname): 9 | with open(fname, 'rb') as fhandle: 10 | head = fhandle.read(24) 11 | if len(head) != 24: 12 | raise RuntimeError("Invalid Header") 13 | if imghdr.what(fname) == 'png': 14 | check = struct.unpack('>i', head[4:8])[0] 15 | if check != 0x0d0a1a0a: 16 | raise RuntimeError("PNG: Invalid check") 17 | width, height = struct.unpack('>ii', head[16:24]) 18 | elif imghdr.what(fname) == 'gif': 19 | width, height = struct.unpack('H', fhandle.read(2))[0] - 2 31 | fhandle.seek(1, 1) 32 | height, width = struct.unpack('>HH', fhandle.read(4)) 33 | else: 34 | raise RuntimeError("Unsupported format") 35 | return width, height -------------------------------------------------------------------------------- /lib/InstagramAPI/InstagramAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import requests 5 | import random 6 | import json 7 | import hashlib 8 | import hmac 9 | import urllib 10 | import uuid 11 | import time 12 | import copy 13 | import math 14 | import sys 15 | from datetime import datetime 16 | import calendar 17 | import os 18 | from requests_toolbelt import MultipartEncoder 19 | 20 | if sys.version_info >= (3, 0): 21 | from urllib.parse import urlencode, quote_plus 22 | 23 | # Turn off InsecureRequestWarning 24 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 25 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 26 | 27 | # try: 28 | # from moviepy.editor import VideoFileClip 29 | # except ImportError: 30 | # print("Fail to import moviepy. Need only for Video upload.") 31 | 32 | 33 | # The urllib library was split into other modules from Python 2 to Python 3 34 | if sys.version_info.major == 3: 35 | import urllib.parse 36 | try: 37 | from ImageUtils import getImageSize 38 | except: 39 | # Issue 159, python3 import fix 40 | from .ImageUtils import getImageSize 41 | 42 | from .exceptions import SentryBlockException 43 | 44 | 45 | class InstagramAPI: 46 | API_URL = 'https://i.instagram.com/api/v1/' 47 | DEVICE_SETTINTS = {'manufacturer': 'Xiaomi', 48 | 'model': 'HM 1SW', 49 | 'android_version': 18, 50 | 'android_release': '4.3'} 51 | USER_AGENT = 'Instagram 10.26.0 Android ({android_version}/{android_release}; 320dpi; 720x1280; {manufacturer}; {model}; armani; qcom; en_US)'.format(**DEVICE_SETTINTS) 52 | IG_SIG_KEY = '4f8732eb9ba7d1c8e8897a75d6474d4eb3f5279137431b2aafb71fafe2abe178' 53 | EXPERIMENTS = 'ig_promote_reach_objective_fix_universe,ig_android_universe_video_production,ig_search_client_h1_2017_holdout,ig_android_live_follow_from_comments_universe,ig_android_carousel_non_square_creation,ig_android_live_analytics,ig_android_follow_all_dialog_confirmation_copy,ig_android_stories_server_coverframe,ig_android_video_captions_universe,ig_android_offline_location_feed,ig_android_direct_inbox_retry_seen_state,ig_android_ontact_invite_universe,ig_android_live_broadcast_blacklist,ig_android_insta_video_reconnect_viewers,ig_android_ad_async_ads_universe,ig_android_search_clear_layout_universe,ig_android_shopping_reporting,ig_android_stories_surface_universe,ig_android_verified_comments_universe,ig_android_preload_media_ahead_in_current_reel,android_instagram_prefetch_suggestions_universe,ig_android_reel_viewer_fetch_missing_reels_universe,ig_android_direct_search_share_sheet_universe,ig_android_business_promote_tooltip,ig_android_direct_blue_tab,ig_android_async_network_tweak_universe,ig_android_elevate_main_thread_priority_universe,ig_android_stories_gallery_nux,ig_android_instavideo_remove_nux_comments,ig_video_copyright_whitelist,ig_react_native_inline_insights_with_relay,ig_android_direct_thread_message_animation,ig_android_draw_rainbow_client_universe,ig_android_direct_link_style,ig_android_live_heart_enhancements_universe,ig_android_rtc_reshare,ig_android_preload_item_count_in_reel_viewer_buffer,ig_android_users_bootstrap_service,ig_android_auto_retry_post_mode,ig_android_shopping,ig_android_main_feed_seen_state_dont_send_info_on_tail_load,ig_fbns_preload_default,ig_android_gesture_dismiss_reel_viewer,ig_android_tool_tip,ig_android_ad_logger_funnel_logging_universe,ig_android_gallery_grid_column_count_universe,ig_android_business_new_ads_payment_universe,ig_android_direct_links,ig_android_audience_control,ig_android_live_encore_consumption_settings_universe,ig_perf_android_holdout,ig_android_cache_contact_import_list,ig_android_links_receivers,ig_android_ad_impression_backtest,ig_android_list_redesign,ig_android_stories_separate_overlay_creation,ig_android_stop_video_recording_fix_universe,ig_android_render_video_segmentation,ig_android_live_encore_reel_chaining_universe,ig_android_sync_on_background_enhanced_10_25,ig_android_immersive_viewer,ig_android_mqtt_skywalker,ig_fbns_push,ig_android_ad_watchmore_overlay_universe,ig_android_react_native_universe,ig_android_profile_tabs_redesign_universe,ig_android_live_consumption_abr,ig_android_story_viewer_social_context,ig_android_hide_post_in_feed,ig_android_video_loopcount_int,ig_android_enable_main_feed_reel_tray_preloading,ig_android_camera_upsell_dialog,ig_android_ad_watchbrowse_universe,ig_android_internal_research_settings,ig_android_search_people_tag_universe,ig_android_react_native_ota,ig_android_enable_concurrent_request,ig_android_react_native_stories_grid_view,ig_android_business_stories_inline_insights,ig_android_log_mediacodec_info,ig_android_direct_expiring_media_loading_errors,ig_video_use_sve_universe,ig_android_cold_start_feed_request,ig_android_enable_zero_rating,ig_android_reverse_audio,ig_android_branded_content_three_line_ui_universe,ig_android_live_encore_production_universe,ig_stories_music_sticker,ig_android_stories_teach_gallery_location,ig_android_http_stack_experiment_2017,ig_android_stories_device_tilt,ig_android_pending_request_search_bar,ig_android_fb_topsearch_sgp_fork_request,ig_android_seen_state_with_view_info,ig_android_animation_perf_reporter_timeout,ig_android_new_block_flow,ig_android_story_tray_title_play_all_v2,ig_android_direct_address_links,ig_android_stories_archive_universe,ig_android_save_collections_cover_photo,ig_android_live_webrtc_livewith_production,ig_android_sign_video_url,ig_android_stories_video_prefetch_kb,ig_android_stories_create_flow_favorites_tooltip,ig_android_live_stop_broadcast_on_404,ig_android_live_viewer_invite_universe,ig_android_promotion_feedback_channel,ig_android_render_iframe_interval,ig_android_accessibility_logging_universe,ig_android_camera_shortcut_universe,ig_android_use_one_cookie_store_per_user_override,ig_profile_holdout_2017_universe,ig_android_stories_server_brushes,ig_android_ad_media_url_logging_universe,ig_android_shopping_tag_nux_text_universe,ig_android_comments_single_reply_universe,ig_android_stories_video_loading_spinner_improvements,ig_android_collections_cache,ig_android_comment_api_spam_universe,ig_android_facebook_twitter_profile_photos,ig_android_shopping_tag_creation_universe,ig_story_camera_reverse_video_experiment,ig_android_direct_bump_selected_recipients,ig_android_ad_cta_haptic_feedback_universe,ig_android_vertical_share_sheet_experiment,ig_android_family_bridge_share,ig_android_search,ig_android_insta_video_consumption_titles,ig_android_stories_gallery_preview_button,ig_android_fb_auth_education,ig_android_camera_universe,ig_android_me_only_universe,ig_android_instavideo_audio_only_mode,ig_android_user_profile_chaining_icon,ig_android_live_video_reactions_consumption_universe,ig_android_stories_hashtag_text,ig_android_post_live_badge_universe,ig_android_swipe_fragment_container,ig_android_search_users_universe,ig_android_live_save_to_camera_roll_universe,ig_creation_growth_holdout,ig_android_sticker_region_tracking,ig_android_unified_inbox,ig_android_live_new_watch_time,ig_android_offline_main_feed_10_11,ig_import_biz_contact_to_page,ig_android_live_encore_consumption_universe,ig_android_experimental_filters,ig_android_search_client_matching_2,ig_android_react_native_inline_insights_v2,ig_android_business_conversion_value_prop_v2,ig_android_redirect_to_low_latency_universe,ig_android_ad_show_new_awr_universe,ig_family_bridges_holdout_universe,ig_android_background_explore_fetch,ig_android_following_follower_social_context,ig_android_video_keep_screen_on,ig_android_ad_leadgen_relay_modern,ig_android_profile_photo_as_media,ig_android_insta_video_consumption_infra,ig_android_ad_watchlead_universe,ig_android_direct_prefetch_direct_story_json,ig_android_shopping_react_native,ig_android_top_live_profile_pics_universe,ig_android_direct_phone_number_links,ig_android_stories_weblink_creation,ig_android_direct_search_new_thread_universe,ig_android_histogram_reporter,ig_android_direct_on_profile_universe,ig_android_network_cancellation,ig_android_background_reel_fetch,ig_android_react_native_insights,ig_android_insta_video_audio_encoder,ig_android_family_bridge_bookmarks,ig_android_data_usage_network_layer,ig_android_universal_instagram_deep_links,ig_android_dash_for_vod_universe,ig_android_modular_tab_discover_people_redesign,ig_android_mas_sticker_upsell_dialog_universe,ig_android_ad_add_per_event_counter_to_logging_event,ig_android_sticky_header_top_chrome_optimization,ig_android_rtl,ig_android_biz_conversion_page_pre_select,ig_android_promote_from_profile_button,ig_android_live_broadcaster_invite_universe,ig_android_share_spinner,ig_android_text_action,ig_android_own_reel_title_universe,ig_promotions_unit_in_insights_landing_page,ig_android_business_settings_header_univ,ig_android_save_longpress_tooltip,ig_android_constrain_image_size_universe,ig_android_business_new_graphql_endpoint_universe,ig_ranking_following,ig_android_stories_profile_camera_entry_point,ig_android_universe_reel_video_production,ig_android_power_metrics,ig_android_sfplt,ig_android_offline_hashtag_feed,ig_android_live_skin_smooth,ig_android_direct_inbox_search,ig_android_stories_posting_offline_ui,ig_android_sidecar_video_upload_universe,ig_android_promotion_manager_entry_point_universe,ig_android_direct_reply_audience_upgrade,ig_android_swipe_navigation_x_angle_universe,ig_android_offline_mode_holdout,ig_android_live_send_user_location,ig_android_direct_fetch_before_push_notif,ig_android_non_square_first,ig_android_insta_video_drawing,ig_android_swipeablefilters_universe,ig_android_live_notification_control_universe,ig_android_analytics_logger_running_background_universe,ig_android_save_all,ig_android_reel_viewer_data_buffer_size,ig_direct_quality_holdout_universe,ig_android_family_bridge_discover,ig_android_react_native_restart_after_error_universe,ig_android_startup_manager,ig_story_tray_peek_content_universe,ig_android_profile,ig_android_high_res_upload_2,ig_android_http_service_same_thread,ig_android_scroll_to_dismiss_keyboard,ig_android_remove_followers_universe,ig_android_skip_video_render,ig_android_story_timestamps,ig_android_live_viewer_comment_prompt_universe,ig_profile_holdout_universe,ig_android_react_native_insights_grid_view,ig_stories_selfie_sticker,ig_android_stories_reply_composer_redesign,ig_android_streamline_page_creation,ig_explore_netego,ig_android_ig4b_connect_fb_button_universe,ig_android_feed_util_rect_optimization,ig_android_rendering_controls,ig_android_os_version_blocking,ig_android_encoder_width_safe_multiple_16,ig_search_new_bootstrap_holdout_universe,ig_android_snippets_profile_nux,ig_android_e2e_optimization_universe,ig_android_comments_logging_universe,ig_shopping_insights,ig_android_save_collections,ig_android_live_see_fewer_videos_like_this_universe,ig_android_show_new_contact_import_dialog,ig_android_live_view_profile_from_comments_universe,ig_fbns_blocked,ig_formats_and_feedbacks_holdout_universe,ig_android_reduce_view_pager_buffer,ig_android_instavideo_periodic_notif,ig_search_user_auto_complete_cache_sync_ttl,ig_android_marauder_update_frequency,ig_android_suggest_password_reset_on_oneclick_login,ig_android_promotion_entry_from_ads_manager_universe,ig_android_live_special_codec_size_list,ig_android_enable_share_to_messenger,ig_android_background_main_feed_fetch,ig_android_live_video_reactions_creation_universe,ig_android_channels_home,ig_android_sidecar_gallery_universe,ig_android_upload_reliability_universe,ig_migrate_mediav2_universe,ig_android_insta_video_broadcaster_infra_perf,ig_android_business_conversion_social_context,android_ig_fbns_kill_switch,ig_android_live_webrtc_livewith_consumption,ig_android_destroy_swipe_fragment,ig_android_react_native_universe_kill_switch,ig_android_stories_book_universe,ig_android_all_videoplayback_persisting_sound,ig_android_draw_eraser_universe,ig_direct_search_new_bootstrap_holdout_universe,ig_android_cache_layer_bytes_threshold,ig_android_search_hash_tag_and_username_universe,ig_android_business_promotion,ig_android_direct_search_recipients_controller_universe,ig_android_ad_show_full_name_universe,ig_android_anrwatchdog,ig_android_qp_kill_switch,ig_android_2fac,ig_direct_bypass_group_size_limit_universe,ig_android_promote_simplified_flow,ig_android_share_to_whatsapp,ig_android_hide_bottom_nav_bar_on_discover_people,ig_fbns_dump_ids,ig_android_hands_free_before_reverse,ig_android_skywalker_live_event_start_end,ig_android_live_join_comment_ui_change,ig_android_direct_search_story_recipients_universe,ig_android_direct_full_size_gallery_upload,ig_android_ad_browser_gesture_control,ig_channel_server_experiments,ig_android_video_cover_frame_from_original_as_fallback,ig_android_ad_watchinstall_universe,ig_android_ad_viewability_logging_universe,ig_android_new_optic,ig_android_direct_visual_replies,ig_android_stories_search_reel_mentions_universe,ig_android_threaded_comments_universe,ig_android_mark_reel_seen_on_Swipe_forward,ig_internal_ui_for_lazy_loaded_modules_experiment,ig_fbns_shared,ig_android_capture_slowmo_mode,ig_android_live_viewers_list_search_bar,ig_android_video_single_surface,ig_android_offline_reel_feed,ig_android_video_download_logging,ig_android_last_edits,ig_android_exoplayer_4142,ig_android_post_live_viewer_count_privacy_universe,ig_android_activity_feed_click_state,ig_android_snippets_haptic_feedback,ig_android_gl_drawing_marks_after_undo_backing,ig_android_mark_seen_state_on_viewed_impression,ig_android_live_backgrounded_reminder_universe,ig_android_live_hide_viewer_nux_universe,ig_android_live_monotonic_pts,ig_android_search_top_search_surface_universe,ig_android_user_detail_endpoint,ig_android_location_media_count_exp_ig,ig_android_comment_tweaks_universe,ig_android_ad_watchmore_entry_point_universe,ig_android_top_live_notification_universe,ig_android_add_to_last_post,ig_save_insights,ig_android_live_enhanced_end_screen_universe,ig_android_ad_add_counter_to_logging_event,ig_android_blue_token_conversion_universe,ig_android_exoplayer_settings,ig_android_progressive_jpeg,ig_android_offline_story_stickers,ig_android_gqls_typing_indicator,ig_android_chaining_button_tooltip,ig_android_video_prefetch_for_connectivity_type,ig_android_use_exo_cache_for_progressive,ig_android_samsung_app_badging,ig_android_ad_holdout_watchandmore_universe,ig_android_offline_commenting,ig_direct_stories_recipient_picker_button,ig_insights_feedback_channel_universe,ig_android_insta_video_abr_resize,ig_android_insta_video_sound_always_on''' 54 | SIG_KEY_VERSION = '4' 55 | 56 | # username # Instagram username 57 | # password # Instagram password 58 | # debug # Debug 59 | # uuid # UUID 60 | # device_id # Device ID 61 | # username_id # Username ID 62 | # token # _csrftoken 63 | # isLoggedIn # Session status 64 | # rank_token # Rank token 65 | # IGDataPath # Data storage path 66 | 67 | def __init__(self, username, password, debug=False, IGDataPath=None): 68 | m = hashlib.md5() 69 | m.update(username.encode('utf-8') + password.encode('utf-8')) 70 | self.device_id = self.generateDeviceId(m.hexdigest()) 71 | self.setUser(username, password) 72 | self.isLoggedIn = False 73 | self.LastResponse = None 74 | self.s = requests.Session() 75 | 76 | def setUser(self, username, password): 77 | self.username = username 78 | self.password = password 79 | self.uuid = self.generateUUID(True) 80 | 81 | def setProxy(self, proxy=None): 82 | """ 83 | Set proxy for all requests:: 84 | 85 | Proxy format - user:password@ip:port 86 | """ 87 | 88 | if proxy is not None: 89 | print('Set proxy!') 90 | proxies = {'http': proxy, 'https': proxy} 91 | self.s.proxies.update(proxies) 92 | 93 | def login(self, force=False): 94 | if (not self.isLoggedIn or force): 95 | if (self.SendRequest('si/fetch_headers/?challenge_type=signup&guid=' + self.generateUUID(False), None, True)): 96 | 97 | data = {'phone_id': self.generateUUID(True), 98 | '_csrftoken': self.LastResponse.cookies['csrftoken'], 99 | 'username': self.username, 100 | 'guid': self.uuid, 101 | 'device_id': self.device_id, 102 | 'password': self.password, 103 | 'login_attempt_count': '0'} 104 | 105 | if (self.SendRequest('accounts/login/', self.generateSignature(json.dumps(data)), True)): 106 | self.isLoggedIn = True 107 | self.username_id = self.LastJson["logged_in_user"]["pk"] 108 | self.rank_token = "%s_%s" % (self.username_id, self.uuid) 109 | self.token = self.LastResponse.cookies["csrftoken"] 110 | 111 | self.syncFeatures() 112 | self.autoCompleteUserList() 113 | self.timelineFeed() 114 | self.getv2Inbox() 115 | self.getRecentActivity() 116 | #print("Login success!\n") 117 | return True 118 | 119 | def syncFeatures(self): 120 | data = json.dumps({'_uuid': self.uuid, 121 | '_uid': self.username_id, 122 | 'id': self.username_id, 123 | '_csrftoken': self.token, 124 | 'experiments': self.EXPERIMENTS}) 125 | return self.SendRequest('qe/sync/', self.generateSignature(data)) 126 | 127 | def autoCompleteUserList(self): 128 | return self.SendRequest('friendships/autocomplete_user_list/') 129 | 130 | def timelineFeed(self): 131 | return self.SendRequest('feed/timeline/') 132 | 133 | def megaphoneLog(self): 134 | return self.SendRequest('megaphone/log/') 135 | 136 | def expose(self): 137 | data = json.dumps({'_uuid': self.uuid, 138 | '_uid': self.username_id, 139 | 'id': self.username_id, 140 | '_csrftoken': self.token, 141 | 'experiment': 'ig_android_profile_contextual_feed'}) 142 | return self.SendRequest('qe/expose/', self.generateSignature(data)) 143 | 144 | def logout(self): 145 | logout = self.SendRequest('accounts/logout/') 146 | 147 | def uploadPhoto(self, photo, caption=None, upload_id=None, is_sidecar=None): 148 | if upload_id is None: 149 | upload_id = str(int(time.time() * 1000)) 150 | data = {'upload_id': upload_id, 151 | '_uuid': self.uuid, 152 | '_csrftoken': self.token, 153 | 'image_compression': '{"lib_name":"jt","lib_version":"1.3.0","quality":"87"}', 154 | 'photo': ('pending_media_%s.jpg' % upload_id, open(photo, 'rb'), 'application/octet-stream', {'Content-Transfer-Encoding': 'binary'})} 155 | if is_sidecar: 156 | data['is_sidecar'] = '1' 157 | m = MultipartEncoder(data, boundary=self.uuid) 158 | self.s.headers.update({'X-IG-Capabilities': '3Q4=', 159 | 'X-IG-Connection-Type': 'WIFI', 160 | 'Cookie2': '$Version=1', 161 | 'Accept-Language': 'en-US', 162 | 'Accept-Encoding': 'gzip, deflate', 163 | 'Content-type': m.content_type, 164 | 'Connection': 'close', 165 | 'User-Agent': self.USER_AGENT}) 166 | response = self.s.post(self.API_URL + "upload/photo/", data=m.to_string()) 167 | if response.status_code == 200: 168 | if self.configure(upload_id, photo, caption): 169 | self.expose() 170 | return False 171 | 172 | def uploadVideo(self, video, thumbnail, caption=None, upload_id=None, is_sidecar=None): 173 | if upload_id is None: 174 | upload_id = str(int(time.time() * 1000)) 175 | data = {'upload_id': upload_id, 176 | '_csrftoken': self.token, 177 | 'media_type': '2', 178 | '_uuid': self.uuid} 179 | if is_sidecar: 180 | data['is_sidecar'] = '1' 181 | m = MultipartEncoder(data, boundary=self.uuid) 182 | self.s.headers.update({'X-IG-Capabilities': '3Q4=', 183 | 'X-IG-Connection-Type': 'WIFI', 184 | 'Host': 'i.instagram.com', 185 | 'Cookie2': '$Version=1', 186 | 'Accept-Language': 'en-US', 187 | 'Accept-Encoding': 'gzip, deflate', 188 | 'Content-type': m.content_type, 189 | 'Connection': 'keep-alive', 190 | 'User-Agent': self.USER_AGENT}) 191 | response = self.s.post(self.API_URL + "upload/video/", data=m.to_string()) 192 | if response.status_code == 200: 193 | body = json.loads(response.text) 194 | upload_url = body['video_upload_urls'][3]['url'] 195 | upload_job = body['video_upload_urls'][3]['job'] 196 | 197 | videoData = open(video, 'rb').read() 198 | # solve issue #85 TypeError: slice indices must be integers or None or have an __index__ method 199 | request_size = int(math.floor(len(videoData) / 4)) 200 | lastRequestExtra = (len(videoData) - (request_size * 3)) 201 | 202 | headers = copy.deepcopy(self.s.headers) 203 | self.s.headers.update({'X-IG-Capabilities': '3Q4=', 204 | 'X-IG-Connection-Type': 'WIFI', 205 | 'Cookie2': '$Version=1', 206 | 'Accept-Language': 'en-US', 207 | 'Accept-Encoding': 'gzip, deflate', 208 | 'Content-type': 'application/octet-stream', 209 | 'Session-ID': upload_id, 210 | 'Connection': 'keep-alive', 211 | 'Content-Disposition': 'attachment; filename="video.mov"', 212 | 'job': upload_job, 213 | 'Host': 'upload.instagram.com', 214 | 'User-Agent': self.USER_AGENT}) 215 | for i in range(0, 4): 216 | start = i * request_size 217 | if i == 3: 218 | end = i * request_size + lastRequestExtra 219 | else: 220 | end = (i + 1) * request_size 221 | length = lastRequestExtra if i == 3 else request_size 222 | content_range = "bytes {start}-{end}/{lenVideo}".format(start=start, end=(end - 1), 223 | lenVideo=len(videoData)).encode('utf-8') 224 | 225 | self.s.headers.update({'Content-Length': str(end - start), 'Content-Range': content_range, }) 226 | response = self.s.post(upload_url, data=videoData[start:start + length]) 227 | self.s.headers = headers 228 | 229 | if response.status_code == 200: 230 | if self.configureVideo(upload_id, video, thumbnail, caption): 231 | self.expose() 232 | return False 233 | 234 | def uploadAlbum(self, media, caption=None, upload_id=None): 235 | if not media: 236 | raise Exception("List of media to upload can't be empty.") 237 | 238 | if len(media) < 2 or len(media) > 10: 239 | raise Exception('Instagram requires that albums contain 2-10 items. You tried to submit {}.'.format(len(media))) 240 | 241 | # Figure out the media file details for ALL media in the album. 242 | # NOTE: We do this first, since it validates whether the media files are 243 | # valid and lets us avoid wasting time uploading totally invalid albums! 244 | for idx, item in enumerate(media): 245 | if not item.get('file', '') or item.get('tipe', ''): 246 | raise Exception('Media at index "{}" does not have the required "file" and "type" keys.'.format(idx)) 247 | 248 | # $itemInternalMetadata = new InternalMetadata(); 249 | # If usertags are provided, verify that the entries are valid. 250 | if item.get('usertags', []): 251 | self.throwIfInvalidUsertags(item['usertags']) 252 | 253 | # Pre-process media details and throw if not allowed on Instagram. 254 | if item.get('type', '') == 'photo': 255 | # Determine the photo details. 256 | # $itemInternalMetadata->setPhotoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']); 257 | pass 258 | 259 | elif item.get('type', '') == 'video': 260 | # Determine the video details. 261 | # $itemInternalMetadata->setVideoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']); 262 | pass 263 | 264 | else: 265 | raise Exception('Unsupported album media type "{}".'.format(item['type'])) 266 | 267 | itemInternalMetadata = {} 268 | item['internalMetadata'] = itemInternalMetadata 269 | 270 | # Perform all media file uploads. 271 | for idx, item in enumerate(media): 272 | itemInternalMetadata = item['internalMetadata'] 273 | item_upload_id = self.generateUploadId() 274 | if item.get('type', '') == 'photo': 275 | self.uploadPhoto(item['file'], caption=caption, is_sidecar=True, upload_id=item_upload_id) 276 | # $itemInternalMetadata->setPhotoUploadResponse($this->ig->internal->uploadPhotoData(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata)); 277 | 278 | elif item.get('type', '') == 'video': 279 | # Attempt to upload the video data. 280 | self.uploadVideo(item['file'], item['thumbnail'], caption=caption, is_sidecar=True, upload_id=item_upload_id) 281 | # $itemInternalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_TIMELINE_ALBUM, $item['file'], $itemInternalMetadata); 282 | # Attempt to upload the thumbnail, associated with our video's ID. 283 | # $itemInternalMetadata->setPhotoUploadResponse($this->ig->internal->uploadPhotoData(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata)); 284 | pass 285 | item['internalMetadata']['upload_id'] = item_upload_id 286 | 287 | albumInternalMetadata = {} 288 | return self.configureTimelineAlbum(media, albumInternalMetadata, captionText=caption) 289 | 290 | def throwIfInvalidUsertags(self, usertags): 291 | for user_position in usertags: 292 | # Verify this usertag entry, ensuring that the entry is format 293 | # ['position'=>[0.0,1.0],'user_id'=>'123'] and nothing else. 294 | correct = True 295 | if isinstance(user_position, dict): 296 | position = user_position.get('position', None) 297 | user_id = user_position.get('user_id', None) 298 | 299 | if isinstance(position, list) and len(position) == 2: 300 | try: 301 | x = float(position[0]) 302 | y = float(position[1]) 303 | if x < 0.0 or x > 1.0: 304 | correct = False 305 | if y < 0.0 or y > 1.0: 306 | correct = False 307 | except: 308 | correct = False 309 | try: 310 | user_id = long(user_id) 311 | if user_id < 0: 312 | correct = False 313 | except: 314 | correct = False 315 | if not correct: 316 | raise Exception('Invalid user entry in usertags array.') 317 | 318 | def configureTimelineAlbum(self, media, albumInternalMetadata, captionText='', location=None): 319 | endpoint = 'media/configure_sidecar/' 320 | albumUploadId = self.generateUploadId() 321 | 322 | date = datetime.utcnow().isoformat() 323 | childrenMetadata = [] 324 | for item in media: 325 | itemInternalMetadata = item['internalMetadata'] 326 | uploadId = itemInternalMetadata.get('upload_id', self.generateUploadId()) 327 | if item.get('type', '') == 'photo': 328 | # Build this item's configuration. 329 | photoConfig = {'date_time_original': date, 330 | 'scene_type': 1, 331 | 'disable_comments': False, 332 | 'upload_id': uploadId, 333 | 'source_type': 0, 334 | 'scene_capture_type': 'standard', 335 | 'date_time_digitized': date, 336 | 'geotag_enabled': False, 337 | 'camera_position': 'back', 338 | 'edits': {'filter_strength': 1, 339 | 'filter_name': 'IGNormalFilter'} 340 | } 341 | # This usertag per-file EXTERNAL metadata is only supported for PHOTOS! 342 | if item.get('usertags', []): 343 | # NOTE: These usertags were validated in Timeline::uploadAlbum. 344 | photoConfig['usertags'] = json.dumps({'in': item['usertags']}) 345 | 346 | childrenMetadata.append(photoConfig) 347 | if item.get('type', '') == 'video': 348 | # Get all of the INTERNAL per-VIDEO metadata. 349 | videoDetails = itemInternalMetadata.get('video_details', {}) 350 | # Build this item's configuration. 351 | videoConfig = {'length': videoDetails.get('duration', 1.0), 352 | 'date_time_original': date, 353 | 'scene_type': 1, 354 | 'poster_frame_index': 0, 355 | 'trim_type': 0, 356 | 'disable_comments': False, 357 | 'upload_id': uploadId, 358 | 'source_type': 'library', 359 | 'geotag_enabled': False, 360 | 'edits': { 361 | 'length': videoDetails.get('duration', 1.0), 362 | 'cinema': 'unsupported', 363 | 'original_length': videoDetails.get('duration', 1.0), 364 | 'source_type': 'library', 365 | 'start_time': 0, 366 | 'camera_position': 'unknown', 367 | 'trim_type': 0} 368 | } 369 | 370 | childrenMetadata.append(videoConfig) 371 | # Build the request... 372 | data = {'_csrftoken': self.token, 373 | '_uid': self.username_id, 374 | '_uuid': self.uuid, 375 | 'client_sidecar_id': albumUploadId, 376 | 'caption': captionText, 377 | 'children_metadata': childrenMetadata} 378 | self.SendRequest(endpoint, self.generateSignature(json.dumps(data))) 379 | response = self.LastResponse 380 | if response.status_code == 200: 381 | self.LastResponse = response 382 | self.LastJson = json.loads(response.text) 383 | return True 384 | else: 385 | if response.status_code == 429: 386 | print("[-] Please wait a few minutes before you try again.") 387 | #print("Request return " + str(response.status_code) + " error!") 388 | # for debugging 389 | try: 390 | self.LastResponse = response 391 | self.LastJson = json.loads(response.text) 392 | except: 393 | pass 394 | return False 395 | 396 | def direct_message(self, text, recipients): 397 | if type(recipients) != type([]): 398 | recipients = [str(recipients)] 399 | recipient_users = '"",""'.join(str(r) for r in recipients) 400 | endpoint = 'direct_v2/threads/broadcast/text/' 401 | boundary = self.uuid 402 | bodies = [ 403 | { 404 | 'type' : 'form-data', 405 | 'name' : 'recipient_users', 406 | 'data' : '[["{}"]]'.format(recipient_users), 407 | }, 408 | { 409 | 'type' : 'form-data', 410 | 'name' : 'client_context', 411 | 'data' : self.uuid, 412 | }, 413 | { 414 | 'type' : 'form-data', 415 | 'name' : 'thread', 416 | 'data' : '["0"]', 417 | }, 418 | { 419 | 'type' : 'form-data', 420 | 'name' : 'text', 421 | 'data' : text or '', 422 | }, 423 | ] 424 | data = self.buildBody(bodies,boundary) 425 | self.s.headers.update ( 426 | { 427 | 'User-Agent' : self.USER_AGENT, 428 | 'Proxy-Connection' : 'keep-alive', 429 | 'Connection': 'keep-alive', 430 | 'Accept': '*/*', 431 | 'Content-Type': 'multipart/form-data; boundary={}'.format(boundary), 432 | 'Accept-Language': 'en-en', 433 | } 434 | ) 435 | #self.SendRequest(endpoint,post=data) #overwrites 'Content-type' header and boundary is missed 436 | response = self.s.post(self.API_URL + endpoint, data=data) 437 | 438 | if response.status_code == 200: 439 | self.LastResponse = response 440 | self.LastJson = json.loads(response.text) 441 | return True 442 | else: 443 | #print ("Request return " + str(response.status_code) + " error!") 444 | # for debugging 445 | try: 446 | self.LastResponse = response 447 | self.LastJson = json.loads(response.text) 448 | except: 449 | pass 450 | return False 451 | 452 | def direct_share(self, media_id, recipients, text=None): 453 | if not isinstance(position, list): 454 | recipients = [str(recipients)] 455 | recipient_users = '"",""'.join(str(r) for r in recipients) 456 | endpoint = 'direct_v2/threads/broadcast/media_share/?media_type=photo' 457 | boundary = self.uuid 458 | bodies = [ 459 | { 460 | 'type': 'form-data', 461 | 'name': 'media_id', 462 | 'data': media_id, 463 | }, 464 | { 465 | 'type': 'form-data', 466 | 'name': 'recipient_users', 467 | 'data': '[["{}"]]'.format(recipient_users), 468 | }, 469 | { 470 | 'type': 'form-data', 471 | 'name': 'client_context', 472 | 'data': self.uuid, 473 | }, 474 | { 475 | 'type': 'form-data', 476 | 'name': 'thread', 477 | 'data': '["0"]', 478 | }, 479 | { 480 | 'type': 'form-data', 481 | 'name': 'text', 482 | 'data': text or '', 483 | }, 484 | ] 485 | data = self.buildBody(bodies, boundary) 486 | self.s.headers.update({'User-Agent': self.USER_AGENT, 487 | 'Proxy-Connection': 'keep-alive', 488 | 'Connection': 'keep-alive', 489 | 'Accept': '*/*', 490 | 'Content-Type': 'multipart/form-data; boundary={}'.format(boundary), 491 | 'Accept-Language': 'en-en'}) 492 | # self.SendRequest(endpoint,post=data) #overwrites 'Content-type' header and boundary is missed 493 | response = self.s.post(self.API_URL + endpoint, data=data) 494 | 495 | if response.status_code == 200: 496 | self.LastResponse = response 497 | self.LastJson = json.loads(response.text) 498 | return True 499 | else: 500 | #print("Request return " + str(response.status_code) + " error!") 501 | # for debugging 502 | try: 503 | self.LastResponse = response 504 | self.LastJson = json.loads(response.text) 505 | except: 506 | pass 507 | return False 508 | 509 | def configureVideo(self, upload_id, video, thumbnail, caption=''): 510 | clip = VideoFileClip(video) 511 | self.uploadPhoto(photo=thumbnail, caption=caption, upload_id=upload_id) 512 | data = json.dumps({ 513 | 'upload_id': upload_id, 514 | 'source_type': 3, 515 | 'poster_frame_index': 0, 516 | 'length': 0.00, 517 | 'audio_muted': False, 518 | 'filter_type': 0, 519 | 'video_result': 'deprecated', 520 | 'clips': { 521 | 'length': clip.duration, 522 | 'source_type': '3', 523 | 'camera_position': 'back', 524 | }, 525 | 'extra': { 526 | 'source_width': clip.size[0], 527 | 'source_height': clip.size[1], 528 | }, 529 | 'device': self.DEVICE_SETTINTS, 530 | '_csrftoken': self.token, 531 | '_uuid': self.uuid, 532 | '_uid': self.username_id, 533 | 'caption': caption, 534 | }) 535 | return self.SendRequest('media/configure/?video=1', self.generateSignature(data)) 536 | 537 | def configure(self, upload_id, photo, caption=''): 538 | (w, h) = getImageSize(photo) 539 | data = json.dumps({'_csrftoken': self.token, 540 | 'media_folder': 'Instagram', 541 | 'source_type': 4, 542 | '_uid': self.username_id, 543 | '_uuid': self.uuid, 544 | 'caption': caption, 545 | 'upload_id': upload_id, 546 | 'device': self.DEVICE_SETTINTS, 547 | 'edits': { 548 | 'crop_original_size': [w * 1.0, h * 1.0], 549 | 'crop_center': [0.0, 0.0], 550 | 'crop_zoom': 1.0 551 | }, 552 | 'extra': { 553 | 'source_width': w, 554 | 'source_height': h 555 | }}) 556 | return self.SendRequest('media/configure/?', self.generateSignature(data)) 557 | 558 | def editMedia(self, mediaId, captionText=''): 559 | data = json.dumps({'_uuid': self.uuid, 560 | '_uid': self.username_id, 561 | '_csrftoken': self.token, 562 | 'caption_text': captionText}) 563 | return self.SendRequest('media/' + str(mediaId) + '/edit_media/', self.generateSignature(data)) 564 | 565 | def removeSelftag(self, mediaId): 566 | data = json.dumps({'_uuid': self.uuid, 567 | '_uid': self.username_id, 568 | '_csrftoken': self.token}) 569 | return self.SendRequest('media/' + str(mediaId) + '/remove/', self.generateSignature(data)) 570 | 571 | def mediaInfo(self, mediaId): 572 | data = json.dumps({'_uuid': self.uuid, 573 | '_uid': self.username_id, 574 | '_csrftoken': self.token, 575 | 'media_id': mediaId}) 576 | return self.SendRequest('media/' + str(mediaId) + '/info/', self.generateSignature(data)) 577 | 578 | def deleteMedia(self, mediaId, media_type=1): 579 | data = json.dumps({'_uuid': self.uuid, 580 | '_uid': self.username_id, 581 | '_csrftoken': self.token, 582 | 'media_type': media_type, 583 | 'media_id': mediaId}) 584 | return self.SendRequest('media/' + str(mediaId) + '/delete/', self.generateSignature(data)) 585 | 586 | def changePassword(self, newPassword): 587 | data = json.dumps({'_uuid': self.uuid, 588 | '_uid': self.username_id, 589 | '_csrftoken': self.token, 590 | 'old_password': self.password, 591 | 'new_password1': newPassword, 592 | 'new_password2': newPassword}) 593 | return self.SendRequest('accounts/change_password/', self.generateSignature(data)) 594 | 595 | def explore(self): 596 | return self.SendRequest('discover/explore/') 597 | 598 | def comment(self, mediaId, commentText): 599 | data = json.dumps({'_uuid': self.uuid, 600 | '_uid': self.username_id, 601 | '_csrftoken': self.token, 602 | 'comment_text': commentText}) 603 | return self.SendRequest('media/' + str(mediaId) + '/comment/', self.generateSignature(data)) 604 | 605 | def deleteComment(self, mediaId, commentId): 606 | data = json.dumps({'_uuid': self.uuid, 607 | '_uid': self.username_id, 608 | '_csrftoken': self.token}) 609 | return self.SendRequest('media/' + str(mediaId) + '/comment/' + str(commentId) + '/delete/', self.generateSignature(data)) 610 | 611 | def changeProfilePicture(self, photo): 612 | # TODO Instagram.php 705-775 613 | return False 614 | 615 | def removeProfilePicture(self): 616 | data = json.dumps({'_uuid': self.uuid, 617 | '_uid': self.username_id, 618 | '_csrftoken': self.token}) 619 | return self.SendRequest('accounts/remove_profile_picture/', self.generateSignature(data)) 620 | 621 | def setPrivateAccount(self): 622 | data = json.dumps({'_uuid': self.uuid, 623 | '_uid': self.username_id, 624 | '_csrftoken': self.token}) 625 | return self.SendRequest('accounts/set_private/', self.generateSignature(data)) 626 | 627 | def setPublicAccount(self): 628 | data = json.dumps({'_uuid': self.uuid, 629 | '_uid': self.username_id, 630 | '_csrftoken': self.token}) 631 | return self.SendRequest('accounts/set_public/', self.generateSignature(data)) 632 | 633 | def getProfileData(self): 634 | data = json.dumps({'_uuid': self.uuid, 635 | '_uid': self.username_id, 636 | '_csrftoken': self.token}) 637 | return self.SendRequest('accounts/current_user/?edit=true', self.generateSignature(data)) 638 | 639 | def editProfile(self, url, phone, first_name, biography, email, gender): 640 | data = json.dumps({'_uuid': self.uuid, 641 | '_uid': self.username_id, 642 | '_csrftoken': self.token, 643 | 'external_url': url, 644 | 'phone_number': phone, 645 | 'username': self.username, 646 | 'full_name': first_name, 647 | 'biography': biography, 648 | 'email': email, 649 | 'gender': gender}) 650 | return self.SendRequest('accounts/edit_profile/', self.generateSignature(data)) 651 | 652 | def getStory(self, usernameId): 653 | return self.SendRequest('feed/user/' + str(usernameId) + '/reel_media/') 654 | 655 | def getUsernameInfo(self, usernameId): 656 | return self.SendRequest('users/' + str(usernameId) + '/info/') 657 | 658 | def getSelfUsernameInfo(self): 659 | return self.getUsernameInfo(self.username_id) 660 | 661 | def getSelfSavedMedia(self): 662 | return self.SendRequest('feed/saved') 663 | 664 | def getRecentActivity(self): 665 | activity = self.SendRequest('news/inbox/?') 666 | return activity 667 | 668 | def getFollowingRecentActivity(self): 669 | activity = self.SendRequest('news/?') 670 | return activity 671 | 672 | def getv2Inbox(self): 673 | inbox = self.SendRequest('direct_v2/inbox/?') 674 | return inbox 675 | 676 | def getv2Threads(self, thread, cursor=None): 677 | endpoint = 'direct_v2/threads/{0}'.format(thread) 678 | if cursor is not None: 679 | endpoint += '?cursor={0}'.format(cursor) 680 | inbox = self.SendRequest(endpoint) 681 | return inbox 682 | 683 | def getUserTags(self, usernameId): 684 | tags = self.SendRequest('usertags/' + str(usernameId) + '/feed/?rank_token=' + str(self.rank_token) + '&ranked_content=true&') 685 | return tags 686 | 687 | def getSelfUserTags(self): 688 | return self.getUserTags(self.username_id) 689 | 690 | def tagFeed(self, tag): 691 | userFeed = self.SendRequest('feed/tag/' + str(tag) + '/?rank_token=' + str(self.rank_token) + '&ranked_content=true&') 692 | return userFeed 693 | 694 | def getMediaLikers(self, mediaId): 695 | likers = self.SendRequest('media/' + str(mediaId) + '/likers/?') 696 | return likers 697 | 698 | def getGeoMedia(self, usernameId): 699 | locations = self.SendRequest('maps/user/' + str(usernameId) + '/') 700 | return locations 701 | 702 | def getSelfGeoMedia(self): 703 | return self.getGeoMedia(self.username_id) 704 | 705 | def fbUserSearch(self, query): 706 | query = self.SendRequest('fbsearch/topsearch/?context=blended&query=' + str(query) + '&rank_token=' + str(self.rank_token)) 707 | return query 708 | 709 | def searchUsers(self, query): 710 | query = self.SendRequest('users/search/?ig_sig_key_version=' + str(self.SIG_KEY_VERSION) + '&is_typeahead=true&query=' + str(query) + '&rank_token=' + str(self.rank_token)) 711 | return query 712 | 713 | def searchUsername(self, usernameName): 714 | query = self.SendRequest('users/' + str(usernameName) + '/usernameinfo/') 715 | return query 716 | 717 | def syncFromAdressBook(self, contacts): 718 | return self.SendRequest('address_book/link/?include=extra_display_name,thumbnails', "contacts=" + json.dumps(contacts)) 719 | 720 | def searchTags(self, query): 721 | query = self.SendRequest('tags/search/?is_typeahead=true&q=' + str(query) + '&rank_token=' + str(self.rank_token)) 722 | return query 723 | 724 | def getTimeline(self): 725 | query = self.SendRequest('feed/timeline/?rank_token=' + str(self.rank_token) + '&ranked_content=true&') 726 | return query 727 | 728 | def getUserFeed(self, usernameId, maxid='', minTimestamp=None): 729 | query = self.SendRequest('feed/user/%s/?max_id=%s&min_timestamp=%s&rank_token=%s&ranked_content=true' 730 | % (usernameId, maxid, minTimestamp, self.rank_token)) 731 | return query 732 | 733 | def getSelfUserFeed(self, maxid='', minTimestamp=None): 734 | return self.getUserFeed(self.username_id, maxid, minTimestamp) 735 | 736 | def getHashtagFeed(self, hashtagString, maxid=''): 737 | return self.SendRequest('feed/tag/' + hashtagString + '/?max_id=' + str(maxid) + '&rank_token=' + self.rank_token + '&ranked_content=true&') 738 | 739 | def searchLocation(self, query): 740 | locationFeed = self.SendRequest('fbsearch/places/?rank_token=' + str(self.rank_token) + '&query=' + str(query)) 741 | return locationFeed 742 | 743 | def getLocationFeed(self, locationId, maxid=''): 744 | return self.SendRequest('feed/location/' + str(locationId) + '/?max_id=' + maxid + '&rank_token=' + self.rank_token + '&ranked_content=true&') 745 | 746 | def getPopularFeed(self): 747 | popularFeed = self.SendRequest('feed/popular/?people_teaser_supported=1&rank_token=' + str(self.rank_token) + '&ranked_content=true&') 748 | return popularFeed 749 | 750 | def getUserFollowings(self, usernameId, maxid=''): 751 | url = 'friendships/' + str(usernameId) + '/following/?' 752 | query_string = {'ig_sig_key_version': self.SIG_KEY_VERSION, 753 | 'rank_token': self.rank_token} 754 | if maxid: 755 | query_string['max_id'] = maxid 756 | if sys.version_info.major == 3: 757 | url += urllib.parse.urlencode(query_string) 758 | else: 759 | url += urllib.urlencode(query_string) 760 | return self.SendRequest(url) 761 | 762 | def getSelfUsersFollowing(self): 763 | return self.getUserFollowings(self.username_id) 764 | 765 | def getUserFollowers(self, usernameId, maxid=''): 766 | if maxid == '': 767 | return self.SendRequest('friendships/' + str(usernameId) + '/followers/?rank_token=' + self.rank_token) 768 | else: 769 | return self.SendRequest('friendships/' + str(usernameId) + '/followers/?rank_token=' + self.rank_token + '&max_id=' + str(maxid)) 770 | 771 | def getSelfUserFollowers(self): 772 | return self.getUserFollowers(self.username_id) 773 | 774 | def getPendingFollowRequests(self): 775 | return self.SendRequest('friendships/pending?') 776 | 777 | def like(self, mediaId): 778 | data = json.dumps({'_uuid': self.uuid, 779 | '_uid': self.username_id, 780 | '_csrftoken': self.token, 781 | 'media_id': mediaId}) 782 | return self.SendRequest('media/' + str(mediaId) + '/like/', self.generateSignature(data)) 783 | 784 | def unlike(self, mediaId): 785 | data = json.dumps({'_uuid': self.uuid, 786 | '_uid': self.username_id, 787 | '_csrftoken': self.token, 788 | 'media_id': mediaId}) 789 | return self.SendRequest('media/' + str(mediaId) + '/unlike/', self.generateSignature(data)) 790 | 791 | def save(self, mediaId): 792 | data = json.dumps({'_uuid': self.uuid, 793 | '_uid': self.username_id, 794 | '_csrftoken': self.token, 795 | 'media_id': mediaId}) 796 | return self.SendRequest('media/' + str(mediaId) + '/save/', self.generateSignature(data)) 797 | 798 | def unsave(self, mediaId): 799 | data = json.dumps({'_uuid': self.uuid, 800 | '_uid': self.username_id, 801 | '_csrftoken': self.token, 802 | 'media_id': mediaId}) 803 | return self.SendRequest('media/' + str(mediaId) + '/unsave/', self.generateSignature(data)) 804 | 805 | def getMediaComments(self, mediaId, max_id=''): 806 | return self.SendRequest('media/' + mediaId + '/comments/?max_id=' + max_id) 807 | 808 | def setNameAndPhone(self, name='', phone=''): 809 | data = json.dumps({'_uuid': self.uuid, 810 | '_uid': self.username_id, 811 | 'first_name': name, 812 | 'phone_number': phone, 813 | '_csrftoken': self.token}) 814 | return self.SendRequest('accounts/set_phone_and_name/', self.generateSignature(data)) 815 | 816 | def getDirectShare(self): 817 | return self.SendRequest('direct_share/inbox/?') 818 | 819 | def backup(self): 820 | # TODO Instagram.php 1470-1485 821 | return False 822 | 823 | def approve(self, userId): 824 | data = json.dumps({ 825 | '_uuid' : self.uuid, 826 | '_uid' : self.username_id, 827 | 'user_id' : userId, 828 | '_csrftoken' : self.token 829 | }) 830 | return self.SendRequest('friendships/approve/'+ str(userId) + '/', self.generateSignature(data)) 831 | 832 | def ignore(self, userId): 833 | data = json.dumps({ 834 | '_uuid' : self.uuid, 835 | '_uid' : self.username_id, 836 | 'user_id' : userId, 837 | '_csrftoken' : self.token 838 | }) 839 | return self.SendRequest('friendships/ignore/'+ str(userId) + '/', self.generateSignature(data)) 840 | 841 | def follow(self, userId): 842 | data = json.dumps({'_uuid': self.uuid, 843 | '_uid': self.username_id, 844 | 'user_id': userId, 845 | '_csrftoken': self.token}) 846 | return self.SendRequest('friendships/create/' + str(userId) + '/', self.generateSignature(data)) 847 | 848 | def unfollow(self, userId): 849 | data = json.dumps({'_uuid': self.uuid, 850 | '_uid': self.username_id, 851 | 'user_id': userId, 852 | '_csrftoken': self.token}) 853 | return self.SendRequest('friendships/destroy/' + str(userId) + '/', self.generateSignature(data)) 854 | 855 | def block(self, userId): 856 | data = json.dumps({'_uuid': self.uuid, 857 | '_uid': self.username_id, 858 | 'user_id': userId, 859 | '_csrftoken': self.token}) 860 | return self.SendRequest('friendships/block/' + str(userId) + '/', self.generateSignature(data)) 861 | 862 | def unblock(self, userId): 863 | data = json.dumps({'_uuid': self.uuid, 864 | '_uid': self.username_id, 865 | 'user_id': userId, 866 | '_csrftoken': self.token}) 867 | return self.SendRequest('friendships/unblock/' + str(userId) + '/', self.generateSignature(data)) 868 | 869 | def userFriendship(self, userId): 870 | data = json.dumps({'_uuid': self.uuid, 871 | '_uid': self.username_id, 872 | 'user_id': userId, 873 | '_csrftoken': self.token}) 874 | return self.SendRequest('friendships/show/' + str(userId) + '/', self.generateSignature(data)) 875 | 876 | def getLikedMedia(self, maxid=''): 877 | return self.SendRequest('feed/liked/?max_id=' + str(maxid)) 878 | 879 | def generateSignature(self, data, skip_quote=False): 880 | if not skip_quote: 881 | try: 882 | parsedData = urllib.parse.quote(data) 883 | except AttributeError: 884 | parsedData = urllib.quote(data) 885 | else: 886 | parsedData = data 887 | return 'ig_sig_key_version=' + self.SIG_KEY_VERSION + '&signed_body=' + hmac.new(self.IG_SIG_KEY.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest() + '.' + parsedData 888 | 889 | def generateDeviceId(self, seed): 890 | volatile_seed = "12345" 891 | m = hashlib.md5() 892 | m.update(seed.encode('utf-8') + volatile_seed.encode('utf-8')) 893 | return 'android-' + m.hexdigest()[:16] 894 | 895 | def generateUUID(self, type): 896 | generated_uuid = str(uuid.uuid4()) 897 | if (type): 898 | return generated_uuid 899 | else: 900 | return generated_uuid.replace('-', '') 901 | 902 | def generateUploadId(self): 903 | return str(calendar.timegm(datetime.utcnow().utctimetuple())) 904 | 905 | def createBroadcast(self, previewWidth=1080, previewHeight=1920, broadcastMessage=''): 906 | data = json.dumps({'_uuid': self.uuid, 907 | '_uid': self.username_id, 908 | 'preview_height': previewHeight, 909 | 'preview_width': previewWidth, 910 | 'broadcast_message': broadcastMessage, 911 | 'broadcast_type': 'RTMP', 912 | 'internal_only': 0, 913 | '_csrftoken': self.token}) 914 | return self.SendRequest('live/create/', self.generateSignature(data)) 915 | 916 | def startBroadcast(self, broadcastId, sendNotification=False): 917 | data = json.dumps({'_uuid': self.uuid, 918 | '_uid': self.username_id, 919 | 'should_send_notifications': int(sendNotification), 920 | '_csrftoken': self.token}) 921 | return self.SendRequest('live/' + str(broadcastId) + '/start', self.generateSignature(data)) 922 | 923 | def stopBroadcast(self, broadcastId): 924 | data = json.dumps({'_uuid': self.uuid, 925 | '_uid': self.username_id, 926 | '_csrftoken': self.token}) 927 | return self.SendRequest('live/' + str(broadcastId) + '/end_broadcast/', self.generateSignature(data)) 928 | 929 | def addBroadcastToLive(self, broadcastId): 930 | # broadcast has to be ended first! 931 | data = json.dumps({'_uuid': self.uuid, 932 | '_uid': self.username_id, 933 | '_csrftoken': self.token}) 934 | return self.SendRequest('live/' + str(broadcastId) + '/add_to_post_live/', self.generateSignature(data)) 935 | 936 | def buildBody(self, bodies, boundary): 937 | body = u'' 938 | for b in bodies: 939 | body += u'--{boundary}\r\n'.format(boundary=boundary) 940 | body += u'Content-Disposition: {b_type}; name="{b_name}"'.format(b_type=b['type'], b_name=b['name']) 941 | _filename = b.get('filename', None) 942 | _headers = b.get('headers', None) 943 | if _filename: 944 | _filename, ext = os.path.splitext(_filename) 945 | _body += u'; filename="pending_media_{uid}.{ext}"'.format(uid=self.generateUploadId(), ext=ext) 946 | if _headers and isinstance(_headers, list): 947 | for h in _headers: 948 | _body += u'\r\n{header}'.format(header=h) 949 | body += u'\r\n\r\n{data}\r\n'.format(data=b['data']) 950 | body += u'--{boundary}--'.format(boundary=boundary) 951 | return body 952 | 953 | def SendRequest(self, endpoint, post=None, login=False): 954 | verify = False # don't show request warning 955 | 956 | if (not self.isLoggedIn and not login): 957 | raise Exception("Not logged in!\n") 958 | 959 | self.s.headers.update({'Connection': 'close', 960 | 'Accept': '*/*', 961 | 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 962 | 'Cookie2': '$Version=1', 963 | 'Accept-Language': 'en-US', 964 | 'User-Agent': self.USER_AGENT}) 965 | 966 | while True: 967 | try: 968 | if (post is not None): 969 | response = self.s.post(self.API_URL + endpoint, data=post, verify=verify) 970 | else: 971 | response = self.s.get(self.API_URL + endpoint, verify=verify) 972 | break 973 | except Exception as e: 974 | print('Except on SendRequest (wait 60 sec and resend): ' + str(e)) 975 | time.sleep(60) 976 | 977 | if response.status_code == 200: 978 | self.LastResponse = response 979 | self.LastJson = json.loads(response.text) 980 | return True 981 | else: 982 | #print("Request return " + str(response.status_code) + " error!") 983 | # for debugging 984 | try: 985 | self.LastResponse = response 986 | self.LastJson = json.loads(response.text) 987 | #print(self.LastJson) 988 | if 'error_type' in self.LastJson and self.LastJson['error_type'] == 'sentry_block': 989 | raise SentryBlockException(self.LastJson['message']) 990 | except SentryBlockException: 991 | raise 992 | except: 993 | pass 994 | return False 995 | 996 | def getTotalFollowers(self, usernameId): 997 | followers = [] 998 | next_max_id = '' 999 | while 1: 1000 | print("[-] Getting Followers ... ") 1001 | time.sleep(float(0.5)) 1002 | self.getUserFollowers(usernameId, next_max_id) 1003 | temp = self.LastJson 1004 | 1005 | for item in temp["users"]: 1006 | followers.append(item) 1007 | try: 1008 | if temp["big_list"] is False: 1009 | return followers 1010 | except Exception as e: 1011 | print("[-] The user has a private profile and is not your friend :(") 1012 | sys.exit() 1013 | next_max_id = temp["next_max_id"] 1014 | 1015 | def getTotalFollowings(self, usernameId): 1016 | followings = [] 1017 | next_max_id = '' 1018 | while 1: 1019 | print("[-] Getting Followings ... ") 1020 | time.sleep(float(0.5)) 1021 | self.getUserFollowings(usernameId,next_max_id) 1022 | temp = self.LastJson 1023 | 1024 | for item in temp["users"]: 1025 | followings.append(item) 1026 | try: 1027 | if temp["big_list"] is False: 1028 | return followings 1029 | except Exception as e: 1030 | print("[-] The user has a private profile and is not your friend :(") 1031 | sys.exit() 1032 | next_max_id = temp["next_max_id"] 1033 | 1034 | 1035 | def getTotalUserFeed(self, usernameId, minTimestamp=None): 1036 | user_feed = [] 1037 | next_max_id = '' 1038 | while True: 1039 | self.getUserFeed(usernameId, next_max_id, minTimestamp) 1040 | temp = self.LastJson 1041 | for item in temp["items"]: 1042 | user_feed.append(item) 1043 | if temp["more_available"] is False: 1044 | return user_feed 1045 | next_max_id = temp["next_max_id"] 1046 | 1047 | def getTotalSelfUserFeed(self, minTimestamp=None): 1048 | return self.getTotalUserFeed(self.username_id, minTimestamp) 1049 | 1050 | def getTotalSelfFollowers(self): 1051 | return self.getTotalFollowers(self.username_id) 1052 | 1053 | def getTotalSelfFollowings(self): 1054 | return self.getTotalFollowings(self.username_id) 1055 | 1056 | def getTotalLikedMedia(self, scan_rate=1): 1057 | next_id = '' 1058 | liked_items = [] 1059 | for x in range(0, scan_rate): 1060 | temp = self.getLikedMedia(next_id) 1061 | temp = self.LastJson 1062 | try: 1063 | next_id = temp["next_max_id"] 1064 | for item in temp["items"]: 1065 | liked_items.append(item) 1066 | except KeyError as e: 1067 | break 1068 | return liked_items -------------------------------------------------------------------------------- /lib/InstagramAPI/__init__.py: -------------------------------------------------------------------------------- 1 | from .InstagramAPI import * 2 | from .ImageUtils import * 3 | -------------------------------------------------------------------------------- /lib/InstagramAPI/exceptions.py: -------------------------------------------------------------------------------- 1 | class SentryBlockException(Exception): 2 | pass -------------------------------------------------------------------------------- /lib/LinkedinAPI/__init__.py: -------------------------------------------------------------------------------- 1 | from .linkedin import Linkedin 2 | 3 | __title__ = "linkedin_api" 4 | __version__ = "1.0.0" 5 | __description__ = "Python wrapper for linkeding api" 6 | 7 | __author__ = "Tom Quirk" 8 | __email__ = "tomquirkacc@gmail.com" 9 | 10 | __all__ = ["Linkedin"] -------------------------------------------------------------------------------- /lib/LinkedinAPI/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import logging 3 | from .cookie_repository import CookieRepository 4 | 5 | logging.getLogger("requests").setLevel(logging.INFO) 6 | logging.getLogger("requests").setLevel(logging.DEBUG) 7 | logging.getLogger("requests").setLevel(logging.WARNING) 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class ChallengeException(Exception): 13 | pass 14 | 15 | 16 | class UnauthorizedException(Exception): 17 | pass 18 | 19 | 20 | class Client(object): 21 | 22 | API_BASE_URL = "https://www.linkedin.com/voyager/api" 23 | REQUEST_HEADERS = { 24 | "user-agent": " ".join( 25 | [ 26 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5)", 27 | "AppleWebKit/537.36 (KHTML, like Gecko)", 28 | "Chrome/66.0.3359.181 Safari/537.36", 29 | ] 30 | ), 31 | 32 | "accept-language": "en-AU,en-GB;q=0.9,en-US;q=0.8,en;q=0.7", 33 | "x-li-lang": "en_US", 34 | "x-restli-protocol-version": "2.0.0", 35 | 36 | } 37 | 38 | AUTH_BASE_URL = "https://www.linkedin.com" 39 | AUTH_REQUEST_HEADERS = { 40 | "X-Li-User-Agent": "LIAuthLibrary:3.2.4 \ 41 | com.linkedin.LinkedIn:8.8.1 \ 42 | iPhone:8.3", 43 | "User-Agent": "LinkedIn/8.8.1 CFNetwork/711.3.18 Darwin/14.0.0", 44 | "X-User-Language": "en", 45 | "X-User-Locale": "en_US", 46 | "Accept-Language": "en-us", 47 | } 48 | 49 | def __init__(self, *, debug=False, refresh_cookies=False, proxies={}): 50 | self.session = requests.session() 51 | self.session.proxies.update(proxies) 52 | self.session.headers.update(Client.REQUEST_HEADERS) 53 | self.proxies = proxies 54 | self.logger = logger 55 | self._use_cookie_cache = not refresh_cookies 56 | self._cookie_repository = CookieRepository() 57 | 58 | logging.basicConfig(level=logging.DEBUG if debug else logging.INFO) 59 | 60 | def _request_session_cookies(self): 61 | """ 62 | Return a new set of session cookies as given by Linkedin. 63 | """ 64 | self.logger.debug("Requesting new cookies.") 65 | 66 | res = requests.get( 67 | f"{Client.AUTH_BASE_URL}/uas/authenticate", 68 | headers=Client.AUTH_REQUEST_HEADERS, 69 | proxies=self.proxies, 70 | ) 71 | return res.cookies 72 | 73 | def _set_session_cookies(self, cookies): 74 | """ 75 | Set cookies of the current session and save them to a file named as the username. 76 | """ 77 | self.session.cookies = cookies 78 | self.session.headers["csrf-token"] = self.session.cookies["JSESSIONID"].strip( 79 | '"' 80 | ) 81 | 82 | @property 83 | def cookies(self): 84 | return self.session.cookies 85 | 86 | def authenticate(self, username, password): 87 | if self._use_cookie_cache: 88 | self.logger.debug("Attempting to use cached cookies") 89 | cookies = self._cookie_repository.get(username) 90 | if cookies: 91 | self._set_session_cookies(cookies) 92 | return 93 | 94 | return self._do_authentication_request(username, password) 95 | 96 | def _do_authentication_request(self, username, password): 97 | """ 98 | Authenticate with Linkedin. 99 | 100 | Return a session object that is authenticated. 101 | """ 102 | self._set_session_cookies(self._request_session_cookies()) 103 | 104 | payload = { 105 | "session_key": username, 106 | "session_password": password, 107 | "JSESSIONID": self.session.cookies["JSESSIONID"], 108 | } 109 | 110 | res = requests.post( 111 | f"{Client.AUTH_BASE_URL}/uas/authenticate", 112 | data=payload, 113 | cookies=self.session.cookies, 114 | headers=Client.AUTH_REQUEST_HEADERS, 115 | proxies=self.proxies, 116 | ) 117 | 118 | data = res.json() 119 | 120 | if data and data["login_result"] != "PASS": 121 | raise ChallengeException(data["login_result"]) 122 | 123 | if res.status_code == 401: 124 | raise UnauthorizedException() 125 | 126 | if res.status_code != 200: 127 | raise Exception() 128 | 129 | self._set_session_cookies(res.cookies) 130 | self._cookie_repository.save(res.cookies, username) -------------------------------------------------------------------------------- /lib/LinkedinAPI/cookie_repository.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # imports 5 | import os 6 | import pickle 7 | import time 8 | import lib.LinkedinAPI.settings as settings 9 | 10 | 11 | class Error(Exception): 12 | 13 | pass 14 | 15 | 16 | class LinkedinSessionExpired(Error): 17 | pass 18 | 19 | 20 | class CookieRepository(object): 21 | 22 | @staticmethod 23 | def save(cookies, username): 24 | CookieRepository._ensure_cookies_dir() 25 | cookiejar_filepath = CookieRepository._get_cookies_filepath(username) 26 | with open(cookiejar_filepath, "wb") as f: 27 | pickle.dump(cookies, f) 28 | 29 | @staticmethod 30 | def get(username): 31 | cookies = CookieRepository._load_cookies_from_cache(username) 32 | if cookies and not CookieRepository._is_token_still_valid(cookies): 33 | raise LinkedinSessionExpired 34 | 35 | return cookies 36 | 37 | @staticmethod 38 | def _ensure_cookies_dir(): 39 | if not os.path.exists(settings.COOKIE_PATH): 40 | os.makedirs(settings.COOKIE_PATH) 41 | 42 | @staticmethod 43 | def _get_cookies_filepath(username): 44 | return "{}{}.jr".format(settings.COOKIE_PATH, username) 45 | 46 | @staticmethod 47 | def _load_cookies_from_cache(username): 48 | cookiejar_filepath = CookieRepository._get_cookies_filepath(username) 49 | try: 50 | with open(cookiejar_filepath, "rb") as f: 51 | cookies = pickle.load(f) 52 | return cookies 53 | except FileNotFoundError: 54 | return None 55 | 56 | @staticmethod 57 | def _is_token_still_valid(cookiejar): 58 | _now = time.time() 59 | for cookie in cookiejar: 60 | if cookie.name == "JSESSIONID" and cookie.value: 61 | if cookie.expires and cookie.expires > _now: 62 | return True 63 | break 64 | 65 | return False -------------------------------------------------------------------------------- /lib/LinkedinAPI/linkedin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provides linkedin api-related code 3 | """ 4 | import random, json, logging 5 | from time import sleep 6 | from urllib.parse import urlencode 7 | from .utils.helpers import get_id_from_urn 8 | from .client import Client 9 | from modules.colors import colors 10 | logging.getLogger("requests").setLevel(logging.INFO) 11 | logging.getLogger("requests").setLevel(logging.DEBUG) 12 | logging.getLogger("requests").setLevel(logging.WARNING) 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | def default_evade(): 18 | """ 19 | A catch-all method to try and evade suspension from Linkedin. 20 | Currenly, just delays the request by a random (bounded) time 21 | """ 22 | sleep(random.randint(1, 3)) 23 | 24 | 25 | class Linkedin(object): 26 | """ 27 | Class for accessing Linkedin API. 28 | """ 29 | 30 | _MAX_UPDATE_COUNT = 100 31 | _MAX_SEARCH_COUNT = 49 32 | _MAX_REPEATED_REQUESTS = ( 33 | 200 34 | ) 35 | 36 | def __init__( 37 | self, 38 | username, 39 | password, 40 | *, 41 | authenticate=True, 42 | refresh_cookies=False, 43 | debug=False, 44 | proxies={}, 45 | ): 46 | self.client = Client( 47 | refresh_cookies=refresh_cookies, debug=debug, proxies=proxies 48 | ) 49 | logging.basicConfig(level=logging.DEBUG if debug else logging.INFO) 50 | self.logger = logger 51 | self.success = False 52 | try: 53 | if authenticate: 54 | self.client.authenticate(username, password) 55 | self.success = True 56 | except Exception as e: 57 | self.success = False 58 | print(colors.bad + " Linkedin: " + str(e) + colors.end) 59 | 60 | 61 | def _fetch(self, uri, evade=default_evade, **kwargs): 62 | 63 | evade() 64 | 65 | url = f"{self.client.API_BASE_URL}{uri}" 66 | return self.client.session.get(url, **kwargs) 67 | 68 | def _post(self, uri, evade=default_evade, **kwargs): 69 | 70 | evade() 71 | 72 | url = f"{self.client.API_BASE_URL}{uri}" 73 | return self.client.session.post(url, **kwargs) 74 | 75 | def search(self, params, limit=None, results=[]): 76 | """ 77 | Do a search. 78 | """ 79 | count = ( 80 | limit 81 | if limit and limit <= Linkedin._MAX_SEARCH_COUNT 82 | else Linkedin._MAX_SEARCH_COUNT 83 | ) 84 | default_params = { 85 | "count": str(count), 86 | "filters": "List()", 87 | "origin": "GLOBAL_SEARCH_HEADER", 88 | "q": "all", 89 | "start": len(results), 90 | "queryContext": "List(spellCorrectionEnabled->true,relatedSearchesEnabled->true,kcardTypes->PROFILE|COMPANY)", 91 | } 92 | default_params.update(params) 93 | 94 | res = self._fetch( 95 | f"/search/blended?{urlencode(default_params, safe='(),')}", 96 | headers={"accept": "application/vnd.linkedin.normalized+json+2.1"}, 97 | ) 98 | 99 | data = res.json() 100 | 101 | new_elements = [] 102 | for i in range(len(data["data"]["elements"])): 103 | new_elements.extend(data["data"]["elements"][i]["elements"]) 104 | 105 | 106 | results.extend(new_elements) 107 | results = results[ 108 | :limit 109 | ] 110 | if ( 111 | limit is not None 112 | and ( 113 | len(results) >= limit # if our results exceed set limit 114 | or len(results) / count >= Linkedin._MAX_REPEATED_REQUESTS 115 | ) 116 | ) or len(new_elements) == 0: 117 | return results 118 | 119 | self.logger.debug(f"results grew to {len(results)}") 120 | 121 | return self.search(params, results=results, limit=limit) 122 | 123 | def search_people( 124 | self, 125 | keywords=None, 126 | connection_of=None, 127 | network_depth=None, 128 | current_company=None, 129 | past_companies=None, 130 | nonprofit_interests=None, 131 | profile_languages=None, 132 | regions=None, 133 | industries=None, 134 | schools=None, 135 | title=None, 136 | include_private_profiles=False, # profiles without a public id, "Linkedin Member" 137 | limit=None, 138 | ): 139 | """ 140 | Do a people search. 141 | """ 142 | filters = ["resultType->PEOPLE"] 143 | if connection_of: 144 | filters.append(f"connectionOf->{connection_of}") 145 | if network_depth: 146 | filters.append(f"network->{network_depth}") 147 | if regions: 148 | filters.append(f'geoRegion->{"|".join(regions)}') 149 | if industries: 150 | filters.append(f'industry->{"|".join(industries)}') 151 | if current_company: 152 | filters.append(f'currentCompany->{"|".join(current_company)}') 153 | if past_companies: 154 | filters.append(f'pastCompany->{"|".join(past_companies)}') 155 | if profile_languages: 156 | filters.append(f'profileLanguage->{"|".join(profile_languages)}') 157 | if nonprofit_interests: 158 | filters.append(f'nonprofitInterest->{"|".join(nonprofit_interests)}') 159 | if schools: 160 | filters.append(f'schools->{"|".join(schools)}') 161 | if title: 162 | filters.append(f"title->{title}") 163 | 164 | params = {"filters": "List({})".format(",".join(filters))} 165 | 166 | if keywords: 167 | keywords = keywords.replace(',','') 168 | params["keywords"] = keywords 169 | 170 | data = self.search(params, limit=limit) 171 | 172 | results = [] 173 | for item in data: 174 | if "publicIdentifier" not in item: 175 | continue 176 | results.append( 177 | { 178 | "urn_id": get_id_from_urn(item.get("targetUrn")), 179 | "distance": item.get("memberDistance", {}).get("value"), 180 | "public_id": item.get("publicIdentifier"), 181 | } 182 | ) 183 | 184 | return results 185 | 186 | def search_companies(self, keywords=None, limit=None): 187 | """ 188 | Do a company search. 189 | """ 190 | filters = ["resultType->COMPANIES"] 191 | 192 | params = { 193 | "filters": "List({})".format(",".join(filters)), 194 | "queryContext": "List(spellCorrectionEnabled->true)", 195 | } 196 | 197 | if keywords: 198 | keywords = keywords.replace(',','') 199 | params["keywords"] = keywords 200 | 201 | data = self.search(params, limit=limit) 202 | 203 | results = [] 204 | for item in data: 205 | if item.get("type") != "COMPANY": 206 | continue 207 | results.append( 208 | { 209 | "urn": item.get("targetUrn"), 210 | "urn_id": get_id_from_urn(item.get("targetUrn")), 211 | "name": item.get("title", {}).get("text"), 212 | "headline": item.get("headline", {}).get("text"), 213 | "subline": item.get("subline", {}).get("text"), 214 | } 215 | ) 216 | 217 | return results 218 | 219 | def get_profile_contact_info(self, public_id=None, urn_id=None): 220 | """ 221 | Return data for a single profile. 222 | 223 | [public_id] - public identifier i.e. tom-quirk-1928345 224 | [urn_id] - id provided by the related URN 225 | """ 226 | res = self._fetch( 227 | f"/identity/profiles/{public_id or urn_id}/profileContactInfo" 228 | ) 229 | data = res.json() 230 | 231 | contact_info = { 232 | "email_address": data.get("emailAddress"), 233 | "websites": [], 234 | "twitter": data.get("twitterHandles"), 235 | "birthdate": data.get("birthDateOn"), 236 | "ims": data.get("ims"), 237 | "phone_numbers": data.get("phoneNumbers", []), 238 | } 239 | 240 | websites = data.get("websites", []) 241 | for item in websites: 242 | if "com.linkedin.voyager.identity.profile.StandardWebsite" in item["type"]: 243 | item["label"] = item["type"][ 244 | "com.linkedin.voyager.identity.profile.StandardWebsite" 245 | ]["category"] 246 | elif "" in item["type"]: 247 | item["label"] = item["type"][ 248 | "com.linkedin.voyager.identity.profile.CustomWebsite" 249 | ]["label"] 250 | 251 | del item["type"] 252 | 253 | contact_info["websites"] = websites 254 | 255 | return contact_info 256 | 257 | def get_profile_skills(self, public_id=None, urn_id=None): 258 | """ 259 | Return the skills of a profile. 260 | 261 | [public_id] - public identifier i.e. tom-quirk-1928345 262 | [urn_id] - id provided by the related URN 263 | """ 264 | params = {"count": 100, "start": 0} 265 | res = self._fetch( 266 | f"/identity/profiles/{public_id or urn_id}/skills", params=params 267 | ) 268 | data = res.json() 269 | 270 | skills = data.get("elements", []) 271 | for item in skills: 272 | del item["entityUrn"] 273 | 274 | return skills 275 | 276 | def get_profile(self, public_id=None, urn_id=None): 277 | """ 278 | Return data for a single profile. 279 | 280 | [public_id] - public identifier i.e. tom-quirk-1928345 281 | [urn_id] - id provided by the related URN 282 | """ 283 | # NOTE this still works for now, but will probably eventually have to be converted to 284 | # https://www.linkedin.com/voyager/api/identity/profiles/ACoAAAKT9JQBsH7LwKaE9Myay9WcX8OVGuDq9Uw 285 | res = self._fetch(f"/identity/profiles/{public_id or urn_id}/profileView") 286 | 287 | data = res.json() 288 | if data and "status" in data and data["status"] != 200: 289 | self.logger.info("request failed: {}".format(data["message"])) 290 | return {} 291 | 292 | # massage [profile] data 293 | profile = data["profile"] 294 | if "miniProfile" in profile: 295 | if "picture" in profile["miniProfile"]: 296 | profile["displayPictureUrl"] = profile["miniProfile"]["picture"][ 297 | "com.linkedin.common.VectorImage" 298 | ]["rootUrl"] 299 | profile["profile_id"] = get_id_from_urn(profile["miniProfile"]["entityUrn"]) 300 | 301 | del profile["miniProfile"] 302 | 303 | del profile["defaultLocale"] 304 | del profile["supportedLocales"] 305 | del profile["versionTag"] 306 | del profile["showEducationOnProfileTopCard"] 307 | 308 | # massage [experience] data 309 | experience = data["positionView"]["elements"] 310 | for item in experience: 311 | if "company" in item and "miniCompany" in item["company"]: 312 | if "logo" in item["company"]["miniCompany"]: 313 | logo = item["company"]["miniCompany"]["logo"].get( 314 | "com.linkedin.common.VectorImage" 315 | ) 316 | if logo: 317 | item["companyLogoUrl"] = logo["rootUrl"] 318 | del item["company"]["miniCompany"] 319 | 320 | profile["experience"] = experience 321 | 322 | # massage [skills] data 323 | # skills = [item["name"] for item in data["skillView"]["elements"]] 324 | # profile["skills"] = skills 325 | 326 | profile["skills"] = self.get_profile_skills(public_id=public_id, urn_id=urn_id) 327 | 328 | # massage [education] data 329 | education = data["educationView"]["elements"] 330 | for item in education: 331 | if "school" in item: 332 | if "logo" in item["school"]: 333 | item["school"]["logoUrl"] = item["school"]["logo"][ 334 | "com.linkedin.common.VectorImage" 335 | ]["rootUrl"] 336 | del item["school"]["logo"] 337 | 338 | profile["education"] = education 339 | 340 | # massage [languages] data 341 | languages = data["languageView"]["elements"] 342 | for item in languages: 343 | del item["entityUrn"] 344 | profile["languages"] = languages 345 | 346 | # massage [publications] data 347 | publications = data["publicationView"]["elements"] 348 | for item in publications: 349 | del item["entityUrn"] 350 | for author in item.get("authors", []): 351 | del author["entityUrn"] 352 | profile["publications"] = publications 353 | 354 | # massage [certifications] data 355 | certifications = data["certificationView"]["elements"] 356 | for item in certifications: 357 | del item["entityUrn"] 358 | profile["certifications"] = certifications 359 | 360 | # massage [volunteer] data 361 | volunteer = data["volunteerExperienceView"]["elements"] 362 | for item in volunteer: 363 | del item["entityUrn"] 364 | profile["volunteer"] = volunteer 365 | 366 | # massage [honors] data 367 | honors = data["honorView"]["elements"] 368 | for item in honors: 369 | del item["entityUrn"] 370 | profile["honors"] = honors 371 | 372 | return profile 373 | 374 | def get_profile_connections(self, urn_id): 375 | """ 376 | Return a list of profile ids connected to profile of given [urn_id] 377 | """ 378 | #, network_depth="F" 379 | return self.search_people(connection_of=urn_id) 380 | 381 | 382 | def get_current_profile(self): 383 | """ 384 | GET current profile 385 | """ 386 | response = self._fetch( 387 | f'/me/', headers={"accept": "application/vnd.linkedin.normalized+json+2.1"}) 388 | data = response.json() 389 | 390 | profile = { 391 | 'firstName': data['included'][0]['firstName'], 392 | 'lastName': data['included'][0]['lastName'], 393 | 'publicIdentifier': data['included'][0]['publicIdentifier'], 394 | 'occupation': data['included'][0]['occupation'], 395 | 'message_id': data['included'][0]['entityUrn'].split(':')[3], 396 | 'is_premium': data.get('data').get('premiumSubscriber'), 397 | } 398 | 399 | try: 400 | profile['avatarUrl'] = data['included'][0]['picture']['rootUrl'] + \ 401 | data['included'][0]['picture']['artifacts'][2]['fileIdentifyingUrlPathSegment'] 402 | except TypeError: 403 | profile['avatarUrl'] = None 404 | 405 | return profile 406 | 407 | def get_company_updates( 408 | self, public_id=None, urn_id=None, max_results=None, results=[] 409 | ): 410 | """" 411 | Return a list of company posts 412 | 413 | [public_id] - public identifier ie - microsoft 414 | [urn_id] - id provided by the related URN 415 | """ 416 | params = { 417 | "companyUniversalName": {public_id or urn_id}, 418 | "q": "companyFeedByUniversalName", 419 | "moduleKey": "member-share", 420 | "count": Linkedin._MAX_UPDATE_COUNT, 421 | "start": len(results), 422 | } 423 | 424 | res = self._fetch(f"/feed/updates", params=params) 425 | 426 | data = res.json() 427 | 428 | if ( 429 | len(data["elements"]) == 0 430 | or (max_results is not None and len(results) >= max_results) 431 | or ( 432 | max_results is not None 433 | and len(results) / max_results >= Linkedin._MAX_REPEATED_REQUESTS 434 | ) 435 | ): 436 | return results 437 | 438 | results.extend(data["elements"]) 439 | self.logger.debug(f"results grew: {len(results)}") 440 | 441 | return self.get_company_updates( 442 | public_id=public_id, urn_id=urn_id, results=results, max_results=max_results 443 | ) 444 | 445 | def get_profile_updates( 446 | self, public_id=None, urn_id=None, max_results=None, results=[] 447 | ): 448 | """" 449 | Return a list of profile posts 450 | 451 | [public_id] - public identifier i.e. tom-quirk-1928345 452 | [urn_id] - id provided by the related URN 453 | """ 454 | params = { 455 | "profileId": {public_id or urn_id}, 456 | "q": "memberShareFeed", 457 | "moduleKey": "member-share", 458 | "count": Linkedin._MAX_UPDATE_COUNT, 459 | "start": len(results), 460 | } 461 | 462 | res = self._fetch(f"/feed/updates", params=params) 463 | 464 | data = res.json() 465 | 466 | if ( 467 | len(data["elements"]) == 0 468 | or (max_results is not None and len(results) >= max_results) 469 | or ( 470 | max_results is not None 471 | and len(results) / max_results >= Linkedin._MAX_REPEATED_REQUESTS 472 | ) 473 | ): 474 | return results 475 | 476 | results.extend(data["elements"]) 477 | self.logger.debug(f"results grew: {len(results)}") 478 | 479 | return self.get_profile_updates( 480 | public_id=public_id, urn_id=urn_id, results=results, max_results=max_results 481 | ) 482 | 483 | def get_current_profile_views(self): 484 | """ 485 | Get profile view statistics, including chart data. 486 | """ 487 | res = self._fetch(f"/identity/wvmpCards") 488 | 489 | data = res.json() 490 | return data["elements"][0]["value"][ 491 | "com.linkedin.voyager.identity.me.wvmpOverview.WvmpViewersCard" 492 | ]["insightCards"][0]["value"][ 493 | "com.linkedin.voyager.identity.me.wvmpOverview.WvmpSummaryInsightCard" 494 | ][ 495 | "numViews" 496 | ] 497 | 498 | def get_school(self, public_id): 499 | """ 500 | Return data for a single school. 501 | 502 | [public_id] - public identifier i.e. uq 503 | """ 504 | params = { 505 | "decorationId": "com.linkedin.voyager.deco.organization.web.WebFullCompanyMain-12", 506 | "q": "universalName", 507 | "universalName": public_id, 508 | } 509 | 510 | res = self._fetch(f"/organization/companies?{urlencode(params)}") 511 | 512 | data = res.json() 513 | 514 | if data and "status" in data and data["status"] != 200: 515 | self.logger.info("request failed: {}".format(data)) 516 | return {} 517 | 518 | school = data["elements"][0] 519 | 520 | return school 521 | 522 | def get_company(self, public_id): 523 | """ 524 | Return data for a single company. 525 | 526 | [public_id] - public identifier i.e. univeristy-of-queensland 527 | """ 528 | params = { 529 | "decorationId": "com.linkedin.voyager.deco.organization.web.WebFullCompanyMain-12", 530 | "q": "universalName", 531 | "universalName": public_id, 532 | } 533 | 534 | res = self._fetch(f"/organization/companies", params=params) 535 | 536 | data = res.json() 537 | 538 | if data and "status" in data and data["status"] != 200: 539 | self.logger.info("request failed: {}".format(data["message"])) 540 | return {} 541 | 542 | company = data["elements"][0] 543 | 544 | return company 545 | 546 | def get_conversation_details(self, profile_urn_id): 547 | """ 548 | Return the conversation (or "message thread") details for a given [public_profile_id] 549 | """ 550 | # passing `params` doesn't work properly, think it's to do with List(). 551 | # Might be a bug in `requests`? 552 | res = self._fetch( 553 | f"/messaging/conversations?\ 554 | keyVersion=LEGACY_INBOX&q=participants&recipients=List({profile_urn_id})" 555 | ) 556 | 557 | data = res.json() 558 | 559 | item = data["elements"][0] 560 | item["id"] = get_id_from_urn(item["entityUrn"]) 561 | 562 | return item 563 | 564 | def get_conversations(self): 565 | """ 566 | Return list of conversations the user is in. 567 | """ 568 | params = {"keyVersion": "LEGACY_INBOX"} 569 | 570 | res = self._fetch(f"/messaging/conversations", params=params) 571 | 572 | return res.json() 573 | 574 | def get_conversation(self, conversation_urn_id): 575 | """ 576 | Return the full conversation at a given [conversation_urn_id] 577 | """ 578 | res = self._fetch(f"/messaging/conversations/{conversation_urn_id}/events") 579 | 580 | return res.json() 581 | 582 | def send_message(self, conversation_urn_id=None, recipients=[], message_body=None): 583 | """ 584 | Send a message to a given conversation. If error, return true. 585 | 586 | Recipients: List of profile urn id's 587 | """ 588 | params = {"action": "create"} 589 | 590 | if not (conversation_urn_id or recipients) and not message_body: 591 | return True 592 | 593 | message_event = { 594 | "eventCreate": { 595 | "value": { 596 | "com.linkedin.voyager.messaging.create.MessageCreate": { 597 | "body": message_body, 598 | "attachments": [], 599 | "attributedBody": {"text": message_body, "attributes": []}, 600 | "mediaAttachments": [], 601 | } 602 | } 603 | } 604 | } 605 | 606 | if conversation_urn_id and not recipients: 607 | res = self._post( 608 | f"/messaging/conversations/{conversation_urn_id}/events", 609 | params=params, 610 | data=json.dumps(message_event), 611 | ) 612 | elif recipients and not conversation_urn_id: 613 | message_event["recipients"] = recipients 614 | message_event["subtype"] = "MEMBER_TO_MEMBER" 615 | payload = { 616 | "keyVersion": "LEGACY_INBOX", 617 | "conversationCreate": message_event, 618 | } 619 | res = self._post( 620 | f"/messaging/conversations", params=params, data=json.dumps(payload) 621 | ) 622 | 623 | return res.status_code != 201 624 | 625 | def mark_conversation_as_seen(self, conversation_urn_id): 626 | """ 627 | Send seen to a given conversation. If error, return True. 628 | """ 629 | payload = json.dumps({"patch": {"$set": {"read": True}}}) 630 | 631 | res = self._post( 632 | f"/messaging/conversations/{conversation_urn_id}", data=payload 633 | ) 634 | 635 | return res.status_code != 200 636 | 637 | def get_user_profile(self): 638 | """" 639 | Return current user profile 640 | """ 641 | sleep( 642 | random.randint(0, 1) 643 | ) # sleep a random duration to try and evade suspention 644 | 645 | res = self._fetch(f"/me") 646 | 647 | data = res.json() 648 | 649 | return data 650 | 651 | def get_invitations(self, start=0, limit=3): 652 | """ 653 | Return list of new invites 654 | """ 655 | params = { 656 | "start": start, 657 | "count": limit, 658 | "includeInsights": True, 659 | "q": "receivedInvitation", 660 | } 661 | 662 | res = self._fetch( 663 | f"{self.client.API_BASE_URL}/relationships/invitationViews", params=params 664 | ) 665 | 666 | if res.status_code != 200: 667 | return [] 668 | 669 | response_payload = res.json() 670 | return [element["invitation"] for element in response_payload["elements"]] 671 | 672 | def reply_invitation( 673 | self, invitation_entity_urn, invitation_shared_secret, action="accept" 674 | ): 675 | """ 676 | Reply to an invite, the default is to accept the invitation. 677 | @Param: invitation_entity_urn: str 678 | @Param: invitation_shared_secret: str 679 | @Param: action: "accept" or "ignore" 680 | Returns True if sucess, False otherwise 681 | """ 682 | invitation_id = get_id_from_urn(invitation_entity_urn) 683 | params = {"action": action} 684 | payload = json.dumps( 685 | { 686 | "invitationId": invitation_id, 687 | "invitationSharedSecret": invitation_shared_secret, 688 | "isGenericInvitation": False, 689 | } 690 | ) 691 | 692 | res = self._post( 693 | f"{self.client.API_BASE_URL}/relationships/invitations/{invitation_id}", 694 | params=params, 695 | data=payload, 696 | ) 697 | 698 | return res.status_code == 200 699 | 700 | def add_connection(self, profile_urn_id): 701 | 702 | data = '{"trackingId":"yvzykVorToqcOuvtxjSFMg==","invitations":[],"excludeInvitations":[],"invitee":{"com.linkedin.voyager.growth.invitation.InviteeProfile":{"profileId":' + '"' + profile_urn_id + '"' + '}}}' 703 | 704 | res = self._post( 705 | '/growth/normInvitations', 706 | data=data, 707 | headers={"accept": "application/vnd.linkedin.normalized+json+2.1"}, 708 | ) 709 | return res.status_code 710 | 711 | def remove_connection(self, public_profile_id): 712 | res = self._post( 713 | f"/identity/profiles/{public_profile_id}/profileActions?action=disconnect", 714 | headers={"accept": "application/vnd.linkedin.normalized+json+2.1"}, 715 | ) 716 | 717 | return res.status_code != 200 718 | 719 | 720 | def get_profile_privacy_settings(self, public_profile_id): 721 | res = self._fetch( 722 | f"/identity/profiles/{public_profile_id}/privacySettings", 723 | headers={"accept": "application/vnd.linkedin.normalized+json+2.1"}, 724 | ) 725 | if res.status_code != 200: 726 | return {} 727 | 728 | data = res.json() 729 | return data.get("data", {}) 730 | 731 | def get_profile_member_badges(self, public_profile_id): 732 | res = self._fetch( 733 | f"/identity/profiles/{public_profile_id}/memberBadges", 734 | headers={"accept": "application/vnd.linkedin.normalized+json+2.1"}, 735 | ) 736 | if res.status_code != 200: 737 | return {} 738 | 739 | data = res.json() 740 | return data.get("data", {}) 741 | 742 | def get_profile_network_info(self, public_profile_id): 743 | res = self._fetch( 744 | f"/identity/profiles/{public_profile_id}/networkinfo", 745 | headers={"accept": "application/vnd.linkedin.normalized+json+2.1"}, 746 | ) 747 | if res.status_code != 200: 748 | return {} 749 | 750 | data = res.json() 751 | return data.get("data", {}) -------------------------------------------------------------------------------- /lib/LinkedinAPI/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) 4 | COOKIE_PATH = os.path.join(ROOT_DIR, "cookies/") -------------------------------------------------------------------------------- /lib/LinkedinAPI/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krishpranav/socialosint/2b469c451cde2cd8453d2a5a5ae476d3a87e138f/lib/LinkedinAPI/utils/__init__.py -------------------------------------------------------------------------------- /lib/LinkedinAPI/utils/helpers.py: -------------------------------------------------------------------------------- 1 | def get_id_from_urn(urn): 2 | return urn.split(":")[3] -------------------------------------------------------------------------------- /lib/PwnDB/PwnDB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # imports 5 | import requests, json, time, random, sys 6 | # from core.colors import colors 7 | from modules.colors import colors 8 | 9 | def haveIBeenPwned(email): 10 | 11 | cookies = { 12 | '__cfduid': 'db69bfd7ae01e4d5d3c3f1e9c8a89bff41585353125', 13 | 'ai_user': '5Ner1|2020-03-27T23:52:08.257Z', 14 | '_ga': 'GA1.2.1775275446.1585353129', 15 | '_gid': 'GA1.2.1675250400.1585501117', 16 | 'ai_session': 'ZPXdR|1585501117405|1585503143569', 17 | 'Searches': '7', 18 | 'BreachedSites': '28', 19 | 'Pastes': '0', 20 | } 21 | 22 | headers = { 23 | 'Host': 'haveibeenpwned.com', 24 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0', 25 | 'Accept': '*/*', 26 | 'Accept-Language': 'en-US,en;q=0.5', 27 | 'Accept-Encoding': 'gzip, deflate', 28 | 'Referer': 'https://haveibeenpwned.com/', 29 | 'X-Requested-With': 'XMLHttpRequest', 30 | 'Request-Id': '|DOpkD.junPv', 31 | 'Request-Context': 'appId=cid-v1:bcc569a3-d364-4306-8bbe-83e9fe4d020e', 32 | 'Connection': 'close', 33 | } 34 | 35 | print(colors.info + " Searching information about Leaks found :)" + colors.end) 36 | 37 | try: 38 | response = requests.get('http://haveibeenpwned.com/unifiedsearch/'+str(email), headers=headers, cookies=cookies, verify=False) 39 | 40 | infoAboutLeak = [] 41 | items = json.loads(response.text) 42 | breaches = items['Breaches'] 43 | 44 | for breache in breaches: 45 | name = breache['Name'] 46 | domain = breache['Domain'] 47 | date = breache['BreachDate'] 48 | infoAboutLeak.append(json.dumps({'name': name, 'domain': domain, 'date': date})) 49 | except: 50 | infoAboutLeak.append(" Sources not found") 51 | 52 | return infoAboutLeak 53 | 54 | def parsePwndbResponse(mail,text): 55 | if "Array" not in text: 56 | return None 57 | 58 | leaks = text.split("Array")[1:] 59 | emails = [] 60 | foundLeak = False 61 | 62 | for leak in leaks: 63 | leaked_email = '' 64 | domain = '' 65 | password = '' 66 | try : 67 | leaked_email = leak.split("[luser] =>")[1].split("[")[0].strip() 68 | domain = leak.split("[domain] =>")[1].split("[")[0].strip() 69 | password = leak.split("[password] =>")[1].split(")")[0].strip() 70 | except: 71 | pass 72 | if leaked_email != "donate" and domain != "btc.thx" and password != "12cC7BdkBbru6JGsWvTx4PPM5LjLX8g": 73 | foundLeak = True 74 | emails.append(json.dumps({'username': leaked_email, 'domain': domain, 'password': password})) 75 | 76 | if foundLeak: 77 | emails.append(haveIBeenPwned(mail)) 78 | 79 | return emails 80 | 81 | def findLeak(emails,tor_proxy): 82 | 83 | session = requests.session() 84 | session.proxies = {'http': 'socks5h://{}'.format(tor_proxy), 'https': 'socks5h://{}'.format(tor_proxy)} 85 | 86 | url = "http://pwndb2am4tzkvold.onion/" 87 | leaks = [] 88 | 89 | print(colors.info + " Searching Leaks :)" + colors.end) 90 | for email in emails: 91 | 92 | item = json.loads(email) 93 | mail = item.get("email") 94 | poc = mail.replace("(","") 95 | mail = poc.replace(")","") 96 | user = item.get("user") 97 | userID = item.get("userID") 98 | username = mail.split("@")[0] 99 | domain = mail.split("@")[1] 100 | request_data = {'luser': username, 'domain': domain, 'luseropr': 1, 'domainopr': 1, 'submitform': 'em'} 101 | time.sleep(1) 102 | try: 103 | response = session.post(url, data=request_data) 104 | except Exception as e: 105 | print(colors.bad + " Can't connect to service! restart tor service and try again." + colors.end) 106 | print(e) 107 | sys.exit() 108 | print(colors.info + " Searching: " + mail + colors.end) 109 | if response.status_code == 200: 110 | target = {'user': user, 'userID': userID, 'email': mail, 'leak': parsePwndbResponse(mail,response.text)} 111 | leaks.append(target) 112 | print(colors.good + " The request was successful" + colors.end) 113 | else: 114 | print(response.status_code) 115 | print(response.text) 116 | print(colors.bad + " The request was not successful for the user: " + colors.W + user + colors.R + " and email: " + colors.W + mail + colors.R + ". Maybe you should increase the delay" + colors.end) 117 | 118 | return leaks 119 | 120 | def saveResultsPwnDB(results): 121 | with open("PwnDBResults.txt", "a") as resultFile: 122 | for result in results: 123 | leak = result.get("leak") 124 | if len(leak) >= 1: 125 | print(colors.good + " User: " + colors.V + result.get("user") + colors.B + " Email: " + colors.V + result.get("email") + colors.V + " Have Leaks " + colors.end) 126 | resultFile.write("User: " + result.get("user") + " Email: " + result.get("email")+"\n") 127 | for i in range (len(leak)-1): 128 | print("\t" + colors.good + " Leaks found in PwnDB: " + colors.V + str(leak[i]) + colors.end) 129 | resultFile.write("\t" + "Leaks found in PwnDB: " + str(leak[i]) + "\n") 130 | 131 | haveIBeenPwnedInfo = leak[-1] 132 | print("\t\t" + colors.info + " Information found in HaveIBeenPwned from pwned websites" + colors.end) 133 | for infoPwned in haveIBeenPwnedInfo: 134 | print("\t\t" + colors.good + " " + colors.V + infoPwned + colors.end) 135 | resultFile.write("\t\t" + infoPwned + "\n") 136 | else: 137 | print(colors.good + " User: " + colors.W + result.get("user") + colors.B + " Email: " + colors.W + result.get("email") + colors.B + " Not Have Leaks in PwnDB" + colors.end) 138 | resultFile.close() 139 | 140 | 141 | -------------------------------------------------------------------------------- /lib/PwnDB/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krishpranav/socialosint/2b469c451cde2cd8453d2a5a5ae476d3a87e138f/lib/PwnDB/__init__.py -------------------------------------------------------------------------------- /lib/TwitterAPI/TwitterAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import twint 5 | from modules.colors import colors 6 | 7 | 8 | def getTweets( 9 | username = None, 10 | search = None, 11 | year = None, 12 | since = None, 13 | until = None, 14 | email = True, 15 | phone = None, 16 | verified = None, 17 | hashtags = None, 18 | userid = None, 19 | limit = 100, 20 | all_tweets = None, 21 | profile_full = None 22 | ): 23 | 24 | tweets = [] 25 | # Configure 26 | c = twint.Config() 27 | c.Limit = limit 28 | c.Email = email 29 | 30 | if username and not all_tweets: 31 | c.Username = username 32 | if search: 33 | c.Search = search 34 | if year: 35 | c.Year = year 36 | if since: 37 | c.Since = since 38 | if until: 39 | c.Until = until 40 | if phone: 41 | c.Phone = phone 42 | if verified: 43 | c.Verified = verified 44 | if hashtags: 45 | c.Hashtags = hashtags 46 | if userid: 47 | c.User_id = userid 48 | if all_tweets: 49 | c.All = username 50 | if profile_full: 51 | c.Profile_full = True 52 | 53 | c.Store_object = True 54 | c.Store_object_tweets_list = tweets 55 | 56 | # Run 57 | twint.run.Search(c) 58 | 59 | return getListOfTweets(tweets) 60 | 61 | def getFollowers( 62 | username, 63 | limit = 100 64 | ): 65 | 66 | users = [] 67 | 68 | c = twint.Config() 69 | c.Username = username 70 | c.Limit = limit 71 | c.Store_object = True 72 | twint.run.Followers(c) 73 | users = twint.output.follows_list 74 | return users 75 | 76 | def getFollowings( 77 | username, 78 | limit = 100 79 | ): 80 | 81 | users = [] 82 | 83 | c = twint.Config() 84 | c.Username = username 85 | c.Limit = limit 86 | c.Store_object = True 87 | twint.run.Following(c) 88 | users = twint.output.follows_list 89 | return users 90 | 91 | 92 | def getUserInformation(username): 93 | print(colors.good + " Getting the information from the user: " + username + "\n" + colors.W) 94 | c = twint.Config() 95 | c.Username = username 96 | twint.run.Lookup(c) 97 | print("\n"+colors.end) 98 | 99 | def getListOfTweets(tweets): 100 | results = [] 101 | for tweet in tweets: 102 | results.append(tweet.__dict__) 103 | return results -------------------------------------------------------------------------------- /lib/TwitterAPI/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krishpranav/socialosint/2b469c451cde2cd8453d2a5a5ae476d3a87e138f/lib/TwitterAPI/__init__.py -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krishpranav/socialosint/2b469c451cde2cd8453d2a5a5ae476d3a87e138f/lib/__init__.py -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krishpranav/socialosint/2b469c451cde2cd8453d2a5a5ae476d3a87e138f/modules/__init__.py -------------------------------------------------------------------------------- /modules/banner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # imports 5 | from modules.colors import colors 6 | import os 7 | def banner(): 8 | 9 | banner = "SOCIAL OSINT" 10 | ban = banner.split("\n") 11 | for line in ban: 12 | centered = line.center(os.get_terminal_size().columns) 13 | print(colors.BOLD + centered + colors.end) -------------------------------------------------------------------------------- /modules/colors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | class colors: 5 | BOLD = '\033[1m' 6 | UNDERLINE = '\033[4m' 7 | G, Y, R, W, V, B, end = '\033[92m', '\033[93m', '\033[91m', '\x1b[37m', '\033[95m', '\033[94m', '\033[0m' 8 | info = end + W + "[-]" + W 9 | good = end + G + "[+]" + B 10 | bad = end + R + "[" + W + "!" + R + "]" 11 | -------------------------------------------------------------------------------- /modules/instagram.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys, requests, argparse, json, re, os, time 4 | from modules.colors import colors 5 | from lib.InstagramAPI import InstagramAPI 6 | from lib.PwnDB import PwnDB 7 | 8 | def getEmailsFromListOfUsers(api,items): 9 | targets = [] 10 | print(colors.info + " Searching users... :) \n" + colors.end) 11 | 12 | for item in items: 13 | 14 | user = str(item.get("user").get("username")) 15 | targets.append(getUserProfile(api,user)) 16 | 17 | return getEmailsFromUsers(targets) 18 | 19 | def getUserProfile(api,username): 20 | 21 | print(colors.info + " Getting information from the user: " + str(username) + colors.end) 22 | time.sleep(float(0.5)) 23 | api.searchUsername(username) 24 | 25 | if api.LastResponse.status_code == 429: 26 | print(colors.bad + " The request was not successful for the user: " + colors.W + username + colors.R + ". Maybe you should increase the delay" + colors.end) 27 | 28 | return api.LastJson 29 | 30 | def getEmailsFromUsers(users): 31 | 32 | targets = [] 33 | print(colors.info + " Searching users with emails :)" + colors.end) 34 | 35 | for user in users: 36 | if str(user["status"]) == "ok": 37 | 38 | username = str(user["user"].get("username")) 39 | userID = str(user["user"].get("pk")) 40 | email = str(user["user"].get("public_email")) 41 | followers = str(user["user"].get("follower_count")) 42 | following = str(user["user"].get("following_count")) 43 | biography = user["user"].get("biography").split(" ") 44 | private = str(user["user"].get("is_private")) 45 | 46 | if email != "None" and email !="": 47 | targets.append(json.dumps({"user":username,"userID":userID,"email":email,"private":private})) 48 | print(colors.good + " Username: " + colors.W + username + colors.B + " UserID: " + colors.W + userID + colors.B + " Email: " + colors.W + email + colors.B + " Followers: " + colors.W + followers + colors.B + " Following: " + colors.W + following + colors.end) 49 | else: 50 | result = searchEmailInBio(biography) 51 | if result: 52 | print(colors.info + " The email was found in the user's biography: " + result + colors.end) 53 | print(colors.good + " Username: " + colors.W + username + colors.B + " UserID: " + colors.W + userID + colors.B + " Email: " + colors.W + result + colors.B + " Followers: " + colors.W + followers + colors.B + " Following: " + colors.W + following + colors.end) 54 | targets.append(json.dumps({"user":username,"userID":userID,"email":result,"private":private})) 55 | 56 | return list(set(targets)) 57 | 58 | def searchEmailInBio(bio): 59 | 60 | for word in bio: 61 | if re.match('^[(a-z0-9\_\-\.)]+@[(a-z0-9\_\-\.)]+\.[(a-z)]{2,15}$',word): 62 | return word 63 | 64 | def checkUserVisibility(api,targetID): 65 | api.getUserFeed(targetID) 66 | if api.LastJson["status"] == "fail": 67 | print(colors.bad + " You are not authorized to view this user. " + colors.end) 68 | return False 69 | else: 70 | return True 71 | 72 | 73 | def getLocationID(api,location): 74 | 75 | api.searchLocation(location) 76 | items = api.LastJson["items"] 77 | for item in items: 78 | print(colors.good + " City: " + colors.W + item.get("location").get("name") + colors.B +" Search ID: " + colors.W + str(item.get("location").get("pk")) + colors.end) 79 | 80 | def getUsersFromAHashTag(api,hashtag): 81 | api.getHashtagFeed(hashtag) 82 | return getEmailsFromListOfUsers(api,api.LastJson["items"]) 83 | 84 | def getUsersFromLocation(api,locationId): 85 | api.getLocationFeed(locationId) 86 | return getEmailsFromListOfUsers(api,api.LastJson["items"]) 87 | 88 | def getUserInformation(api,target): 89 | userInfo = [] 90 | api.searchUsername(str(target)) 91 | info = api.LastJson 92 | userInfo.append(info) 93 | results = getEmailsFromUsers(userInfo) 94 | 95 | if not checkUserVisibility(api,info["user"].get("pk")) and results == []: 96 | return False 97 | else: 98 | return results 99 | 100 | def getUsersOfTheSearch(api,query): 101 | 102 | print(colors.info + "Searching users..." + colors.end) 103 | api.searchUsers(query) 104 | users = api.LastJson["users"] 105 | results = [] 106 | 107 | for user in users: 108 | api.searchUsername(user.get("username")) 109 | results.append(api.LastJson) 110 | userInfo = api.LastJson["user"] 111 | print(colors.good + " Username: " + colors.W + user.get("username") + colors.B + " User ID: " + colors.W + str(user.get("pk")) + colors.B + " Private: " + colors.W + str(user.get("is_private")) + colors.B + " Followers: " + colors.W + str(userInfo.get("follower_count")) + colors.B + " Following: " + colors.W + str(userInfo.get("following_count")) + colors.end) 112 | 113 | return getEmailsFromUsers(results) 114 | 115 | def getMyFollowers(api): 116 | users = api.getTotalSelfFollowers() 117 | return getListOfUsers(api,users) 118 | 119 | def getMyFollowings(api): 120 | 121 | users = api.getTotalSelfFollowings() 122 | return getListOfUsers(api,users) 123 | 124 | def getUserFollowers(api,username): 125 | 126 | user = getUserProfile(api,username) 127 | users = api.getTotalFollowers(user["user"].get("pk")) 128 | return getListOfUsers(api,users) 129 | 130 | def getUserFollowings(api,username): 131 | 132 | user = getUserProfile(api,username) 133 | users = api.getTotalFollowings(user["user"].get("pk")) 134 | return getListOfUsers(api,users) 135 | 136 | def getListOfUsers(api,users): 137 | targets = [] 138 | print(colors.info + " " + str(len(users)) + " targets have been obtained" + colors.end) 139 | 140 | for user in users: 141 | targets.append(getUserProfile(api,user.get("username"))) 142 | 143 | return getEmailsFromUsers(targets) 144 | 145 | def sortContacts(followers,followings): 146 | 147 | followers.extend(followings) 148 | targets = [] 149 | 150 | for user in followers: 151 | temp = json.loads(user) 152 | targets.append(json.dumps({"user":temp.get("user"),"userID":temp.get("userID"),"email":temp.get("email"),"private":temp.get("private")})) 153 | return list(set(targets)) 154 | 155 | 156 | -------------------------------------------------------------------------------- /modules/linkedin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys, json, time 4 | from modules.colors import colors 5 | from lib.LinkedinAPI import Linkedin 6 | from lib.PwnDB import PwnDB 7 | 8 | def getCompanyInformation(api,companyID): 9 | 10 | info = api.get_company(companyID) 11 | locations = info['confirmedLocations'] 12 | for location in locations: 13 | country = str(location.get("country")) 14 | area = str(location.get("geographicArea")) 15 | city = str(location.get("city")) 16 | postalCode = str(location.get("postalCode")) 17 | print(colors.good + " Country: " + colors.W + country + colors.B + " Area: " + colors.W + area + colors.B + " City: " + colors.W + city + colors.B + " Postal Code: " + colors.W + postalCode + colors.end ) 18 | 19 | def getEmployeesFromCurrentCompany(api,companyID): 20 | return api.search_people(current_company=[str(companyID)]) 21 | 22 | def getEmployeesFromPastCompany(api,companyID): 23 | return api.search_people(past_companies=[str(companyID)]) 24 | 25 | def getEmailsFromUsers(api,employees): 26 | 27 | results = [] 28 | for employee in employees: 29 | 30 | employeeID = str(employee.get("public_id")) 31 | userID = str(employee.get("urn_id")) 32 | info = getContactInformation(api,employeeID) 33 | email = str(info.get("email")) 34 | twitter = str(info.get("twitter")) 35 | phone = str(info.get("phone")) 36 | print(colors.good + " User ID: " + colors.W + userID + colors.B + " Public ID of employee: " + colors.W + employeeID + colors.B + " Email: " + colors.W + email + colors.B + " Phone: " + colors.W + phone + colors.B + " Twitter: " + colors.W + twitter + colors.end) 37 | 38 | if email != "Not Found": 39 | results.append(json.dumps({"user":employeeID,"userID":userID,"email":email})) 40 | return results 41 | 42 | def getCompanyEmployees(api,companies): 43 | targets = [] 44 | for company in companies: 45 | nameCompany = company.get("name") 46 | employees = searchUsersOfCompany(api,nameCompany) 47 | if employees != [] and employees not in targets: 48 | targets.append(employees) 49 | return unionUsers(targets) 50 | 51 | def unionUsers(targets): 52 | results = [] 53 | for target in targets: 54 | for result in target: 55 | results.append(result) 56 | print(colors.info + " " + str(len(results)) + " different users have been found in total ^-^" + colors.end) 57 | return results 58 | 59 | def searchUsers(api,query): 60 | print(colors.info + " Searching users... :)" + colors.end) 61 | items = api.search_people(keywords=query) 62 | 63 | for item in items: 64 | user = str(item.get("public_id")) 65 | userID = str(item.get("urn_id")) 66 | print(colors.good + " User: " + colors.W + user + colors.B + " userID: " + colors.W + userID + colors.end) 67 | print(colors.info + " " + str(len(items)) + " users have been found ^-^" + colors.end) 68 | return items 69 | 70 | def searchCompanies(api,query): 71 | print(colors.info + " Searching companies... :)" + colors.end) 72 | query = query.replace(',','') 73 | items = api.search_companies(query) 74 | 75 | for item in items: 76 | nameCompany = str(item.get("name")) 77 | companyID = str(item.get("urn_id")) 78 | numberEmployees = str(item.get("subline")) 79 | print(colors.good + " Name: " + colors.W + nameCompany + colors.B + " company ID: " + colors.W + companyID + colors.B + " Number of employees: " + colors.W + numberEmployees + colors.end) 80 | print(colors.info + " " + str(len(items)) + " companies have been found ^-^" + colors.end) 81 | return items 82 | 83 | def searchUsersOfCompany(api,nameCompany): 84 | print(colors.info + " Searching employees of company: " + nameCompany + colors.end) 85 | employees = api.search_people(keywords=nameCompany) 86 | print(colors.info + " " + str(len(employees)) + " employees have been found ^-^" + colors.end) 87 | 88 | return employees 89 | 90 | def getUserInformation(api,publicID): 91 | results = [] 92 | # profile = api.get_profile(publicID) 93 | # print(profile) 94 | info = getContactInformation(api,publicID) 95 | email = str(info.get("email")) 96 | twitter = str(info.get("twitter")) 97 | phone = str(info.get("phone")) 98 | print(colors.good + " User: " + colors.W + publicID + colors.B + " Email: " + colors.W + email + colors.B + " Phone: " + colors.W + phone + colors.B + " Twitter: " + colors.W + twitter + colors.end) 99 | 100 | if email != "Not Found": 101 | results.append(json.dumps({"user":publicID,"userID":"Not-Found","email":email})) 102 | return results 103 | 104 | def getContactInformation(api,publicID): 105 | 106 | print(colors.info + " Searching user contact information: " + publicID) 107 | info = api.get_profile_contact_info(publicID) 108 | email = '' 109 | twitter = '' 110 | phone = '' 111 | if info.get("email_address") == None: 112 | email = "Not Found" 113 | else: 114 | email = info.get("email_address") 115 | 116 | if info.get("twitter") == []: 117 | twitter = "Not Found" 118 | else: 119 | twitter = str(info.get("twitter")[0].get("name")) 120 | if info.get("phone_numbers") == []: 121 | phone = "Not Found" 122 | else: 123 | phone = info.get("phone_numbers")[0].get("number") 124 | 125 | return {"email":email,"twitter":twitter,"phone":phone} 126 | 127 | def sendContactRequestAListOfUsers(api, users): 128 | 129 | for user in users: 130 | userID = user.get("urn_id") 131 | sendContactRequest(api,userID) 132 | 133 | def sendContactRequest(api, userID): 134 | 135 | response = api.add_connection(userID) 136 | if response == 201: 137 | print(colors.good + " Contact request successfully" + colors.end) 138 | else: 139 | print(colors.bad + " Contact request unsuccessfully" + colors.end) 140 | 141 | return response 142 | 143 | def getFollowers(api, userID): 144 | print(colors.info + " Getting contacts..." + colors.end) 145 | followers = api.get_profile_connections(userID) 146 | print(colors.info + " " + str(len(followers)) + " contacts have been found ^-^" + colors.end) 147 | return followers 148 | 149 | def getMyContacts(api): 150 | print(colors.info + " Getting your contacts..." + colors.end) 151 | userID = getMyUserID(api) 152 | followers = getFollowers(api,userID) 153 | print(colors.info + " " + str(len(followers)) + " contacts have been found ^-^" + colors.end) 154 | return followers 155 | 156 | def getMyUserID(api): 157 | profile = api.get_current_profile() 158 | return profile.get("message_id") 159 | 160 | def getMyPublicID(api): 161 | profile = api.get_current_profile() 162 | return profile.get("publicIdentifier") -------------------------------------------------------------------------------- /modules/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os, sys, json 4 | from lib.InstagramAPI import InstagramAPI 5 | from lib.LinkedinAPI import Linkedin 6 | from lib.PwnDB import PwnDB 7 | 8 | from modules import instagram 9 | from modules import linkedin 10 | from modules import twitter 11 | from modules.colors import colors 12 | 13 | def saveResults(file,results): 14 | print(colors.info + " Writing the file..." + colors.end) 15 | content = "" 16 | with open(str(file), "r") as resultFile: 17 | content = resultFile.read() 18 | resultFile.close() 19 | with open(str(file), "a") as resultFile: 20 | for result in results: 21 | target = json.loads(result) 22 | if target['email'] not in content: 23 | resultFile.write(target['user']+":"+target['userID']+":"+target['email']+"\n") 24 | resultFile.close() 25 | print(colors.good + " Correctly saved information...\n" + colors.end) 26 | 27 | 28 | def readCredentials(credentialsFile): 29 | try: 30 | data = [] 31 | with open(credentialsFile) as json_file: 32 | data = json.load(json_file) 33 | json_file.close() 34 | except: 35 | print(colors.bad + " Incorrect JSON format" + colors.end) 36 | sys.exit() 37 | 38 | return data 39 | 40 | def instagramParameters(args,ig_username,ig_password): 41 | results = [] 42 | api = InstagramAPI(ig_username,ig_password) 43 | 44 | if (api.login()): 45 | print(colors.good + " Successful login to Instagram!\n" + colors.end) 46 | if args.info: 47 | instagram.getLocationID(api,args.info) 48 | 49 | if args.hashtag_ig: 50 | results.extend(instagram.getUsersFromAHashTag(api,args.hashtag_ig)) 51 | 52 | if args.target_ig: 53 | temp = instagram.getUserInformation(api,args.target_ig) 54 | if temp == False: 55 | print(colors.info + " The user has a private profile or doesn't have public email..." + colors.end) 56 | else: 57 | results.extend(temp) 58 | if args.followers_ig and not args.followings_ig: 59 | results.extend(instagram.getUserFollowers(api,args.target_ig)) 60 | if args.followings_ig and not args.followers_ig: 61 | results.extend(instagram.getUserFollowings(api,args.target_ig)) 62 | if args.followers_ig and args.followings_ig: 63 | followers = instagram.getUserFollowers(api,args.target_ig) 64 | followings = instagram.getUserFollowings(api,args.target_ig) 65 | results.extend(instagram.sortContacts(followers,followings)) 66 | 67 | if args.location: 68 | results.extend(instagram.getUsersFromLocation(api,args.location)) 69 | 70 | if args.search_users_ig: 71 | results.extend(instagram.getUsersOfTheSearch(api,args.search_users_ig)) 72 | 73 | if args.my_followers and not args.my_followings: 74 | results.extend(instagram.getMyFollowers(api)) 75 | 76 | if args.my_followings and not args.my_followers: 77 | results.extend(instagram.getMyFollowings(api)) 78 | 79 | if args.my_followings and args.my_followers: 80 | followers = instagram.getMyFollowers(api) 81 | followings = instagram.getMyFollowings(api) 82 | results.extend(instagram.sortContacts(followers,followings)) 83 | else: 84 | print(colors.bad + " Can't Login to Instagram!" + colors.end) 85 | 86 | return results 87 | 88 | 89 | def linkedinParameters(args,in_email,in_password): 90 | 91 | results = [] 92 | api = Linkedin(in_email, in_password) 93 | if api.__dict__.get("success"): 94 | print(colors.good + " Successful login to Linkedin!\n" + colors.end) 95 | 96 | if args.company: 97 | linkedin.getCompanyInformation(api,args.company) 98 | users = [] 99 | if args.employees: 100 | users = linkedin.getEmployeesFromCurrentCompany(api,args.company) 101 | results.extend(linkedin.getEmailsFromUsers(api,users)) 102 | if args.employees and args.add_contacts: 103 | linkedin.sendContactRequestAListOfUsers(api,users) 104 | 105 | 106 | if args.search_companies: 107 | companies = linkedin.searchCompanies(api,args.search_companies) 108 | users = [] 109 | if args.employees: 110 | users = linkedin.getCompanyEmployees(api,companies) 111 | results.extend(linkedin.getEmailsFromUsers(api, users)) 112 | if args.add_contacts: 113 | linkedin.sendContactRequestAListOfUsers(api,users) 114 | 115 | if args.my_contacts: 116 | results.extend(linkedin.getEmailsFromUsers(api,linkedin.getMyContacts(api))) 117 | 118 | if args.user_contacts: 119 | users = linkedin.getFollowers(api,args.user_contacts) 120 | results.extend(linkedin.getEmailsFromUsers(api,users)) 121 | if args.add_contacts: 122 | linkedin.sendContactRequestAListOfUsers(api,users) 123 | 124 | if args.search_users_in: 125 | users = linkedin.searchUsers(api,args.search_users_in) 126 | if args.pwndb: 127 | results.extend(linkedin.getEmailsFromUsers(api, users)) 128 | if args.add_contacts: 129 | linkedin.sendContactRequestAListOfUsers(api,users) 130 | 131 | if args.target_in: 132 | results.extend(linkedin.getUserInformation(api,args.target_in)) 133 | if args.add_contacts: 134 | linkedin.sendContactRequest(api,args.target_in) 135 | 136 | if args.add_a_contact: 137 | linkedin.sendContactRequest(api,args.add_one_contact) 138 | 139 | else: 140 | print(colors.bad + " Can't Login to linkedin!" + colors.end) 141 | 142 | return results 143 | 144 | def twitterParameters(args): 145 | results = [] 146 | print(colors.good + " Using Twint!\n" + colors.end) 147 | 148 | if args.target_tw and not args.followers_tw and not args.followings_tw: 149 | results.extend(twitter.getUserTweetsWithEmails( 150 | args.target_tw, 151 | args.limit, 152 | args.year, 153 | args.since, 154 | args.until, 155 | args.profile_full, 156 | args.all_tw)) 157 | 158 | if args.target_tw and args.followers_tw or args.followings_tw: 159 | users = [] 160 | if args.followers_tw: 161 | users = twitter.getFollowers(args.target_tw,args.limit) 162 | 163 | if args.followings_tw: 164 | users.extend(twitter.getFollowings(args.target_tw,args.limit)) 165 | 166 | results.extend(twitter.getTweetEmailsFromListOfUsers( 167 | list(set(users)), 168 | args.limit, 169 | args.year, 170 | args.since, 171 | args.until, 172 | args.profile_full, 173 | args.all_tw)) 174 | 175 | 176 | if args.hashtag_tw: 177 | results.extend(twitter.getTweetEmailsFromHashtag( 178 | args.hashtag_tw, 179 | args.limit, 180 | args.year, 181 | args.since, 182 | args.until)) 183 | 184 | return results 185 | 186 | 187 | def run(args): 188 | 189 | results = [] 190 | creds = '' 191 | if args.credentials and os.path.isfile(args.credentials) and os.access(args.credentials, os.R_OK): 192 | creds = readCredentials(args.credentials) 193 | else: 194 | print(colors.bad + " The file can't be accessed" + colors.end) 195 | sys.exit() 196 | 197 | if args.output and not os.path.isfile(args.output): 198 | print(colors.bad + " The file doesn't exist" + colors.end) 199 | sys.exit() 200 | 201 | if args.pwndb: 202 | status = os.system('systemctl is-active --quiet tor') 203 | if status != 0: 204 | print(colors.bad + " Can't connect to service! restart tor service and try again." + colors.end) 205 | sys.exit() 206 | 207 | if args.instagram: 208 | ig_username = creds.get("instagram").get("username") 209 | ig_password = creds.get("instagram").get("password") 210 | results.extend(instagramParameters(args,ig_username,ig_password)) 211 | 212 | if args.linkedin: 213 | in_email = creds.get("linkedin").get("email") 214 | in_password = creds.get("linkedin").get("password") 215 | results.extend(linkedinParameters(args,in_email,in_password)) 216 | 217 | if args.twitter: 218 | results.extend(twitterParameters(args)) 219 | 220 | if args.output: 221 | saveResults(args.output,results) 222 | 223 | if args.pwndb and results != [] and results != False: 224 | juicyInformation = PwnDB.findLeak(results,args.tor_proxy) 225 | PwnDB.saveResultsPwnDB(juicyInformation) 226 | elif results == []: 227 | print(colors.info + " No emails were found to search." + colors.end) 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /modules/twitter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import json, re 4 | from modules.colors import colors 5 | from lib.TwitterAPI import TwitterAPI 6 | 7 | 8 | def getTweetEmailsFromHashtag(query,limit,year,since,until): 9 | print(colors.good + " Emails will be searched in the tweets of the hashtag #" + query + colors.end) 10 | results = [] 11 | tweets = Twitter.getTweets(hashtags=True,limit=limit,search=query,year=year,until=until,since=since) 12 | results = getEmailsFromTweets(tweets) 13 | return results 14 | 15 | def getUserTweetsWithEmails(username,limit,year,since,until,profile_full,all_tw): 16 | Twitter.getUserInformation(username) 17 | print(colors.good + " User " + username + "'s tweets will be searched for emails" + colors.end) 18 | results = [] 19 | tweets = Twitter.getTweets(username=username,limit=limit,year=year,since=since,until=until,profile_full=profile_full,all_tweets=all_tw) 20 | results = getEmailsFromTweets(tweets) 21 | return results 22 | 23 | def getTweetEmailsFromListOfUsers(users,limit,year,since,until,profile_full,all_tw): 24 | 25 | results = [] 26 | print(colors.good + " Emails will be searched in the Tweets of " + str(len(users))+ " users :) " + colors.end) 27 | for user in users: 28 | results.extend(getUserTweetsWithEmails(user,limit,year,since,until,profile_full,all_tw)) 29 | 30 | return results 31 | 32 | def getUserInformation(username): 33 | Twitter.getUserInformation(username) 34 | 35 | def getFollowers(username,limit): 36 | print(colors.good + " Getting followers of the user: " + username + colors.end) 37 | return Twitter.getFollowers(username,limit) 38 | 39 | def getFollowings(username,limit): 40 | print(colors.good + " Getting followings of the user: " + username + colors.end) 41 | return Twitter.getFollowers(username,limit) 42 | 43 | 44 | 45 | 46 | def getEmailsFromTweets(tweets): 47 | results = [] 48 | 49 | for tweet in tweets: 50 | user = tweet.get("username") 51 | userID = str(tweet.get("user_id")) 52 | email = findEmail(tweet.get("tweet").split(" ")) 53 | if email: 54 | results.append(json.dumps({"user":user,"userID":userID,"email":email})) 55 | print(colors.good + " Username: " + colors.W + user + colors.B + " UserID: " + colors.W + userID + colors.B + " Email: " + colors.W + email + colors.end) 56 | 57 | return list(set(results)) 58 | 59 | def findEmail(tweet): 60 | for word in tweet: 61 | if re.match('^[(a-z0-9\_\-\.)]+@[(a-z0-9\_\-\.)]+\.[(a-z)]{2,15}$',word): 62 | return word -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # requirements required for this tool 2 | moviepy==0.2.2.11 3 | requests-toolbelt==0.7.0 4 | PySocks==1.6.8 5 | requests==2.20.0 6 | twint -------------------------------------------------------------------------------- /socialosint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | 6 | try: 7 | import argparse 8 | import moviepy 9 | import requests-toolbelt 10 | import PySocks 11 | import requests 12 | import twint 13 | except ImportError: 14 | print('Requirements not satisfied pls install it by typing this command\n.') 15 | print('python3 -m pip install -r requirements.txt') 16 | 17 | from modules.main import run 18 | import modules.banner as banner 19 | 20 | if __name__ == "__main__": 21 | banner.banner() 22 | parser = argparse.ArgumentParser(description="Social Pwned",prog='socialpwned.py',formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=40)) 23 | general = parser.add_argument_group("General Arguments","General arguments") 24 | general.add_argument("--credentials",required=True, action="store", help="Credentials in a JSON file. If you use instagram you must enter the username. If you use LinkedIn you must enter the email.") 25 | general.add_argument("--pwndb",required=False, action="store_true", help="Searches all the emails published by users in PwnDB and stores the results in the file PwnDBResults.txt") 26 | general.add_argument("--output",required=False, action="store", help="Save users, users ID and emails found in a file",metavar="FILE") 27 | general.add_argument("--tor-proxy",default='127.0.0.1:9050', type=str, help="Set Tor proxy (default: 127.0.0.1:9050)",metavar="PROXY") 28 | instagram = parser.add_argument_group("Instagram Arguments","Specific arguments for Instagram") 29 | instagram.add_argument("--instagram",required=False, action="store_true", help="You must use this flag if you want to use the Instagram functions") 30 | instagram.add_argument("--info",required=False, action="store", help="Get information about locations and their IDs.",metavar="QUERY") 31 | instagram.add_argument("--location",required=False, action="store", help="Get users with public email from a location ID.",metavar="LOCATION_ID") 32 | instagram.add_argument("--hashtag-ig",required=False, action="store", help="Get users with public email from a hashtag.",metavar="QUERY") 33 | instagram.add_argument("--target-ig",required=False, action="store", help="Get email, user ID, followers and followings of a specific username.",metavar="USERNAME") 34 | instagram.add_argument("--search-users-ig",required=False, action="store", help="Search any user in Instagram",metavar="QUERY") 35 | instagram.add_argument("--my-followers",required=False, action="store_true", help="Get users with public email from your followers") 36 | instagram.add_argument("--my-followings",required=False, action="store_true", help="Get users with public email from your followings") 37 | instagram.add_argument("--followers-ig",required=False, action="store_true", help="Get users with public emails from the followers of a target") 38 | instagram.add_argument("--followings-ig",required=False, action="store_true", help="Get users with public emails from the followings of a target") 39 | linkedin = parser.add_argument_group("Linkedin Arguments","Specific arguments for Linkedin") 40 | linkedin.add_argument("--linkedin",required=False, action="store_true", help="You must use this flag if you want to use the LikedIn functions") 41 | linkedin.add_argument("--company",required=False, action="store", help="Get information about a specific company from company ID",metavar="COMPANY_ID") 42 | linkedin.add_argument("--search-companies",required=False, action="store", help="Search any company.\nYou can also search for a specific company by entering the exact name",metavar="QUERY") 43 | linkedin.add_argument("--employees",required=False, action="store_true",help="Get the employees of a company and contact information. If you combine it with the flag --search-companies you get the current and past employees and if you combine it with the flag --company you get only the current employees") 44 | linkedin.add_argument("--my-contacts",required=False, action="store_true",help="Display my contacts and their contact information") 45 | linkedin.add_argument("--user-contacts",required=False, action="store",help="Display contacts from a specific user ID and their contact information",metavar="USER_ID") 46 | linkedin.add_argument("--search-users-in",required=False, action="store",help="Search any user in Linkedin",metavar="QUERY") 47 | linkedin.add_argument("--target-in",required=False, action="store",help="Get a user's contact information",metavar="USERNAME") 48 | linkedin.add_argument("--add-contacts",required=False, action="store_true",help="Send contact request for all users") 49 | linkedin.add_argument("--add-a-contact",required=False, action="store",help="Send contact request for a single user with his user ID",metavar="USER_ID") 50 | twitter = parser.add_argument_group("Twitter Arguments","Specific arguments for Twitter") 51 | twitter.add_argument("--twitter",required=False, action="store_true", help="You must use this flag if you want to use the Twitter functions") 52 | twitter.add_argument("--limit",required=False, action="store", default='100', type=int, help="Number of Tweets to pull (Increments of 20).",metavar="LIMIT") 53 | twitter.add_argument("--year",required=False, action="store", default=None, type=int, help="Filter Tweets before specified year.",metavar="YEAR") 54 | twitter.add_argument("--since",required=False, action="store", default=None, type=str, help="Filter Tweets sent since date (Example: 2017-12-27 20:30:15 or 2017-12-27).",metavar="DATE") 55 | twitter.add_argument("--until",required=False, action="store", default=None, type=str, help="Filter Tweets sent until date (Example: 2017-12-27 20:30:15 or 2017-12-27).",metavar="DATE") 56 | twitter.add_argument("--profile-full",required=False, action="store_true", help="Slow, but effective method of collecting a user's Tweets and RT.") 57 | twitter.add_argument("--all-tw",required=False, action="store_true", help="Search all Tweets associated with a user.") 58 | twitter.add_argument("--target-tw",required=False, action="store",help="User's Tweets you want to scrape",metavar="USERNAME") 59 | twitter.add_argument("--hashtag-tw",required=False, action="store",help="Get tweets containing emails from a hashtag",metavar="USERNAME") 60 | twitter.add_argument("--followers-tw",required=False, action="store_true", help="Scrape a person's followers.") 61 | twitter.add_argument("--followings-tw",required=False, action="store_true", help="Scrape a person's follows.") 62 | args = parser.parse_args() 63 | 64 | run(args) 65 | 66 | 67 | --------------------------------------------------------------------------------