بازگشت به وبلاگ
 رفع خطای ارسال فایل در ربات بله با aiogram | failed to get HTTP URL content

رفع خطای ارسال فایل در ربات بله با aiogram | failed to get HTTP URL content

۱۴۰۵/۶/۶admin

اگر با کتابخانه‌ی aiogram در پایتون یک ربات برای پیام‌رسان بله (Bale) ساخته‌اید و هنگام ارسال عکس، ویدیو یا فایل با خطای زیر مواجه شده‌اید، این مقاله دقیقاً برای شماست:

aiogram.exceptions.TelegramBadRequest: Telegram server says - Bad Request: failed to get HTTP URL content

این خطا معمولاً وقتی رخ می‌دهد که یک ربات پل ارتباطی (Bridge Bot) بین تلگرام و بله ساخته باشید و بخواهید فایل‌ها را بین دو پلتفرم منتقل کنید. نکته‌ی جالب این‌جاست که این خطا:

  • مستقل از حجم فایل رخ می‌دهد (چه فایل ۲۰۰ کیلوبایتی، چه ۵ مگابایتی).
  • مستقل از نوع فایل رخ می‌دهد (عکس، سند، ویدیو، همه دچار مشکل می‌شوند).
  • بلافاصله و بدون تأخیر شبکه‌ای رخ می‌دهد.

در ادامه، مسیر دقیق دیباگ کردن این مشکل، علت واقعی آن در سطح کد کتابخانه‌ی aiogram، و راه‌حل نهایی را قدم‌به‌قدم بررسی می‌کنیم.

خطا از کجا می‌آید؟ تلگرام یا بله؟

اولین نکته‌ی مهم این است که این پیام خطا، متن استاندارد Telegram Bot API است و بسیاری از کلون‌ها و پیاده‌سازی‌های سازگار با آن (از جمله API بله در آدرس tapi.bale.ai) از همین متن خطا برای موقعیت مشابه استفاده می‌کنند. در لاگ‌های ربات، باید دقت کنید که این خطا واقعاً از سمت کدام بات (تلگرام یا بله) برگردانده شده است.

با بررسی لاگ‌ها مشخص شد که خطا دقیقاً هنگام ارسال فایل از تلگرام به سمت بله (target_bot = bale_bot) رخ می‌دهد، در حالی که:

  • ارسال پیام متنی بین دو پلتفرم مشکلی نداشت.
  • ارسال فایل از بله به تلگرام هم بدون مشکل کار می‌کرد.

این یعنی مشکل، مختص ارسال فایل به API بله بود.

رد کردن فرضیه‌های اشتباه

پیش از رسیدن به علت اصلی، چند فرضیه‌ی رایج را بررسی و رد کردیم؛ این مرحله برای هرکسی که با خطای مشابه مواجه می‌شود مفید است:

فرضیه ۱: محدودیت حجم فایل

مستندات بله محدودیت آپلود مولتی‌پارت را تا ۵۰ مگابایت برای اسناد و ۱۰ مگابایت برای عکس اعلام کرده است. اما این خطا حتی برای یک عکس چند کیلوبایتی هم رخ می‌داد؛ پس موضوع حجم فایل نبود.

فرضیه ۲: توکن بیزینسی بله (Business API)

در مستندات بله آمده که نسخه‌ی Business API فقط از file_id پشتیبانی می‌کند و آپلود مولتی‌پارت را قبول نمی‌کند. برای رد یا تأیید این فرضیه، یک درخواست خام با curl مستقیماً به API بله زدیم:

curl -X POST "https://tapi.bale.ai/bot<TOKEN>/sendPhoto" \
  -F "chat_id=<CHAT_ID>" \
  -F "photo=@small_test.jpg"

نتیجه موفق بود و بله یک file_id معتبر برگرداند. این یعنی توکن ربات هیچ محدودیتی نداشت و مشکل هم به حساب کاربری/توکن ربات مربوط نمی‌شد.

با موفقیت curl، فرضیه‌ی محدودیت حساب کاملاً رد شد و مسیر بررسی به سمت خودِ کتابخانه‌ی aiogram و نحوه‌ی ساخت درخواست HTTP توسط آن منحرف شد.

علت اصلی: مکانیزم attach:// در aiogram

برای پیدا کردن تفاوت دقیق بین درخواست موفق curl و درخواست ناموفق aiogram، یک سرور تستی محلی راه‌اندازی و بدنه‌ی خام (Raw Body) هر دو درخواست با هم مقایسه شد.

درخواست curl (موفق)

Content-Disposition: form-data; name="photo"; filename="small_test.jpg"
Content-Type: image/jpeg

<بایت‌های واقعی تصویر>

فایل مستقیماً و به‌صورت باینری در همان فیلد photo قرار می‌گیرد؛ دقیقاً همان چیزی که مستندات بله انتظار دارد.

درخواست aiogram (ناموفق)

Content-Disposition: form-data; name="photo"

attach://VQ95G3bqKmXuMw

اینجا نکته‌ی کلیدی مشخص شد: aiogram مقدار فیلد photo را به‌صورت یک رشته‌ی متنی با فرمت attach://<شناسه> ارسال می‌کند، و بایت‌های واقعی فایل را در یک بخش جداگانه از بدنه‌ی مولتی‌پارت، با نام همان شناسه (نه با نام photo)، قرار می‌دهد.

این رفتار، ویژگی رسمی و مستند Telegram Bot API است که به شما اجازه می‌دهد یک فایل آپلودشده را در فیلدهای مختلف (مثل thumbnail یا آلبوم رسانه) دوباره ارجاع دهید. سرور رسمی تلگرام، این ارجاع attach:// را با نام فیلد بدنه تطبیق می‌دهد و فایل واقعی را پیدا می‌کند.

اما پیاده‌سازی API بله این مکانیزم ارجاعی را پشتیبانی نمی‌کند. وقتی بله مقدار attach://VQ95G3bqKmXuMw را در فیلد photo دریافت می‌کند، آن را نه file_id می‌شناسد و نه یک URL واقعی HTTP؛ در نتیجه تلاش می‌کند آن را به‌عنوان یک URL دانلود کند، شکست می‌خورد، و همان خطای معروف را برمی‌گرداند:

Bad Request: failed to get HTTP URL content

این دقیقاً توضیح می‌دهد چرا خطا مستقل از حجم و نوع فایل، و همیشه آنی رخ می‌داد؛ چون سرور بله اصلاً منتظر دریافت کامل بدنه‌ی فایل نمی‌ماند و روی همان مقدار متنی نامعتبر خطا می‌دهد.

با بررسی کد منبع aiogram (فایل client/session/aiohttp.py، متد build_form_data) مشخص شد که این رفتار برای همه‌ی متدهای ارسال فایل (sendPhoto, sendDocument, sendVideo و ...) به‌صورت پیش‌فرض و بدون قابلیت غیرفعال‌سازی از طریق تنظیمات فعال است.

راه‌حل: بازنویسی متد ساخت فرم مولتی‌پارت

راه‌حل، ساخت یک کلاس فرزند (Subclass) از AiohttpSession است که به‌جای استفاده از ارجاع attach://، بایت‌های فایل را مستقیماً زیر همان نام فیلد اصلی (مثل photo یا document) قرار می‌دهد؛ دقیقاً همان کاری که curl انجام می‌داد.

from typing import Any

from aiohttp import FormData
from aiogram import Bot
from aiogram.client.session.aiohttp import AiohttpSession
from aiogram.methods.base import TelegramMethod
from aiogram.types import InputFile


class DirectUploadAiohttpSession(AiohttpSession):
    """
    API بله مکانیزم ارجاعی attach:// را که aiogram به‌صورت پیش‌فرض
    برای ارسال فایل استفاده می‌کند، پشتیبانی نمی‌کند و در نتیجه خطای
    "failed to get HTTP URL content" برمی‌گرداند. این کلاس، بایت‌های
    فایل را مستقیماً زیر نام فیلد اصلی (مثل photo یا document) ارسال
    می‌کند.
    """

    def build_form_data(self, bot: Bot, method: TelegramMethod) -> FormData:
        form = FormData(quote_fields=False)
        files: dict[str, Any] = {}
        for key, value in method.model_dump(warnings=False).items():
            if isinstance(value, InputFile):
                form.add_field(key, value.read(bot), filename=value.filename or key)
                continue
            prepared = self.prepare_value(value, bot=bot, files=files)
            if not prepared:
                continue
            form.add_field(key, prepared)
        for key, value in files.items():
            form.add_field(key, value.read(bot), filename=value.filename or key)
        return form

سپس، فقط کافی است هنگام ساخت سشن ربات بله، از این کلاس به‌جای AiohttpSession استفاده کنید:

bale_session = DirectUploadAiohttpSession(api=bale_server)
bale_bot = Bot(token=BALE_TOKEN, session=bale_session)

نکته‌ی مهم: این تغییر را فقط برای سشن ربات بله اعمال کنید. سشن ربات تلگرام (tg_bot) نباید تغییر کند، چون API رسمی تلگرام مکانیزم attach:// را به‌درستی و بدون مشکل پشتیبانی می‌کند و نیازی به این وصله ندارد.

نتیجه‌گیری

خطای failed to get HTTP URL content در بسیاری از پیاده‌سازی‌های غیررسمی و سازگار با Telegram Bot API (مثل بله) به این دلیل رخ می‌دهد که این سرویس‌ها، مکانیزم ارجاعی attach:// را که در کتابخانه‌هایی مثل aiogram به‌صورت پیش‌فرض فعال است، پیاده‌سازی نکرده‌اند. با بازنویسی متد build_form_data در یک کلاس سشن اختصاصی، می‌توان فایل را دقیقاً همان‌طور که خودِ API بله انتظار دارد — یعنی به‌صورت مستقیم و بدون ارجاع — ارسال کرد.

اگر روی پروژه‌ای مشابه (اتصال ربات تلگرام به بله، ایتا، یا هر پلتفرم دیگر سازگار با Bot API) کار می‌کنید و با همین خطا مواجه شده‌اید، به احتمال زیاد همین راه‌حل مشکل شما را هم برطرف می‌کند.

پرسش‌های متداول (FAQ)

چرا فقط ارسال فایل به بله خطا می‌دهد ولی ارسال پیام متنی مشکلی ندارد؟ چون این باگ فقط مربوط به فیلدهایی از نوع InputFile است. پیام‌های متنی از این مکانیزم استفاده نمی‌کنند و مستقیماً به‌صورت رشته‌ی ساده ارسال می‌شوند.

آیا این مشکل مربوط به نسخه‌ی خاصی از aiogram است؟ خیر، این رفتار (ارسال فایل با ارجاع attach://) در هسته‌ی معماری aiogram 3.x برای همه‌ی نسخه‌ها یکسان است، چون این مکانیزم بخشی از استاندارد Telegram Bot API محسوب می‌شود.

آیا این تغییر روی ربات تلگرام هم لازم است؟ خیر. تلگرام رسمی به‌طور کامل از attach:// پشتیبانی می‌کند؛ این وصله فقط برای APIهایی مثل بله لازم است که این ویژگی را پیاده‌سازی نکرده‌اند.

نظرات

برای ارسال نظر باید وارد شوید.