نوشتن یه ربات تلگرام با Pyromod در Pyrogram

Pyromod + pyrogramخب سلام دوستان امید وارم حال دلتون خوب باشه یا اگه نیست بهتر بشه +)وقتی میخواید شروع کنید به کار کردن با ربات های تلگرامی با پایروگرام یکم اذیت میشید که چطوری دیتا رو از کاربر بگیرید و چطوری جوابش رو بدید، در واقع step گذاری کنیدو... امروز میخوایم از Pyromod برای هندل کردن ای مورد استفاده کنیم، در کل چیز زیادی نداره Pyromod و یه سری مانکی پچ هستش و زیاد هم سخت نیست، بریم جلو یادش بگیریم :)نکته: وقتی دارید از Pyromod استفاده میکنید باید از نسخه async پایروگرام استفاده کنید(هرچند ورژن 2 async هستش)نکته: در این آموزش موارد بیسیک و پایه ای رو توضیح نمیدم مثلا فیلتر چیه، هندلر چیه و... این موارد داخل آموزش های قبلی به صورت کامل توضیح داده شده!نکته :‌ تو این آموزش چند تا از متود های خوب لایبرری pyromod رو نگاه میکنیم و... ولی همش رو نمیگیم برای هیمن میتونید سورس کدش رو ببینید و خیلی درک بهتری پیدا بکنید ازش ... https://github.com/usernein/pyromod/tree/master/pyromod خب اولین کار نصب لایبرری ها هستش:pip install pyromod pip install pyrogram بعد از اینکه نصب شدن یه فایل باز میکنیم و کدمون رو داخلش مینویسیم، چون قرار نیست ربات پیچیده ای باشه و پلاگین و... داشته باشه داخل یه فایل مینویسیم...Step1-initializefrom pyrogram import Client, filters, enums from pyromod.helpers import ikb, kb, array_chunk from pyromod import listen api_id = your_api_id api_hash = &quotyour_api_hash&quot bot = Client( name=&quotmy_bot&quot, api_id=api_id, api_hash=api_hash, bot_token=&quotyour_token&quot, proxy=dict(scheme=&quotsocks5&quot, hostname=&quotyour_ip&quot, port=your_port))اول اومدیم مواردی که نیاز داشتیم رو import کردیم، بعد یه اینستنس از کلاس کلاینت ایجاد کردیم به اسم bot.اینجا از pyromod اون listen رو هم ایمپورت کردیم که مانکی پچ اعمال بشه و یه سری متود رو به ما بده مثل ask و clear_listener, cancel_listener و... در غیر اینصورت به ما ارور میده چون مانکی پچ pyromod اعمال نشده، پس این رو یادتون نره که import کنید.Step2-First Handlerbase_msg = &quotلطفا یکی از دکمه های زیر را انتخاب نمایید ??&quot ask_phone_msg = &quot? شماره تلفن خورد را ارسال نمایید&quot timeout_err_msg = &quot❌ کاربر گرامی، زمان پاسخگویی شما به اتمام رسیده است&quot keys = [[&quot? راهنمایی&quot, &quot? افزودن اکانت&quot] keyboard = kb(keys, resize_keyboard=True) @bot.on_message(filters.private & filters.command(&quotstart&quot)) async def start_bot(client, message): chat_id = message.chat.id await client.send_chat_action(chat_id=chat_id, action=enums.ChatAction.TYPING) await message.reply(text=base_msg, reply_markup=keyboard)خب در کد بالا اومدیم یه سری پیام که قرار به کاربر نشون بدیم رو اول کدمون قرار دادیم و داخل متغیر های مربوطه ریختیم(میتونید یه فایل json بسازید داخل اون پیام های ربات رو بزارید به نظرم اینجوری تمیز تره)، اینجا یه متغیر به اسم keyboard ایجاد کریدم که مقدارش دوتا دکمه افزودن اکانت و راهنمایی هستش، بعد یه هندلر نوشتیم و داخلش اومدیم یه send_action فرستادیم + یه پیام که به کاربر بگه یکی از دکمه ها رو انتخاب کن. داخل ربات api ما دو نوع دکمه داریم یکیش ReplayKeyboardMarkup هستش و اون یکی InlineKeyboardButton، اولی دکمه عادی و دومی دکمه شیشه ای هستش، لایبرری pyromod به ما این قابلیت رو میده که یکم ساده تر دکمه هامون رو بسازیم، برای دکمه های عادی از kb و برای دکمه های شیشه ای از ikb استفاده میکنیم که بالا تر ایمپورتشون کردیم.در pyromod برای اینکه کیبورد های عادی بسازیم کافیه یه لیست بدیم به متود kb دقیقا مثل همونی که بالاتر نوشتیم، حالا داخل این لیست ما میتونیم شکل نمایش دکمه هامون رو مشخص کنیم مثلا دکمه ها در یک row قرار بگیرن یا در n تا row جدا قرار بگیرن و اینکه چند دکمه در هر row قرار بگیره(column)، مثلا تو چیزی که بالا نوشتیم بهش گفتیم دکمه ها داخل 2 تا row جدا قرار نمایش داده بشه(هر لیست تو در تو به عنوان یک row در نظر گرفته میشه و item های هر لیست هم به عنوان بگیم column های اون row نمایش داده میشن).الان دکمه های که نوشتیم اینشکلی هستن:reply keyboard markupحالا اگه بخوایم این دوتا دکمه داخل یه row قرار بگیرن میایم اینشکلی مینویسیم:keyboard = kb([ [&quot? راهنمایی&quot , &quot? افزودن اکانت&quot]], resize_keyboard=True)reply keyboard markupStep3-Second Handlerاینجا یه موردی هستش که باید بگم، قبلا step رو دستی مینوشتیم و میرفتیم جلو و هندلر های مختلفی مینوشتیم و داخل هرکدوم میومدیم مثلا فیلتر های تسکت میزاشتیم و با gorup جدا میکردیم، ولی اینجا دیگه نیازی به این کار نیست میتونیم داخل همین فانکشن بریم جلو و کدمون رو ادامه بدیم...@bot.on_message(filters.private & filters.command(prefixes=&quot&quot, commands=keys[1])) async def get_account(client, message): chat_id = message.chat.id try: phone_number = await client.ask( chat_id=chat_id, text=ask_phone_msg, reply_markup=keyboard, timeout=10) print(phone_number.text) except listen.ListenerTimeout: await message.reply(text=timeout_err_msg, reply_markup=keyboard) bot.run()خب تو کد بالا اومدیم یه هندلر دیگه اضافه کردیم، مثلا اگه الان قرار ربات دریافت اکانت بنویسیم اینجا میتوینم داخل همین فانکشن کل عملیات رو خیلی راحت بنویسیم(قبلا هم میتونستیم با شرط گذاشتن و... پیش ببریم قضیه رو ولی الان با pyarmod خیلی راحت تر هستش) البته برای اینکه کدمون تمیز تر باشه میتونیم برای هر دکمه که داخل رباتمون هست یه هندلر جدا بنویسیم.الان ما میخوایم به کاربر بگیم که شمارش رو بفرسته و بعد چیزی که میفرسته رو دریفات کنیم و مثلا اگه تو 30 ثانیه هر چند دقیقه که ما میگیم نفرستاده بگه وقتش تموم شده.در کد بالا از متود ask لایبرری pyromod استفاده کردیم این یکی از اون مانکی پچ هایی هستش که pyromod اضافه کرده، اینجا دقیقا مثل متود send_message میتونیم پارامتر هایی که میخوایم رو بهش بدیدم همچنین میتونیم دوتا پارامتر اضافه filter و timeout رو هم ست بکنیم براش(اپشنال هستش اجباری نیست) این timeout تعیین میکنه که چقدر متود ask منتظر ورودی کاربر باشه، مهم ترین نکته این هستش که وقتی پیام فرستاده میشه ما چیزی که کاربر در جوابش میفرسته رو میتونیم داخل phone_number ذخیره کنیم بعد از اینکه کاربر یه چیزی بفرسته این عمل ask خود به خود کنسل میشه و دیگه هرچیزی که کاربر بفرسته برای این ask ذخیره نمیشه.اینجا داخل اکسپشن هم هندل کردیم که اگر تو تایم تایین شده جواب نداد به کاربر بگه دوباره دکمه رو بزنه(این اکسپشن برای ورژن جدید pyromod هستش و تو ورژن های قبل تر باید از اکسپشن asyncio.exceptions.TimeoutError استفاده کنیم).Step3-4-Method array_chunk/Paginationیه مورد هستش که اینجا خواستم اضافه بکنم اون هم متود array_chunk هستش که قبلا ایمپورتش کرده بودیم، اگه تعداد دکمه هامون زیاد باشه میتونیم از این متود برای ساختن راحت تره این column هامون استفاده بکنیم، با این متود میتونیم بگیم column هامون چند تا چندتا باشه مثلا :k=[(&quotTest 1&quot, &quottest_1&quot), (&quotTest 2&quot, &quottest_2&quot), (&quotTest 3&quot, &quottest_3&quot), (&quotTest 4&quot, &quottest_4&quot)]])الان ما همچین چیزی داریم اگه بیایم از این متود استفاده کنیم میتوینم بگیم بهش برامون هر row که میشه بساز (یعنی تعداد row داینامیک میشه) ولی داخل هر کدوم فقط n تا کالمنی که ما میگیم رو قرار بده:b= array_chunk(k, 2) keyboard = ikb(b)الان برای ما دوتا row میسازه و داخل هرکدوم 2 تا column قرار میده که خیلی ساده تر میکنه کار ما رو، برای کیبورد عادی هم میتونید استفاده کنید.همچنین یه متود Pagination هم داره این لایبرری pyromod که میتونید برای ساختن پیجینیشن دکمه ها ازش استفاده کنیم، برای زمانی خوبه که تعداد دکمه ها زیاد باشه و نخوایم در یک پیام نشونشون بدیم.Step4-KeyboardButtonبرای نوشتن دکمه های شیشه دو مورد هستش که اول باید توضیح بدم. مورد اول : در دکمه های شیشه ای ما باید 2 مورد رو حتما ست بکنیم یکش text هستش همون متنی که روی دکمه نمایش داده میشه و دومین مورد هم callback_data یا url هستش(یکی از این دو مورد اخر حتما باید ست بشه).مورد دوم : وقتی روی دکمه عادی کلیک میکنید یه تکست میفرسته داخل چت و در کل ما از اپدیت عادی؛ درواقع تو هندلر message میتوینم اون رو دریافت کنیم ولی برای دکمه های شیشه ای فرق میکنه و وقی کلیک میشه رو دکمه یه دیتایی رو برمیگیردونه دقیقا همون callback_data که ست میکنیم، و ما برای دریافت اون و تشخیص اینکه رو کدوم دکمه کلیک شده باید بیایم از هندلر on_callback_query استفاده بکینم.@bot.on_message(filters.private & filters.command(&quotstart&quot)) async def start_bot(client, message): keyboard = ikb([[ (&quot? راهنمایی&quot, &quothelp&quot), (&quot? افزودن اکانت&quot, &quotadd_account&quot)]]) await message.reply(text=base_msg, reply_markup=keyboard) bot.run()خب این همون کدی هستش که قبلا با کیبورد عادی نوشتیم و الان با کیبورد شیشه ای، اینجا هم مثل قبل هر لیست تو در تو یک row هستش و هر row میتونه شامل یه سری column باشه.هر دکمه رو داخل یه تاپل قرار میدیم که شامل تکست و callback_data یا url میشه. چیزی که الان نوشتیم خروجیش اینشکلی میشه:Inline Keyboard Button الان اگه رو یکی از این دکمه ها کلیک بشه یه اپدیتی میفرسته و همونطور که قبلا گفتیم داخل هندلر callback_qurey باید بگیریمش.@bot.on_callback_query() def get_callback(client, query): chat_id = query.message.chat.id if query.data == &quotadd_account&quot: phone_number = await client.ask(chat_id=chat_id, text=ask_phone_msg) print(phone_number.text)الان رو هر دکمه ای که کلیک بشه اپدیتش میاد اینجا و ما باید یه جوری تشخیص بدیم که اپدیت برای کدوم دکمست برای همین میایم از اون callback_data که قبلا ست کرده بویدم استفاده میکنیم، اینجا به جای message مینویسیم query که منطقی تر باشه هرچند هر چیزی که بخوایم میتونیم بنویسیم... و پیشنهاد میکنم این query رو پرینت بکنید و چیزی که داخلش هست رو ببینید که درک بهتری از اپدیتش داشته باشید و...تو کد بالا اومدیم شرط گذاشتیم اگر dataش برابر add_acount بود به کاربر بگه شمارش رو وارد کنه اینجا باز هم اومدیم خیلی ساده از متود ask برای گرفتن شماره کاربر استفاده کردیم.یه مورد دیگه هستش، وقتی الان کاربر رو دکمه کلیک میکنه درسته که بهش میگه شمارش رو بفرسته ولی اگه دقت کنید روی دکمه یه ساعت سفید رنگ میاد که این به این دلیل هستش که داخل callbacK_query ما باید بیایم یه answer_callback_query رو بفرستیم یا مثلا دکمه رو پاک کنیم یا دکمه رو چینج بکنیم مثلا یه سری دکمه دیگه نمایش بدیم ولی اینجا چون ما ask کریدم و در واقع send_message میشه برای همین اون علامت باقی میمونه، اگه بخوایم بعد از اینکه جواب میدیم پاک بشه میتونیم از متود answer استفاده کنیم (یه شورت کات هستش به جای اسم بالا که یکم طولانیه)به این شکل مینویسیم :await query.answer(&quot&quot)البته اینشکلی هیچ اتفاقی نمیفته(در واقع میفته ولی کاربر چیزی نمیبینه صرفا اون علامت ساعت میره)اگه بخوایم میتونیم از این برای نمایش یه سری متن هم استفاده بکنیم مثلا زمانی که کاربر روی یه دکمه کلیک میکنه ولی دکمه برای اون کاربر نیستش مثلا فقط ادمین حق داره از اون استفاده بکنه تو این شرایط میتونیم این رو بزاریم و وقتی کاربر کلیک میکنه بهش اخطار بده و... کافیه داخل اون استرینگ متنی که میخوایم رو بنویسیم.answer callback query همونطور که میبینید بالای صفحه نمایش داده میشه و اون ساعت هم روی دکمه شیشه ای دیگه نمیمونه. داخل کد های بالا ما مینوشتیم client.ask شما میتونید روی ابجکت user و chat هم از متود ask استفاده کنید. همچنین وقتی ask میکنید منتظر میمونه تا اپدیت جدید دریافت بشه و همینجوری منظتر میمونه، مگر اینکه پروسس کدمون kill بشه(البته اگه timeout ست نکرده باشیم براش) برای هیمن میتونیم از متود cancel_listener استفاده کنیم و فقط یه پارامتر chat_id باید بدیم بهش البته میتونیم رو ابجکت مثلا User و Chat هم صداش بزنیم مثلا :bot.cancel_listener(chat_id) # OR message.chat.cancel_listener() # OR message.from_user.cancel_listener()البته این عمل یه اکسپشنی رو هم برمیگردونه، یه مانکی پچ دیگه هم برای این Exception ایجاد شده که میتونید زمانی که ask میکنید اون ask رو داخل یه try قرار بدید و ارور رو هندل کنیدexcept pyrogram.errors.ListenerCanceled:درواقع این متود cancel_listener میاد future که پشت صحنه ایجاد شده رو پاک میکنه. نکته: وقتی میگید app.ask اینجا دیگه منتظره که یه پیامی بیاد و اون رو دریفات بکنه، تو این حالت خط بعدش اجرا نمیشه و تو این حالت ها نمیتونید کنسل بکنید چون این هر اپدیت مسیجی که بیاد رو دریافت میکنه، اگر مثلا اپدیت هایی غیر از مسیج داشته باشیم مثلا اینلاین کوئری یا کالبک دکمه شیشه ای و.. میتونیم از این کنسل استفاده کنیم، بیشتر مواقع هم همون تایم اوت ست کردن جواب میده...میتونید به یه شکل دیگه هم منطق کنسل کردن رو پیاده کنید، مثلا یه حالت دارید که میگید به کاربر ورودی رو بده و یه دکمه عادی به اسم کنسل هم نشون دادید بهش، داخل کد میتونید شرط بزارید مثلاuser_input = await app.ask(.......) if user_input in cancel_key: do something...به این شکل میتونید چک کنید اگر اینپوت کلید کنسل بود یه کاری رو انجام بده.... اینجوری هم میشه! غیر از این حالت ها تایم اوت جواب میده.خب دوستان آموزش تا همینجا بود امید وارم براتون مفید بوده باشه، پیشنهاد میکنیم لینک های مربوطه که براتون گذاشتم رو یه نگاهی بندازید، اونجا کامل تر هستش چون داخل یه مقاله نمیشه همه چیز رو گفت و خسته کننده و طولانی میشه و بهتر که همیشه یه نگاهی به داکیومنت بندازیم...مثل همیشه میتونید لایک کنید کامنت بزارید و اگه دوست داشتید با بقیه هم شیر بکنید، اگه مشکلی، حرفی چیزی هم بود میتونید کامنت بزارید ❤️