├── proxychains.conf ├── requirements.txt ├── .gitignore ├── README.md ├── restart_instagram_downloader.sh ├── archived_codes.py ├── LICENSE ├── 615_import_firefox_session.py ├── variables.py ├── functions.py ├── best_instagram_downloader.py └── riad_azz.py /proxychains.conf: -------------------------------------------------------------------------------- 1 | [ProxyList] 2 | socks5 127.0.0.1 40000 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | telebot 2 | instaloader 3 | python-dotenv 4 | requests[socks] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignored folders 2 | __pycache__/ 3 | venv/ 4 | 5 | # ignored files 6 | .env 7 | test.py 8 | .cache 9 | nohup.out 10 | session 11 | instagram_response.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Best Instagram Downloader 2 | 3 | ## How to run guide 4 | - clone repo 5 | - set these to `.env` file in the root folder of repo 6 | ``` 7 | BEST_INSTAGRAM_DOWNLOADER_BOT_API 8 | INSTAGRAM_DOWNLOADER_LOG_CHANNEL_ID 9 | ``` 10 | - install required python modules: 11 | ``` 12 | pip3 install -r requirements.txt 13 | ``` 14 | - run the main file with: 15 | ``` 16 | nohup python3 best_instagram_downloader.py & 17 | ``` 18 | 19 | ## to-do next: 20 | - [x] handle expired session 21 | 22 | 23 | ------------------------------------------------- 24 | ## special thanks to riad-azz 25 | ### repo 26 | https://github.com/riad-azz/instagram-video-downloader 27 | ### main core 28 | src/app/api/instagram/p/[shortcode] 29 | ### my python equivalent code 30 | riad-azz.py -------------------------------------------------------------------------------- /restart_instagram_downloader.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the script name 4 | SCRIPT_NAME="best_instagram_downloader.py" 5 | 6 | # Find and kill the running process 7 | echo "Finding and killing the existing process..." 8 | PID=$(pgrep -f "$SCRIPT_NAME") 9 | 10 | if [ -n "$PID" ]; then 11 | echo "Killing process with PID $PID..." 12 | kill -9 "$PID" 13 | else 14 | echo "No running process found." 15 | fi 16 | 17 | # Restart the script 18 | echo "Starting the script..." 19 | 20 | # get address of current script file (which is repository directory) 21 | SCRIPT_DIR=$(dirname "$(realpath "$0")") 22 | 23 | # Navigate to the script's directory 24 | cd "$SCRIPT_DIR" 25 | 26 | # Activate the virtual environment 27 | source "$SCRIPT_DIR/venv/bin/activate" 28 | 29 | # Run the Python script using the virtual environment's Python interpreter 30 | nohup python3 "$SCRIPT_NAME" > /dev/null 2>&1 & 31 | 32 | echo "Script restarted successfully." 33 | -------------------------------------------------------------------------------- /archived_codes.py: -------------------------------------------------------------------------------- 1 | # download all posts of a specific profile 2 | ''' 3 | USERNAME = "therock" 4 | profile = Profile.from_username(L.context, USERNAME) 5 | for post in profile.get_posts(): 6 | L.download_post(post, target=profile.username) 7 | ''' 8 | def media_id_to_code(media_id): 9 | alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' 10 | short_code = '' 11 | while media_id > 0: 12 | remainder = media_id % 64 13 | media_id = (media_id-remainder)/64 14 | short_code = alphabet[remainder] + short_code 15 | return short_code 16 | 17 | 18 | def code_to_media_id(short_code): 19 | alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' 20 | media_id = 0 21 | for letter in short_code: 22 | media_id = (media_id*64) + alphabet.index(letter) 23 | 24 | return media_id 25 | 26 | print(code_to_media_id("C0KuSEuI_JU")) 27 | print(code_to_media_id("C0KuSEuI")) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Arash Nemat Zadeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /615_import_firefox_session.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | from glob import glob 3 | from os.path import expanduser 4 | from platform import system 5 | from sqlite3 import OperationalError, connect 6 | 7 | try: 8 | from instaloader import ConnectionException, Instaloader 9 | except ModuleNotFoundError: 10 | raise SystemExit("Instaloader not found.\n pip install [--user] instaloader") 11 | 12 | 13 | def get_cookiefile(): 14 | default_cookiefile = { 15 | "Windows": "~/AppData/Roaming/Mozilla/Firefox/Profiles/*/cookies.sqlite", 16 | "Darwin": "~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite", 17 | }.get(system(), "~/.mozilla/firefox/*/cookies.sqlite") 18 | cookiefiles = glob(expanduser(default_cookiefile)) 19 | if not cookiefiles: 20 | raise SystemExit("No Firefox cookies.sqlite file found. Use -c COOKIEFILE.") 21 | return cookiefiles[0] 22 | 23 | 24 | def import_session(cookiefile, sessionfile): 25 | print("Using cookies from {}.".format(cookiefile)) 26 | conn = connect(f"file:{cookiefile}?immutable=1", uri=True) 27 | try: 28 | cookie_data = conn.execute( 29 | "SELECT name, value FROM moz_cookies WHERE baseDomain='instagram.com'" 30 | ) 31 | except OperationalError: 32 | cookie_data = conn.execute( 33 | "SELECT name, value FROM moz_cookies WHERE host LIKE '%instagram.com'" 34 | ) 35 | instaloader = Instaloader(max_connection_attempts=1) 36 | instaloader.context._session.cookies.update(cookie_data) 37 | username = instaloader.test_login() 38 | if not username: 39 | raise SystemExit("Not logged in. Are you logged in successfully in Firefox?") 40 | print("Imported session cookie for {}.".format(username)) 41 | instaloader.context.username = username 42 | instaloader.save_session_to_file(sessionfile) 43 | 44 | 45 | if __name__ == "__main__": 46 | p = ArgumentParser() 47 | p.add_argument("-c", "--cookiefile") 48 | p.add_argument("-f", "--sessionfile") 49 | args = p.parse_args() 50 | try: 51 | import_session(args.cookiefile or get_cookiefile(), args.sessionfile) 52 | except (ConnectionException, OperationalError) as e: 53 | raise SystemExit("Cookie import failed: {}".format(e)) 54 | -------------------------------------------------------------------------------- /variables.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | import instaloader 5 | from instaloader import * 6 | 7 | import re 8 | import requests 9 | import traceback # to print error traceback 10 | import telebot 11 | 12 | import json 13 | import urllib.parse 14 | 15 | # we use dotenv to use os.getenv() instead of os.environ[] 16 | # and read from '.env' in current folder instead of '/etc/environment' 17 | # more guide: 18 | # https://dev.to/jakewitcher/using-env-files-for-environment-variables-in-python-applications-55a1 19 | load_dotenv() 20 | 21 | # env variables 22 | bot_token = os.getenv('BEST_INSTAGRAM_DOWNLOADER_BOT_API') 23 | log_channel_id = os.getenv('INSTAGRAM_DOWNLOADER_LOG_CHANNEL_ID') # set to False if not needed 24 | 25 | # initialize bot 26 | bot = telebot.TeleBot(bot_token) 27 | 28 | # settings 29 | bot_username = "@Best_Instagram_Downloader_Bot" 30 | caption_trail = "\n\n\n" + bot_username 31 | session_file_name = "session" # any name change should apply to .gitignore too 32 | 33 | # warp socks proxy 34 | warp_proxies = os.environ["WARP_PROXIES"] 35 | warp_proxies = json.loads(warp_proxies) 36 | 37 | # regex 38 | insta_post_or_reel_reg = r'(?:https?://www\.)?instagram\.com\S*?/(p|reel)/([a-zA-Z0-9_-]{11})/?' 39 | spotify_link_reg = r'(?:https?://)?open\.spotify\.com/(track|album|playlist|artist)/[a-zA-Z0-9]+' 40 | 41 | # messages 42 | start_msg = '''Hi😃👋 43 | Send an instagram link to download. 44 | 45 | It can be a post link like this: 46 | https://www.instagram.com/p/DFx\_jLuACs3 47 | 48 | Or it can be a reel link like this: 49 | https://www.instagram.com/reel/C59DWpvOpgF''' 50 | 51 | help_msg = '''This bot is open source and you are welcome to contribute and improve it. 52 | https://github.com/arashnm80/best-instagram-downloader 53 | 54 | You can give me energy by giving a star in github''' 55 | 56 | privacy_msg = '''This bot doesn't gather any info from the users. 57 | 58 | Also it's whole open source and available here: 59 | https://github.com/arashnm80/best-instagram-downloader''' 60 | 61 | end_msg = '''If you liked the bot you can support me by giving a star [here](https://github.com/arashnm80/best-instagram-downloader)⭐ (it's free) 62 | 63 | You can also check out my *Music Downloader* too: @SpotSeekBot''' 64 | 65 | fail_msg = '''Sorry, my process wasn't successful. 66 | But you can try again another time or with another link.''' 67 | 68 | wrong_pattern_msg = '''wrong pattern. 69 | You should send an instagram post or reel link.''' 70 | 71 | reel_msg = '''reel links are not supported at the moment. 72 | You can send post links instead. 73 | 74 | Motivate me to add support of reels and stories by subscribing to [my youtube](https://www.youtube.com/@Arashnm80)''' 75 | -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | from variables import * 2 | 3 | 4 | 5 | # # classes (for rate limiting - might delete them) 6 | 7 | # class MyCustomException(Exception): 8 | # def __init__(self, message="Custom rate limit exceeded"): 9 | # self.message = message 10 | # super().__init__(self.message) 11 | 12 | # class MyRateController(instaloader.RateController): 13 | # def sleep(self, secs): 14 | # raise MyCustomException("Custom rate limit exceeded") 15 | 16 | 17 | # functions 18 | 19 | def log(log_message): 20 | if not log_channel_id: 21 | return # set to False log channel means it is not needed 22 | 23 | log_bot_url = "https://api.telegram.org/bot" + bot_token + "/" 24 | 25 | log = requests.post(log_bot_url + "sendMessage", data={ 26 | "chat_id": log_channel_id, 27 | "text": log_message 28 | }) 29 | 30 | # Check if the log was sent successfully 31 | if log.status_code == 200: 32 | print('log registered') 33 | else: 34 | print('Error in registering log:', log.status_code) 35 | 36 | def try_to_delete_message(chat_id, message_id): 37 | try: 38 | bot.delete_message(chat_id, message_id) 39 | except: 40 | pass # ignore errors if user has already deleted the message 41 | 42 | # def get_ready_to_work_insta_instance(): 43 | # # # (for rate limiting - might delete it) 44 | # # L = instaloader.Instaloader(rate_controller=lambda ctx: MyRateController(ctx)) 45 | 46 | # # create instance 47 | # L = instaloader.Instaloader() 48 | 49 | # # prepare session 50 | # try: 51 | # L.load_session_from_file(USER, session_file_name) # gives error if file doesn't exist 52 | # print(L.test_login()) # gives error if session is expired 53 | # print("using old session") 54 | # except: 55 | # log("session failed") 56 | # print("session failed") 57 | # #### commented for debug 58 | # # if os.path.exists(session_file_name): 59 | # # os.remove(session_file_name) # delete older session if it file exists 60 | # # L.login(USER, PASS) 61 | # # log("new login with instagram bot") 62 | # # L.save_session_to_file(session_file_name) 63 | # # print("save to a new session") 64 | # #### 65 | 66 | 67 | # return L 68 | 69 | # def download_post_to_folder(post_shortcode, folder): 70 | # L = get_ready_to_work_insta_instance() 71 | # post = Post.from_shortcode(L.context, post_shortcode) 72 | # L.download_post(post, target=folder) 73 | 74 | def get_post_or_reel_shortcode_from_link(link): 75 | match = re.search(insta_post_or_reel_reg, link) 76 | if match: 77 | return match.group(2) 78 | else: 79 | return False 80 | -------------------------------------------------------------------------------- /best_instagram_downloader.py: -------------------------------------------------------------------------------- 1 | from functions import * 2 | from riad_azz import get_instagram_media_links 3 | 4 | @bot.message_handler(commands=['start']) 5 | def start_command_handler(message): 6 | bot.send_message(message.chat.id, start_msg, parse_mode="Markdown", disable_web_page_preview=True) 7 | log(f"{bot_username} log:\n\nuser: {message.chat.id}\n\nstart command") 8 | 9 | @bot.message_handler(commands=['help']) 10 | def help_command_handler(message): 11 | bot.send_message(message.chat.id, help_msg, parse_mode="Markdown", disable_web_page_preview=True) 12 | log(f"{bot_username} log:\n\nuser: {message.chat.id}\n\nhelp command") 13 | 14 | @bot.message_handler(commands = ['privacy']) 15 | def privacy_message_handler(message): 16 | bot.send_message(message.chat.id, privacy_msg, parse_mode="Markdown", disable_web_page_preview=True) 17 | log(f"{bot_username} log:\n\nuser: {message.chat.id}\n\nprivacy command") 18 | 19 | @bot.message_handler(regexp = spotify_link_reg) 20 | def spotify_link_handler(message): 21 | bot.send_message(message.chat.id, "This bot only supports Instagram links. Please send an Instagram post or reel link.\n\nIf you want to download from Spotify you can check out my other bot: @SpotSeekBot") 22 | 23 | @bot.message_handler(regexp = insta_post_or_reel_reg) 24 | def post_or_reel_link_handler(message): 25 | try: 26 | log(f"{bot_username} log:\n\nuser:\n{message.chat.id}\n\n✅ message text:\n{message.text}") 27 | guide_msg_1 = bot.send_message(message.chat.id, "Ok wait a few moments...") 28 | post_shortcode = get_post_or_reel_shortcode_from_link(message.text) 29 | print(post_shortcode) 30 | 31 | if not post_shortcode: 32 | log(f"{bot_username} log:\n\nuser: {message.chat.id}\n\n🛑 error in getting post_shortcode") 33 | return # post shortcode not found 34 | 35 | media_links, caption = get_instagram_media_links(post_shortcode) 36 | 37 | # todo: fix later if possible and don't let it to happen in the first place 38 | # if they are both empty and the riad_azz returned this error: 39 | # "Error extracting media info: 'NoneType' object has no attribute 'get'" 40 | if (not media_links) and (not caption): 41 | raise Exception("riad_azz returned nothing") 42 | 43 | # # debug 44 | # print(media_links) 45 | # media_links = media_links[:9] 46 | # print(media_links[-1]) 47 | 48 | # caption handling 49 | if caption is None: 50 | caption = '' 51 | while len(caption) + len(caption_trail) > 1024: 52 | caption = caption[:-1] 53 | caption = caption + caption_trail 54 | 55 | media_list = [] 56 | for idx, item in enumerate(media_links): 57 | if item['type'] == 'video': 58 | if idx == 0: 59 | media = telebot.types.InputMediaVideo(item['url'], caption=caption) 60 | else: 61 | media = telebot.types.InputMediaVideo(item['url']) 62 | else: 63 | if idx == 0: 64 | media = telebot.types.InputMediaPhoto(item['url'], caption=caption) 65 | else: 66 | media = telebot.types.InputMediaPhoto(item['url']) 67 | media_list.append(media) 68 | 69 | def chunk_list(lst, n): 70 | for i in range(0, len(lst), n): 71 | yield lst[i:i + n] 72 | 73 | if len(media_list) == 1: 74 | media = media_list[0] 75 | if isinstance(media, telebot.types.InputMediaPhoto): 76 | bot.send_photo(message.chat.id, media.media, caption=media.caption) 77 | else: 78 | bot.send_video(message.chat.id, media.media, caption=media.caption) 79 | else: 80 | for chunk in chunk_list(media_list, 10): 81 | print(chunk) 82 | bot.send_media_group(message.chat.id, chunk) 83 | bot.send_message(message.chat.id, end_msg, parse_mode="Markdown", disable_web_page_preview=True) 84 | try_to_delete_message(message.chat.id, guide_msg_1.message_id) 85 | return 86 | except Exception as e: 87 | try: 88 | try_to_delete_message(message.chat.id, guide_msg_1.message_id) 89 | except: 90 | pass 91 | log(f"{bot_username} log:\n\nuser: {message.chat.id}\n\n🛑 error in main body: {str(e)}") 92 | bot.send_message(message.chat.id, fail_msg, parse_mode="Markdown", disable_web_page_preview=True) 93 | # import traceback 94 | # traceback.print_exc() # print error traceback 95 | 96 | @bot.message_handler(func=lambda message: True) 97 | def wrong_pattern_handler(message): 98 | log(f"{bot_username} log:\n\nuser: {message.chat.id}\n\n❌wrong pattern: {message.text}") 99 | bot.send_message(message.chat.id, wrong_pattern_msg, parse_mode="Markdown", disable_web_page_preview=True) 100 | 101 | bot.infinity_polling() 102 | 103 | -------------------------------------------------------------------------------- /riad_azz.py: -------------------------------------------------------------------------------- 1 | from variables import * 2 | 3 | def generate_request_body(shortcode): 4 | return urllib.parse.urlencode({ 5 | 'av': '0', 6 | '__d': 'www', 7 | '__user': '0', 8 | '__a': '1', 9 | '__req': 'b', 10 | '__hs': '20183.HYP:instagram_web_pkg.2.1...0', 11 | 'dpr': '3', 12 | '__ccg': 'GOOD', 13 | '__rev': '1021613311', 14 | '__s': 'hm5eih:ztapmw:x0losd', 15 | '__hsi': '7489787314313612244', 16 | '__dyn': '7xeUjG1mxu1syUbFp41twpUnwgU7SbzEdF8aUco2qwJw5ux609vCwjE1EE2Cw8G11wBz81s8hwGxu786a3a1YwBgao6C0Mo2swtUd8-U2zxe2GewGw9a361qw8Xxm16wa-0oa2-azo7u3C2u2J0bS1LwTwKG1pg2fwxyo6O1FwlA3a3zhA6bwIxe6V8aUuwm8jwhU3cyVrDyo', 17 | '__csr': 'goMJ6MT9Z48KVkIBBvRfqKOkinBtG-FfLaRgG-lZ9Qji9XGexh7VozjHRKq5J6KVqjQdGl2pAFmvK5GWGXyk8h9GA-m6V5yF4UWagnJzazAbZ5osXuFkVeGCHG8GF4l5yp9oOezpo88PAlZ1Pxa5bxGQ7o9VrFbg-8wwxp1G2acxacGVQ00jyoE0ijonyXwfwEnwWwkA2m0dLw3tE1I80hCg8UeU4Ohox0clAhAtsM0iCA9wap4DwhS1fxW0fLhpRB51m13xC3e0h2t2H801HQw1bu02j-', 18 | '__comet_req': '7', 19 | 'lsd': 'AVrqPT0gJDo', 20 | 'jazoest': '2946', 21 | '__spin_r': '1021613311', 22 | '__spin_b': 'trunk', 23 | '__spin_t': '1743852001', 24 | '__crn': 'comet.igweb.PolarisPostRoute', 25 | 'fb_api_caller_class': 'RelayModern', 26 | 'fb_api_req_friendly_name': 'PolarisPostActionLoadPostQueryQuery', 27 | 'variables': json.dumps({ 28 | 'shortcode': shortcode, 29 | 'fetch_tagged_user_count': None, 30 | 'hoisted_comment_id': None, 31 | 'hoisted_reply_id': None, 32 | }), 33 | 'server_timestamps': 'true', 34 | 'doc_id': '8845758582119845', 35 | }) 36 | 37 | def get_instagram_media_links(shortcode): 38 | url = "https://www.instagram.com/graphql/query" 39 | headers = { 40 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', 41 | 'Accept': '*/*', 42 | 'Accept-Language': 'en-US,en;q=0.5', 43 | 'Content-Type': 'application/x-www-form-urlencoded', 44 | 'X-FB-Friendly-Name': 'PolarisPostActionLoadPostQueryQuery', 45 | 'X-BLOKS-VERSION-ID': '0d99de0d13662a50e0958bcb112dd651f70dea02e1859073ab25f8f2a477de96', 46 | 'X-CSRFToken': 'uy8OpI1kndx4oUHjlHaUfu', 47 | 'X-IG-App-ID': '1217981644879628', 48 | 'X-FB-LSD': 'AVrqPT0gJDo', 49 | 'X-ASBD-ID': '359341', 50 | 'Sec-GPC': '1', 51 | 'Sec-Fetch-Dest': 'empty', 52 | 'Sec-Fetch-Mode': 'cors', 53 | 'Sec-Fetch-Site': 'same-origin', 54 | 'Pragma': 'no-cache', 55 | 'Cache-Control': 'no-cache', 56 | 'Referer': f'https://www.instagram.com/p/{shortcode}/', 57 | } 58 | data = generate_request_body(shortcode) 59 | response = requests.post(url, headers=headers, data=data, proxies=warp_proxies) 60 | response.raise_for_status() 61 | json_response = response.json() 62 | 63 | # debug - to view json view pretty 64 | with open(f"instagram_response.json", "w") as f: 65 | json.dump(json_response, f, indent=2) 66 | 67 | media_links = [] 68 | caption = None 69 | try: 70 | media = json_response['data']['xdt_shortcode_media'] 71 | # caption 72 | caption = media.get('edge_media_to_caption', {}).get('edges', [{}])[0].get('node', {}).get('text', '') 73 | # Check if it's a sidecar (multiple media) 74 | if media.get('__typename') == 'XDTGraphSidecar' and 'edge_sidecar_to_children' in media: 75 | edges = media['edge_sidecar_to_children']['edges'] 76 | for edge in edges: 77 | node = edge['node'] 78 | media_type = 'video' if node.get('is_video', False) else 'image' 79 | if media_type == 'video': 80 | url = node.get('video_url') 81 | else: 82 | display_resources = node.get('display_resources', []) 83 | if display_resources: 84 | url = display_resources[-1]['src'] 85 | else: 86 | url = node.get('display_url') 87 | media_links.append({'type': media_type, 'url': url}) 88 | else: 89 | media_type = 'video' if media.get('is_video', False) else 'image' 90 | if media_type == 'video': 91 | url = media.get('video_url') 92 | else: 93 | display_resources = media.get('display_resources', []) 94 | if display_resources: 95 | url = display_resources[-1]['src'] 96 | else: 97 | url = media.get('display_url') 98 | media_links.append({'type': media_type, 'url': url}) 99 | except Exception as e: 100 | print(f"Error extracting media info: {e}") 101 | # return "error", f"{e}" 102 | return media_links, caption 103 | 104 | # # Example usage: 105 | # # shortcode = "DJx51PyxMpy" # rock post: multiple videos and images 106 | # shortcode = "DMLLAxNsWFL" # zelatan: one video (opens in my browser but erros for telegram servers) 107 | # links, caption = get_instagram_media_links(shortcode) 108 | # print(links, caption) --------------------------------------------------------------------------------