آموزش ساخت ربات تلگرام با زبان پایتون

آموزش ساخت ربات تلگرام با زبان پایتون

فهرست مطالب

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

ربات تلگرام را چگونه می‌سازند؟

ساختن ربات تلگرام با کمک زبان برنامه نویسی پایتون امکان پذیر است. پایتون با کتابخانه‌هایی که در زمینه‌ی ساخت ربات تلگرام دارد، به شما این امکان را می‌دهد که بتوانید به راحتی یک ربات تلگرام بسازید. کتابخانه‌ها در پایتون دارای یک سری ماژول و کلاس از پیش نوشته شده هستند و هر کدام برای برآورده کردن نیازی مشخص استفاده می‌شوند.

پیش‌نیازهای ساخت یک ربات تلگرامی

همان‌طور که قبل‌تر گفتیم، برای ساخت ربات از زبان پایتون استفاده خواهیم کرد. بنابراین داشتن تسلط یا حداقل دانش کلی در پایتون برای همراه بودن با ما در این مسیر نیاز است. واضح است که به یک اکانت تلگرام برای ایجاد ربات و استفاده کردن از آن نیاز داریم.
در کنار این‌ها، نصب یک محیط کدنویسی مخصوص پایتون برای نوشتن کدهای ربات و اجرا گرفتن از آن نیز از الزامات کار است. ما در این آموزش از ویرایشگر کد Visual Studio Code یا به اختصار VSCode استفاده خواهیم کرد. جهت دانلود و نصب Visual Studio Code، به سایت رسمی آن مراجعه کنید.

اگر فایل پایتون روی سیستم شما نصب نیست، از طریق این لینک اقدام به نصب آن کنید. لازم به ذکر است که ما از ورژن 3 پایتون برای نوشتن کدهای پروژه استفاده خواهیم کرد.

شروع ساخت یک ربات تلگرامی

در این مقاله‌ی آموزشی، مراحل ساخت یک ربات گفتگوی معمولی با امکان مکالمه‌ی متنی، دریافت عکس و لوکیشن را پیش خواهیم برد و جزئیاتی که در ساخت یک ربات وجود دارد را بررسی خواهیم کرد. در مراحل مختلف هر چیزی را که نیاز باشد توضیح خواهیم داد.

قدم اول؛ بات فادر

قبل از هر چیز لازم است یک ربات به اصطلاح خام برای خودمان در تلگرام ایجاد کنیم. توجه کنید این ربات فعلاً هیچ عملیاتی را انجام نخواهد داد. برای این کار لازم است مراحل زیر را انجام دهید:

1. وارد ربات ‌بات فادر به آدرس BotFather@ شوید و ربات را Start کنید.

ربات بات فادر

2. پس از استارت ربات، پیامی برای شما ارسال می‌شود. در بین گزینه‌های آن پیام، گزینه‌ی newbot/ را بیابید و روی آن کلیک کنید.

کلیک روی newbot

3. در این مرحله یک نام برای رباتتان انتخاب کنید و آن را تایپ و ارسال کنید.

انتخاب نام ربات

4. اکنون یک نام کاربری که قبلاً از آن در تلگرام استفاده نشده انتخاب کنید و برای بات فادر بفرستید. دقت کنید در پایان نامی که انتخاب می‌کنید باید عبارت bot قرار دهید.

انتخاب نام کاربری

5. تبریک! ربات شما ایجاد شد. در این مرحله، پیامی حاوی یک توکن برای شما ارسال می‌شود. این توکن منحصراً برای ربات شماست و از آن برای اتصال کدها به ربات استفاده خواهید کرد. آن را در جایی امن ذخیره کنید و در اختیار فرد دیگری قرار ندهید.

توکن ربات

در اینجا کار ما با بات فادر به اتمام می‌رسد.

قدم دوم؛ فراهم کردن محیط کدنویسی پایتون

قبل‌تر گفتیم که از کد ادیتور VSCODE برای نوشتن کدهای پایتون استفاده می‌کنیم. این برنامه را باز می‌کنیم و یک فولدر برای ربات می‌سازیم. بعد از آن، مانند تصویر زیر یک فایل با نام Mybot.py می‌سازیم تا کدهای پایتون را در آن بنویسیم. یک فایل به اسم myTOKEN.txt نیز می‌سازیم و توکن رباتمان را آن‌جا ذخیره می‌کنیم تا در پایان راه اندازی، آن را به کد اضافه کنیم.

فراهم کردن محیط کدنویسی پایتون

قدم سوم؛ نصب کتابخانه‌های مورد نیاز

برخی کتابخانه‌های پایتون از قبل به همراه فایل اصلی پایتون نصب شده‌اند. اما تعداد زیادی از کتابخانه‌ها نیاز به نصب به صورت دستی دارند. در این پروژه، از کتابخانه‌ی python-telegram-bot استفاده می‌کنیم. این کتابخانه از جمله کتابخانه‌هایی است که نیاز به نصب دارد. برای نصب آن کافی است دستور زیر را در cmd وارد کنید:

				
					pip install python-telegram-bot
				
			

پس از نصب آن، محیط cmd را ببندید و دوباره وارد محیط VSCODE بشوید.

قدم چهارم؛ ایمپورت کردن پکیج‌ها و کلاس‌ها

ابتدا باید ماژول‌های مورد نیاز را ایمپورت کنیم:

				
					import logging
				
			

این ماژول برای ایجاد log استفاده می‌شود و برای کنترل کردن اتفاقات و مشکلات در برنامه شماست.

				
					from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import Application, CommandHandler, ContextTypes, ConversationHandler, MessageHandler, filters

				
			
  • ReplyKeyboardMarkup: یک صفحه کلید شخصی سازی شده با قابلیت ریپلای می‌سازد.
  • ReplyKeyboardRemove: پس از انجام عملیات، کلیدهای customize شده را حذف می‌کند.
  • Update: ورودی‌های ربات را به روزرسانی می‌کند.
  • Application: انواع به روزرسانی‌ها را به Handlerها ارسال می‌کند و نقطه‌ی ورود به Python Telegram Bot است.
  • CommandHandler: کامندها در تلگرام به همراه یک “/” نمایش داده می‌شوند. CommandHandler این کامندها را مدیریت می‌کند.
  • ContextTypes: نوع داده‌ای آرگومان‌ها را تعیین می‌کند.
  • ConversationHandler: همان‌طور که از نامش پیداست، مکالمه‌ها را مدیریت می‌کند.
  • MessageHandler: یک کلاس برای مدیریت کردن پیام‌های ارسالی در ربات تلگرام است. این پیام‌‌ها می‌توانند متن، مدیا یا آپدیت وضعیت باشند.
  • Filters: نوع ورودی را محدود یا فیلتر می‌کند. مثلاً برای ارسال موقعیت مکانی یا لوکیشن، با اعمال فیلتر، ربات چیزی جز لوکیشن را دریافت نخواهد کرد.

قدم پنجم؛ شروع کد زنی

استارت اصلی کار ما از این مرحله به بعد است.

				
					logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
				
			

در اینجا با کمک ماژول logging و تابع basicConfig کانفیگ یا پیکربندی برای ورود انجام می‌شود. برای عدم ایجاد مشکل، تابع basicConfig باید قبل از شروع threadهای دیگر و از thread اصلی فراخوانی شود.

در پارامتر format، برخی از رشته‌های مهم که بخشی از LogRecord هستند را مشخص می‌کنیم. (asctime)% زمان ایجاد LogRecord را مشخص می‌کند. LogRecord در واقع گزارش‌هایی است که در طول استفاده از ربات، در ترمینال برای ما ثبت می‌شود. در ادامه این رکوردها را خواهیم دید.

در پارامتر level، سطح level را در لاگ برای نشان دادن پیام مشخص می‌کنید. برای پایتون، 6 سطح لاگ وجود دارد که شامل NOTSET=0 ،DEBUG=10 ،INFO=20 ، WARN=30 ،ERROR=40 و CRITICAL=50 است. همان‌طور که می‌بینید، ما از INFO استفاده کردیم. INFO تایید می‌کند که همه چیز درست و همان‌گونه که باید کار می‌کند.

				
					logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
GENDER, PHOTO, LOCATION, BIO = range(4)

				
			

برای httpx یک سطح logging بالاتر مشخص می‌کنیم تا از لاگ شدن یک جای ریکوئست‌های GET و POST جلوگیری شود. در خط سوم متغیرها به کمک range و با استفاده از یک توالی از اعداد صحیح تعریف می‌شوند. range(4) به معنی این است که اعداد 0 و 1 و 2 و 3 را به ترتیب به متغیرها نسبت دهد.

				
					async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):

    reply_keyboard = [["Boy", "Girl", "Other"]]

    await update.message.reply_text(
        "Hello, We are creating a telegram bot with pyhton."
        "Click /cancel if you want to leave\n\n"
        "What is your gender?",
        reply_markup=ReplyKeyboardMarkup(
            reply_keyboard, one_time_keyboard=True, input_field_placeholder="Boy or Girl?"
        ),
    )

    return GENDER
				
			

در این بخش، تابع Start را ایجاد می‌کنیم. پس از استارت بات این تابع شروع به کار می‌کند. با استفاده از ReplyKeyboardMarkup دو گزینه برای گرفتن جنسیت کاربر ایجاد می‌کنیم؛ پسر و دختر. در کنار آن یک پیام متنی نیز برای کاربر ارسال می‌شود. محتوای این پیام هر چیزی می‌تواند باشد. در این پیام کامند cancel/ را برای خروج از ربات قرار می‌دهیم که در ادامه تابع آن را تعریف خواهیم کرد. در آخر، تابع به کمک return جنسیت یا GENDER را برمی‌گردانیم.
قطعاً تا الان متوجه این شدید که قبل از def از واژه‌ی async استفاده کرده‌ایم. پیشنهاد می‌کنیم حتماً درباره برنامه نویسی async در پایتون و کاربرد async و await اطلاعات کسب کنید.

				
					async def gender(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("Gender of %s: %s", user.first_name, update.message.text)
    await update.message.reply_text(
        "If you want, send me a picture of yourself. "
        "send /skip if you don't want to.",
        reply_markup=ReplyKeyboardRemove(),
    )

    return PHOTO
				
			

در این بخش، جنسیتی که کاربر انتخاب کرده را ‌ذخیره می‌کنیم. با استفاده از logger.info در قسمت ترمینال، اطلاعاتی که کاربر از جنسیت خود داده را نشان می‌دهیم. سپس از کاربر می‌خواهیم تا یک فایل تصویری برای ما بفرستد. امکان رد کردن این قسمت با کامند skip/ وجود دارد. کلیدهایی که در تابع قبلی ایجاد کرده بودیم را نیز با متد ReplyKeyboardRemove حذف می‌کنیم.

				
					async def photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    photo_file = await update.message.photo[-1].get_file()
    await photo_file.download_to_drive("pouyanitbot.jpg")
    logger.info("Photo of %s: %s", user.first_name, "pouyanitbot.jpg")
    await update.message.reply_text(
        "Nice!Now send me your location or /skip this part"
    )

    return LOCATION
				
			

پس از اینکه کاربر تصویر را فرستاد با استفاده از این تابع، تصویر را دریافت و با نام دلخواه ذخیره می‌کنیم. ما در اینجا از نام pouyanitbot استفاده کردیم. پس از آن از کاربر می‌خواهیم که لوکیشن خود را برای ما ارسال کند. امکان رد کردن این بخش را نیز برای کاربر قرار می‌دهیم.

				
					async def skip_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("User %s sent no photo.", user.first_name)
    await update.message.reply_text(
        "NP! Send me your location so I know where you are from or /skip this part."
    )

    return LOCATION
				
			

این تابع را برای وقتی که کاربر کامند skip/ را وارد کرد و تمایل به فرستادن عکس نداشت، می‌سازیم. ابتدا با logger.info برنامه به ما اطلاع می‌دهد که کاربر عکسی نفرستاد. سپس پیغامی از طرف ربات به کاربر می‌فرستیم تا لوکیشن خود را بفرستد. در اینجا نیز دو حالت داریم. اگر لوکیشن خود را بفرستد، آن را دریافت و به مرحله بعد می‌رویم. اگر نخواهد بفرستد، با کامند skip/ به مرحله بعد می‌رود.

				
					async def location(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    user_location = update.message.location
    logger.info(
        "Location of %s: %f / %f", user.first_name, user_location.latitude, user_location.longitude
    )
    await update.message.reply_text(
        "What a beautiful place you live. Tell me about yourself so I can get to know you better."
    )

    return BIO
				
			

این تابع وقتی عمل می‌کند که کاربر لوکیشن خود را برای ربات بفرستد. ابتدا لوکیشن را دریافت و ذخیره می‌کنیم. سپس از کاربر می‌خواهیم یک پیام متنی برای ربات بفرستد و خودش را معرفی کند. اگر کاربر چیزی جز پیام متنی بفرستد، ربات توانایی دریافت آن را نخواهد داشت.

				
					async def skip_location(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("User %s sent no location.", user.first_name)
    await update.message.reply_text(
        "Tell me about yourself so I can get to know you better."
    )

    return BIO
				
			

تابعی تعریف می‌کنیم برای وقتی که از کاربر لوکیشنی دریافت نکردیم. در ادامه پیامی به کاربر ارسال می‌کنیم و از او درخواست پیام متنی برای معرفی خودش می‌کنیم. در نهایت با return BIO آن را می‌گیریم.

				
					async def bio(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("Bio of %s: %s", user.first_name, update.message.text)
    await update.message.reply_text("Nice to meet you my friend.")

    return ConversationHandler.END
				
			

تابع bio را می‌سازیم تا ربات پیام متنی کاربر را دریافت و ذخیره کند. این پیام در ترمینال به ما نمایش داده خواهد شد. پس از آن از طرف ربات پیامی مبنی بر اتمام مکالمه برای کاربر می‌فرستیم و با استفاده از کلاس ConversationHandler مکالمه تمام می‌شود.

				
					async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("User %s canceled the conversation.", user.first_name)
    await update.message.reply_text(
        " Take care.", reply_markup=ReplyKeyboardRemove()
    )

    return ConversationHandler.END
				
			

در تابع Start از کامند cancel/ استفاده کردیم. در این بخش عملیاتی که بعد از کلیک روی این گزینه باید صورت بگیرد تعریف می‌کنیم. ابتدا با logger.info در ترمینال کاربری که از ربات خارج شد را ثبت می‌کنیم. بعد از آن پیامی برای کاربر ارسال کرده و همزمان دو کلیدی که برای تعیین جنسیت کاربر تعریف کرده بودیم را حذف می‌کنیم. در آخر هم با دستور return ConversationHandler.END مکالمه را به اتمام می‌رسانیم.

				
					def main():
    application = Application.builder().token("TOKEN").build()

    conv_handler = ConversationHandler(
        entry_points=[CommandHandler("start", start)],
        states={
            GENDER: [MessageHandler(filters.Regex("^(Boy|Girl)$"), gender)],
            PHOTO: [MessageHandler(filters.PHOTO, photo), CommandHandler("skip", skip_photo)],
            LOCATION: [
                MessageHandler(filters.LOCATION, location),
                CommandHandler("skip", skip_location),
            ],
            BIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, bio)],
        },
        fallbacks=[CommandHandler("cancel", cancel)],
    )

    application.add_handler(conv_handler)
    application.run_polling(allowed_updates=Update.ALL_TYPES)
				
			

تابع main برای اجرای برنامه است و به نحوی مهم‌ترین قسمت کد ربات ما می‌باشد. در قسمت اول با استفاده از کلاس Application، توکن رباتمان را به برنامه می‌دهیم. توکنی که ربات BotFather پس از ساختن ربات برای ما ارسال کرد را در متد token و به جای TOKEN قرار می‌دهیم.
در بخش دوم، Conversation Handler را به همراه حالت‌هایی که GENDER، PHOTO، LOCATION و BIO باید داشته باشند را اضافه می‌کنیم. Entry_points چیزهایی هستند که مکالمه را آغاز می‌کنند. در اینجا ربات ما با start/ آغاز می‌شود. در states حالت‌های مختلف مکالمه‌ای که کاربر می‌تواند در آن باشد را تعریف می‌کنیم. درون fallback لیستی از handlerهایی که کاربر حین مکالمه می‌تواند استفاده کند را تعریف می‌کنیم.
در نهایت نیز با دستور application.run_polling مشخص می‌کنیم تا وقتی خودمان برنامه را متوقف نکردیم، ربات ران شود. برای توقف اجرا در محیط ترمینال از ctrl+c استفاده می‌شود.

اکنون می‌توانیم کدها را به صورت یکجا ببینیم:

				
					import logging
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import Application, CommandHandler, ContextTypes, ConversationHandler, MessageHandler,filters

logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)

logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
GENDER, PHOTO, LOCATION, BIO = range(4)

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    reply_keyboard = [["Boy", "Girl"]]
    await update.message.reply_text(
        "Hello, We are creating a telegram bot with pyhton."
        "Click /cancel if you want to leave\n\n"
        "What is your gender?",
        reply_markup=ReplyKeyboardMarkup(
            reply_keyboard, one_time_keyboard=True, input_field_placeholder="Boy or Girl?"
        ),
    )

    return GENDER

async def gender(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("Gender of %s: %s", user.first_name, update.message.text)
    await update.message.reply_text(
        "If you want, send me a picture of yourself. "
        "send /skip if you don't want to.",
        reply_markup=ReplyKeyboardRemove(),
    )

    return PHOTO

async def photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    photo_file = await update.message.photo[-1].get_file()
    await photo_file.download_to_drive("pouyanitbot.jpg")
    logger.info("Photo of %s: %s", user.first_name, "pouyanitbot.jpg")
    await update.message.reply_text(
        "Nice!Now send me your location or /skip this part"
    )

    return LOCATION

async def skip_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("User %s sent no photo.", user.first_name)
    await update.message.reply_text(
        "NP! Send me your location so I know where you are from or /skip this part."
    )

    return LOCATION

async def location(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    user_location = update.message.location
    logger.info(
        "Location of %s: %f / %f", user.first_name, user_location.latitude, user_location.longitude
    )
    await update.message.reply_text(
        "What a beautiful place you live. Tell me about yourself so I can get to know you better."
    )

    return BIO

async def skip_location(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("User %s sent no location.", user.first_name)
    await update.message.reply_text(
        "Tell me about yourself so I can get to know you better."
    )

    return BIO

async def bio(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("Bio of %s: %s", user.first_name, update.message.text)
    await update.message.reply_text("Nice to meet you my friend.")

    return ConversationHandler.END

async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.message.from_user
    logger.info("User %s canceled the conversation.", user.first_name)
    await update.message.reply_text(
        " Take care.", reply_markup=ReplyKeyboardRemove()
    )

    return ConversationHandler.END

def main():
    application = Application.builder().token("6560416616:AAFwYidGVUVWulQJnB5CyR36C8L48MBIMSY").build()

    conv_handler = ConversationHandler(
        entry_points=[CommandHandler("start", start)],
        states={
            GENDER: [MessageHandler(filters.Regex("^(Boy|Girl)$"), gender)],
            PHOTO: [MessageHandler(filters.PHOTO, photo), CommandHandler("skip", skip_photo)],
            LOCATION: [
                MessageHandler(filters.LOCATION, location),
                CommandHandler("skip", skip_location),
            ],
            BIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, bio)],
        },
        fallbacks=[CommandHandler("cancel", cancel)],
    )

    application.add_handler(conv_handler)
    application.run_polling(allowed_updates=Update.ALL_TYPES)

if __name__ == "__main__":
    main()
				
			

قدم ششم؛ اجرای ربات

ساختار برنامه ما کامل شد. اکنون نوبت آن است که برنامه را در سیستم خودمان ران کنیم و در تلگرام رباتی را که ساختیم استارت کنیم. به این نکته توجه کنید که اگر در خاک کشور عزیزمان ایران هستید، قبل از ران کردن برنامه، قند شکن خود را روشن کنید. در غیر این صورت برنامه اجرا نمی‌شود. طبق مراحل زیر برای استفاده از ربات پیش بروید:

1. پس از اجرای برنامه پیامی به این شکل در ترمینال دریافت خواهید کرد:

ترمینال

2. حال وارد ربات شوید و آن را استارت کنید.

استارت ربات

3. پس از استارت ربات با چنین صفحه‌ای روبرو خواهید شد:

پس از استارت ربات

باقی مراحل مشخص است و ربات به همان صورت که در کد مشخص کردیم عمل خواهد کرد و نیازی به توضیح اضافه ندارد.
تبریک می‌گوییم! با هم توانستیم یک ربات گفتگو در تلگرام راه بیندازیم.

کلام آخر

پایتون یک زبان قدرتمند در دنیای برنامه نویسی است که در بسیاری از حوزه‌ها از آن استفاده می‌شود. یکی از موارد استفاده از زبان پایتون، ساخت ربات تلگرام است. چندین کتابخانه و پکیج مخصوص ساخت ربات تلگرام وجود دارد. اما معروف ترین آن‌ها، PTB یا python-telegram-bot است. در این مقاله سعی کردیم با کمک پایتون یک ربات تلگرامی نسبتاً ساده بسازیم تا شما با منطق پشت ربات‌ها و نحوه‌ی ساخته شدن آن‌ها آشنا شوید. با مطالعه درباره python-telegram-bot قادر خواهید بود ربات‌های جذابی بسازید. در نهایت، از اینکه تا انتهای مقاله همراه ما بودید متشکریم.

به این مقاله امتیاز دهید!

میانگین امتیاز 3.7 / 5. تعداد رأی ها : 3

هنوز هیچ رأیی داده نشده. اولین نفر باشید!

اشتراک گذاری اشتراک گذاری در تلگرام اشتراک گذاری در لینکدین اشتراک گذاری در ایکس کپی کردن لینک پست

و در ادامه بخوانید

اولین دیدگاه را اضافه کنید.

برچسب ها

برنامه نویسی پایتون