Documentation Index
Fetch the complete documentation index at: https://mintlify.com/karilaa-dev/tt-bot/llms.txt
Use this file to discover all available pages before exploring further.
TT-Bot supports downloading Instagram content (videos, images, and carousels) using the RapidAPI Instagram Downloader service.
Supported content
The bot can download from:
- Posts - Single images or videos
- Reels - Short-form videos
- Carousels - Multiple images/videos in a single post
- TV videos - IGTV content
URL patterns
Supported Instagram URL formats:
# From instagram_api/client.py:18
INSTAGRAM_URL_REGEX = re.compile(
r"https?://(?:www\.)?instagram\.com/(?:p|reels?|reel|tv|stories)/[\w-]+",
re.IGNORECASE,
)
Examples:
https://www.instagram.com/p/ABC123/
https://www.instagram.com/reel/XYZ789/
https://instagram.com/tv/DEF456/
RapidAPI integration
The bot uses the Instagram Downloader API from RapidAPI:
# From instagram_api/client.py:28
class InstagramClient:
async def get_media(self, url: str) -> InstagramMediaInfo:
session = _get_http_session()
api_key = config["instagram"]["rapidapi_key"]
headers = {
"X-Rapidapi-Key": api_key,
"X-Rapidapi-Host": _RAPIDAPI_HOST,
}
api_url = f"https://{_RAPIDAPI_HOST}/convert"
API host
_RAPIDAPI_HOST = (
"instagram-downloader-download-instagram-stories-videos4.p.rapidapi.com"
)
Configuration
Set your RapidAPI key in .env:
RAPIDAPI_KEY=your_api_key_here
The key is loaded from config:
api_key = config["instagram"]["rapidapi_key"]
Usage examples
Download Instagram video
Send an Instagram URL to the bot:
https://www.instagram.com/reel/ABC123/
The bot:
- Detects Instagram URL pattern
- Calls RapidAPI to extract media info
- Downloads video and thumbnail in parallel
- Sends video back to user
# From handlers/instagram.py:84
# Download video and thumbnail concurrently
thumb_coro = _download_url(media_info.thumbnail_url) if media_info.thumbnail_url else None
if thumb_coro:
video_bytes, thumb_bytes = await asyncio.gather(
_download_url(video_url), thumb_coro
)
else:
video_bytes = await _download_url(video_url)
thumb_bytes = None
Download Instagram carousel
Carousels with multiple images/videos are handled automatically:
# From handlers/instagram.py:136
# Collect all media URLs (images and videos in carousels)
media_items = media_info.media
if image_limit:
media_items = media_items[:image_limit]
# Download all media in parallel
download_tasks = [_download_url(item.url) for item in media_items]
all_bytes = await asyncio.gather(*download_tasks)
- Downloads all items in parallel
- Converts HEIC/non-native images to PNG
- Sends in batches of 10 (Telegram limit)
- Group chats limited to 10 items
File mode vs. photo mode
Users can toggle between sending as files or compressed photos:
# From handlers/instagram.py:109
if file_mode:
await bot.send_document(
chat_id=message.chat.id,
document=BufferedInputFile(video_bytes, "instagram_video.mp4"),
thumbnail=thumb,
caption=caption,
reply_to_message_id=message.message_id,
disable_content_type_detection=True,
)
else:
await bot.send_video(
chat_id=message.chat.id,
video=BufferedInputFile(video_bytes, "instagram_video.mp4"),
thumbnail=thumb,
caption=caption,
reply_to_message_id=message.message_id,
supports_streaming=True,
)
Error handling
The bot handles various Instagram API errors:
404 - Not found
# From instagram_api/client.py:43
if response.status == 404:
raise InstagramNotFoundError("Post not found or private")
Shown when:
- Post was deleted
- Post is private
- Invalid URL
429 - Rate limit
if response.status == 429:
raise InstagramRateLimitError("API rate limit exceeded")
RapidAPI has usage limits based on your subscription tier.
Network errors
if response.status != 200:
text = await response.text()
logger.error(
f"Instagram API error {response.status}: {text}"
)
raise InstagramNetworkError(
f"API returned status {response.status}"
)
Image processing
The bot automatically converts non-native image formats:
# From handlers/instagram.py:160
async def maybe_convert(item, img_bytes):
if item.type != "image":
return img_bytes
ext = detect_image_format(img_bytes)
if ext not in _NATIVE_EXTENSIONS:
try:
return await loop.run_in_executor(
executor, convert_image_to_png, img_bytes
)
except Exception as e:
logger.error(f"Failed to convert image: {e}")
return img_bytes
_NATIVE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
HEIC and other formats are converted to PNG for Telegram compatibility.
Inline mode support
Instagram content works in inline mode for sharing in any chat:
# From handlers/instagram.py:225
async def send_instagram_inline(
inline_message_id: str,
instagram_url: str,
lang: str,
user_id: int,
username: str | None,
full_name: str | None,
) -> None:
"""Download Instagram media and send it as an inline message edit."""
client = InstagramClient()
media_info = await client.get_media(instagram_url)
if media_info.is_video:
await _send_instagram_inline_video(
inline_message_id, media_info, lang, user_id, username, full_name
)
else:
await _send_instagram_inline_image(
inline_message_id, media_info, lang, user_id, username, full_name
)
Storage channel requirement
Inline mode requires uploading to a storage channel first to get file IDs:
# From handlers/instagram.py:291
storage_msg = await bot.send_video(
chat_id=STORAGE_CHANNEL_ID,
video=BufferedInputFile(video_bytes, "instagram_video.mp4"),
caption=_build_storage_caption(
media_info.link, user_id, username, full_name
),
parse_mode="HTML",
disable_notification=True,
thumbnail=thumb_file,
supports_streaming=True,
)
file_id = storage_msg.video.file_id if storage_msg.video else None
Set STORAGE_CHANNEL_ID in .env to enable inline mode.
The RapidAPI response includes:
# From instagram_api/client.py:74
media_items = []
for item in data.get("media", []):
media_items.append(
InstagramMediaItem(
type=item.get("type", "image"),
url=item["url"],
thumbnail=item.get("thumbnail"),
quality=item.get("quality"),
)
)
Each media item contains:
type - “image” or “video”
url - Direct download URL
thumbnail - Thumbnail URL (for videos)
quality - Media quality indicator
Parallel downloads
All carousel items download in parallel:
# From handlers/instagram.py:142
download_tasks = [_download_url(item.url) for item in media_items]
all_bytes = await asyncio.gather(*download_tasks)
Image conversion
Image conversion runs in thread pool to avoid blocking:
# From handlers/instagram.py:165
return await loop.run_in_executor(
executor, convert_image_to_png, img_bytes
)
Batched sending
Large carousels are split into batches of 10:
# From handlers/instagram.py:183
# Split into batches of 10
batches = [
items_with_bytes[i : i + 10]
for i in range(0, len(items_with_bytes), 10)
]
Database logging
All Instagram downloads are logged to the database:
# From handlers/instagram.py:62
is_images = not media_info.is_video
try:
await add_video(message.chat.id, instagram_url, is_images)
logger.info(
f"Instagram Download: CHAT {message.chat.id} - URL {instagram_url}"
)
except Exception as e:
logger.error(f"Can't write into database: {e}")
The is_images flag distinguishes between video and image posts for statistics.