python-3.xtelegramtelegram-botpython-telegram-bottelegram-api

Unable to Download Multiple Images from Telegram Media Group Using Async Function


This question is a follow up from this question asked earlier here

I need help in trying to modify this code section which I am currently using to download just 1 image.

async def handle_s_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    try:
        text = update.message.text or update.message.caption

        if text is None:
            logger.error("Received message with no text or caption.")
            return

        if not text.startswith('/s '):
            logger.error("Message does not start with /s command.")
            return

        # Prepare text for Twitter (second Twitter) and Telegram
        text_for_second_twitter = text[3:].strip()  # Remove /s prefix for second Twitter
        text_for_telegram = text[3:].strip()  # Remove /s prefix for Telegram

        # Check message length
        if len(text_for_second_twitter) > 279:
            over_limit = len(text_for_second_twitter) - 279
            await context.bot.send_message(
                chat_id=BOT_CHANNEL_ID,
                text=f"Message too long! Length: {len(text_for_second_twitter)} characters, Over limit by: {over_limit} characters."
            )
            logger.info(f"Message too long: Length {len(text_for_second_twitter)}, Over limit by: {over_limit}")
            return

        # Other settings
        chat_id = update.message.chat.id
        message_id = update.message.message_id
        reply_to_message_id = update.message.reply_to_message.message_id if is_reply(update) else None

        logger.debug(f"Processed text for second Twitter: {text_for_second_twitter}")
        logger.debug(f"Processed text for Telegram: {text_for_telegram}")

        media_path = None
        caption = None

        if update.message.photo:
            file_id = update.message.photo[-1].file_id
            file = await context.bot.get_file(file_id)
            media_path = f"photo_{message_id}.jpg"
            await file.download_to_drive(media_path)
            caption = update.message.caption
            logger.debug(f"Downloaded photo with ID: {file_id}, saved as: {media_path}")

            if caption and caption.startswith('/s '):
                caption = caption[3:].strip()
        else:
            logger.debug("No photo found in the message")

        tweet_client_v2 = client_V2_swing
        tweet_client_v1 = client_V1_swing

        if not is_valid_json_file(BOT_TO_SECOND_TWITTER_JSON):
            logger.error(f"Invalid JSON file: {BOT_TO_SECOND_TWITTER_JSON}")
            return

        data = load_tracking_data(BOT_TO_SECOND_TWITTER_JSON) or {}
        logger.debug(f"Loaded data: {data}")

        bot_to_public_data = load_tracking_data(BOT_TO_PUBLIC_JSON) or {}
        logger.debug(f"Loaded bot_to_public_data: {bot_to_public_data}")

        public_message_id = bot_to_public_data.get(str(reply_to_message_id)) if reply_to_message_id else None
        logger.debug(f"Public message ID: {public_message_id}")

        tweet_id = None

        try:
            if media_path:
                tweet_id = post_to_twitter(
                    tweet_client_v2, tweet_client_v1,
                    text=text_for_second_twitter,  # Post without /s prefix
                    media_path=media_path,
                    in_reply_to_tweet_id=data.get(str(reply_to_message_id), {}).get('tweet_id') if reply_to_message_id else None
                )
                logger.debug(f"Tweet ID after posting: {tweet_id}")

                with open(media_path, 'rb') as photo_file:
                    public_msg = await context.bot.send_photo(
                        chat_id=PUBLIC_CHANNEL_ID,
                        photo=photo_file,
                        caption=text_for_telegram,  # Ensure caption is used correctly
                        reply_to_message_id=public_message_id
                    )
                logger.info(f"Telegram: Posted photo with message ID {public_msg.message_id}")

                os.remove(media_path)
            else:
                tweet_id = post_to_twitter(
                    tweet_client_v2, tweet_client_v1,
                    text=text_for_second_twitter,  # Post without /s prefix
                    in_reply_to_tweet_id=data.get(str(reply_to_message_id), {}).get('tweet_id') if reply_to_message_id else None
                )
                logger.debug(f"Tweet ID after posting: {tweet_id}")

                public_msg = await context.bot.send_message(
                    chat_id=PUBLIC_CHANNEL_ID,
                    text=text_for_telegram,
                    reply_to_message_id=public_message_id
                )
                logger.info(f"Telegram: Posted message with ID {public_msg.message_id}")

            bot_to_public_data[str(message_id)] = public_msg.message_id
            save_tracking_data(BOT_TO_PUBLIC_JSON, bot_to_public_data)
            logger.info(f"Updated bot_to_public.json with bot message ID {message_id} and public message ID {public_msg.message_id}")

            data[str(message_id)] = {
                'tweet_id': tweet_id,
                'reply_to_message_id': reply_to_message_id
            }
            save_tracking_data(BOT_TO_SECOND_TWITTER_JSON, data)
            logger.info(f"Updated JSON with message ID {message_id} and tweet ID {tweet_id}")

        except Exception as e:
            logger.error(f"Error during processing: {e}", exc_info=True)

    except Exception as e:
    logger.error(f"Handling /s command error: {e}", exc_info=True)

I used this logic from the answer to an earlier post.

To do this:

group_id = None
urls = []

async def download_all_photos(context: ContextTypes.DEFAULT_TYPE):
    media_paths = []
    
    for download_url, file_name, file_id in urls:
        try:
            # Log the full download URL
            full_download_url = f"{download_url}"
            logger.info(f'Downloading photo from URL: {full_download_url}')
    
            # Download the photo using the download URL
            response = requests.get(full_download_url)
            response.raise_for_status()  # Check if the download was successful
            with open(file_name, 'wb') as f:
                f.write(response.content)
            media_paths.append(file_name)
            logger.info(f'Downloaded photo successfully with file_id: {file_id}')
        except Exception as e:
            logger.error(f'Error downloading photo with file_id {file_id}: {e}')

    logger.info(f'All downloaded media paths: {media_paths}')
    return media_paths

async def handle_s_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    global group_id, urls
    
    try:
        text = update.message.text or update.message.caption

        if text is None or not text.startswith('/s '):
            return

        # Prepare text for Twitter (second Twitter)
        text_for_second_twitter = text[3:].strip()  # Remove /s prefix

        # Check message length
        if len(text_for_second_twitter) > 279:
            over_limit = len(text_for_second_twitter) - 279
            await context.bot.send_message(
                chat_id=BOT_CHANNEL_ID,
                text=f"Message too long! Length: {len(text_for_second_twitter)} characters, Over limit by: {over_limit} characters."
            )
            return

        media_path = None
        caption = None

        if update.message.photo:
            file_id = update.message.photo[-1].file_id
            file_info = await context.bot.get_file(file_id)
            download_url = file_info.file_path
            file_name = f'photo_{update.message.message_id}_{file_id}.jpg'

            urls.append([download_url, file_name, file_id])

            if group_id is None and update.message.media_group_id:
                group_id = update.message.media_group_id
            
            # Wait to see if there are more photos
            await asyncio.sleep(2)
            
            if update.message.media_group_id != group_id:
                media_paths = download_all_photos(context)
                group_id = None
                urls.clear()
                
                # Upload all media to Twitter
                media_ids = []
                for path in media_paths:
                    media = client_V1_swing.media_upload(filename=path)
                    media_ids.append(media.media_id)

                # Post tweet with multiple images
                response = client_V2_swing.create_tweet(
                    text=text_for_second_twitter,
                    media_ids=media_ids
                )
                
                logger.info(f"Tweet posted: {response.data['id']}")
                tweet_id = response.data['id']

                # Optionally, send to Telegram as well
                with open(media_paths[0], 'rb') as photo_file:
                    public_msg = await context.bot.send_photo(
                        chat_id=PUBLIC_CHANNEL_ID,
                        photo=photo_file,
                        caption=text_for_second_twitter
                    )
                logger.info(f"Telegram: Posted photo with message ID {public_msg.message_id}")
                
                # Cleanup downloaded files
                for path in media_paths:
                    os.remove(path)

    except Exception as e:
        logger.error(f"Handling /s command error: {e}", exc_info=True)

I'm still having trouble downloading the images as the logic is not right and would greatly appreciate any guidance or suggestions you could offer to help me download multiple images successfully.


Solution

  • First. Please change:

            if update.message.media_group_id != group_id:
                media_paths = download_all_photos(context)
    

    To:

            if update.message.media_group_id != group_id:
                media_paths = await download_all_photos(context)
    

    Looking closer. I don't get this code:

            if group_id is None and update.message.media_group_id:
                group_id = update.message.media_group_id
            
            # Wait to see if there are more photos
            await asyncio.sleep(2)
            
            if update.message.media_group_id != group_id:
                media_paths = await download_all_photos(context)
                group_id = None
    

    Could you just skip it, and just call download? I mean - update.message is not changing in this code, so why to wait, and... if so - group_id is same as ...media_group_id so download_all_photos is not triggered.