نوشتن ربات CLI تلگرام با زبان PYTHON و با لایبرری pyrogram قسمت دوم

pyrogramاخرین اپدیت : Pyrogram version >= 2.0خب دوستان تو قسمت اول یک سری پیشنیاز ها و یه info کلی راجب پایروگرام رو گرفتیم، در این قسمت و قسمت های بعد میخوایم ربات اصلیمون که تو قسمت اول بهش اشاره کردیم رو بنویسیم.تو این چند قسمت از دیتابیس redis استفاده میکنیم، شما میتونید از دیتابیس های دیگه هم استفاده کنید یا داخل فایل بنویسید یا متغیر تعریف کنید و...) ولی در کل بهتر الان دیتابیس رو یاد بگیرید چون بعدا هم باز به دیتابیس نیاز پیدا میکنید . نکته: کد هایی که مینویسیم رو هم میتونید از گیت هاب بردارید لینکش رو آخر هر قسمت گذاشتم.نکته: تو این قسمت از async هم استفاده میکنیم و در ادامه هم باز استفاده خواهیم کرد پیشنهاد میکنم یاد بگیرید و یه درکی ازش پیدا کنید.Step-1: Send messagemessageخب در کد بالا ما اومدیم فقط پرایویت چت(پی وی) رو فیلتر کردیم یعنی هر اپدیتی از سمت پیوی بیاد این ربات اون رو دریافت میکنه. در ادامه داخل فانکشن اومدیم پیام سلام رو به کاربر ارسال کردیم.نکته: در کد بالا اومدیم هندلر مون رو asynchronous کردیم با گذاشتن async قبل def و بعدا متود هایی که صدا میزنیم رو هم await میکنیم و اینکه پایروگرام به صورت پیش فرض خودش به صورت concurrent هستش.تو کد بالا ما از دوتا متود جدید استفاده کردیم به اسم reply و send_message، تو پایروگرام برای اینکه یه پیام text رو به کاربر بفرستیم از این دوتا متود میتونیم استفاده کنیم، هرکدوم از این متود ها یه سری پارامتر میگیرن که میتونیم بدیم بهشون مثلا اینجا متود reply یکی از مهم ترین پارامتر هاش اون هم متنی هستش که قرار به کاربر بفرستیم، دقت کنید این متود رو ما باید روی خود ابجکت message صدا بزنیم یعنی client.reply_message بهمون ارور میده چون متود این ابجکت نیستش پس باید روی ابجکت message صداش بزنیم.مورد بعدی متود send_message هستش، این متود پارامتر های زیادی رو میگیره ولی مهم ترینش chat_id و text هستش اولین مورد chat_idهستش، هر اکانت، چنل، گروه و ربات تلگرامی یه ایدی عددی منحصر به فرد داره که ما میتونیم از این ایدی عددی برای تعامل با اون کاربر استفاده کنیم، اینجا ما با chat_id یا username میتونیم مشخص میکنیم که به چه کسی پیام ارسال بشه و بعد متنی که قرار ارسال بشه رو هم مشخص کردیم، یه سری پارامتر دیگه هم میگیره این متود که اون ها اختیاری هستن میتونید ست بکنید یا ست نکنید.پارامتر های دیگه متود send_message و reply رو میتونید توی این لینک ها ببینید.نکته: با استفاده از id عددی فقط به افرادی که قبلا باهاشون تعامل داشتید میتونید پیام ارسال کنید. مثلا تو یه گروه با هم باشن، هم دیگه رو دیده باشن یا قبلا بهم دیگه پیام داده باشن و.. اینا میشه تعامل داشتن با اون id، اگر این تعامل وجود نداشته باشه بهتون ارور PEER_ID_INVALID رو میده یعنی شما با کاربر قبلا تعاملی نداشتید این نکته مهمی هستش که بهش برخورد میکنید.Step-2 : Admin Panel یه قسمت میخوایم بزاریم برای ربات که بتونیم یه سری جمله رو اضافه و حذف بکنیم و همچنین جمله های موجود رو نمایش بدیم، وقتی کسی بهش پیام داد بیاد از این جمله های ست شده براشون بفرسته ، برای اینکار از دیتابیس Redis استفاده میکنم شما میتونید از دیتابیس های دیگه هم استفاده کنید یا داخل فایل بنویسیدو... Step-2-1: Smart Pluginsقبل از قسمت ادمین پنل میخوام راجب اسمارت پلاگین براتون بگم چون قرار ازش استفاده کنیم.پایروگرام این قابلیت رو به شما میده که هندلر هاتون رو داخل فایل های مختلف قرار بدید مثلا اینجا ما میایم قسمت مربوط به ادمین رو داخل یه فایل جدا میزاریم که کد تمیز تر باشه و توسعش هم راحت تر باشه. برای اینکه از اسمارت پلاگین استفاده کنید میتونید داخل فایل اصلی به این شکل بنویسید(یه فایل به اسم main.py ایجاد کنید و با یکی از دو روش زیر پلاگین رو بهش معرفی کنید): smart_pluginخب بعدش شما باید یک دایرکتوری به اسم plugins ایجاد کنید کنار فایل اصلیتون و داخل این دایرکتوری پلاگین هاتون رو قرار میدید ( دیگه نیازی ندارید که تو کد اصلی چیزی رو ایمپورت کنید و...)اگر خواستید بیشتر بخونید راجبش و.. میتونید اینجا رو ببینید.خب در مرحله بعدی میایم دیتابیس رو وصل میکنیم به ربات و کم کم میریم جلو برای اینکا میتونید یه فایل __init__.pyداخل فولدر plugins ایجاد کنید و داخل این فایل مثلا ایدی ادمین و اینستنسی که از کلاس دیتابیستون میسازید رو قرار بدید:__init__.py الان در کد بالا اومدیم ردیس رو ایمپورت کردیم و بعد بهش کانکت شدیم(رو سیستم تون سرویس redis server رو باید start کرده باشید که اینجا بتونید بهش کانکت بشید دیفالت پورتش 6379 هستش البته اگه تغیر نداده باشید، برای قسمت host هم الان رو سیستم لوکال هستش برای همین میتونید بنویسید localhost و مقدار db هم شماره دیتابیس هستش ، داخل ردیس اسم نمیزارید برای دیتابیس تون و از شماره استفاده میکنید از 0 تا 15 میتونید عدد بدید که دیفالت 0 هستش البته کانفیگش رو میشه تغیر داد داد و تعداد بیشتری ایجاد کرد) یه اینستنس از کلاس redis.StrictRedis به اسم query ساختیم که از این به بعد به وصیله اون کوری میزنیم و دیتا هامون رو میگیریم و...و همچنین ایدی ادمین رو هم قرار دادیم الان کافیه هر جا که نیاز داشتیم query و admin رو ایمپورت کنیم .Step-3 : Help Messagefrom pyrogram import Client, filters from pyrogram.enums.ChatAction import TYPING from plugins import query, admin @Client.on_message(filters.private & filters.user(admin) & filters.command(&quothelp&quot)) async def help_message(client, message): chat_id = message.chat.id help_message = &quot&quot&quot⚡️ **اپشن های موجود** ⚡️ \n☔️ اضافه کردن پیام به ربات با کامند /add_msg \n☔️ حذف کردن پیام از ربات با کامند /del_msg \n☔️ نمایش جمسملات موجود با کامند /show_msg \n☔️ نمایش وضعیت ربات /info \n☔️ چک کردن وضعیت ربات با کامند /status \n☔️ فوروارد کردن پیام /forward \b☔️ جوین شدن داخل گروه مورد نظر /join_chat \n☔️ ست کردن پیام بعد از جوین شدن ربات در گروه /join_msg \n. &quot&quot&quot await client.send_chat_action(chat_id=chat_id, action=TYPING) await message.reply(text=help_message)خب بیاید ببینم در کد بالا دقیقا چیکار کردیم ..اول اومدیم یه فایل به اسم admin_pannel.py تو دایرکتوری پلاگین مون ایجاد کردیم و اولین هندلر مون رو برای پنل ادمین نوشتیم .@Client.on_message(filters.private & filters.user(admin) & filters.command(&quothelp&quot)) def help_message(client, message):خب در کد بالا اومدیم از چند تا فیلتر استفاده کردیم اگه بخواید از چند تا فیلتر تو هندلر تون استفاده کنید میتونید از علامت & برای and وعلامت | (پایپ) برای or استفاده کنید و اگر بخواید فیلتری رو not کنید یعنی بگید اگر این فیلتر نبود بیا یه کاری رو انجام بده باید از علامت ~ به همراه & یا پایپ ( | ) استفاده کنید(اگه وسط دوتا فیلتر استفاده میکنید در غیر اینصورت همون ~ کافیه).در مثال بالا گفتیم اگر فیلتر private و فیلتر user و فیلتر command برقرار بود یعنی شرط هایی که تایین کردیم اوکی بود بیا داخل فانکشن help_message و با استفاده از متود send_message اون help_message ما رو به کاربر بفرست.در اینجا قبل از فرستادن پیام اومدیم از متود send_chat_action استفاده کردیم، حتما دیدید که وقتی کسی پیامی مینویسه اون بالا تلگرام میزنه is typing... خب این همون chat action هستش و ما الان چت اکشن تایپینگ رو ارسال میکنیم به کاربر(برای مشخص شدن اینکه که چه نوع اکشی فرستاد بشه اومدیم دومین خط import کریدمش). لیست بقیه چت اکشن ها رو اینجا بببینید.نکته: در کد بالا برای help_message اومدیم جلوی اموجی ☔️ یه n\ قرار دادیم اگر این n\ رو بیاید انتهای جمله قرار بدید باعث میشه اون Tab هایی که داخل کد زدید هم در تلگرام ارسال بشن برای همین ما اومدیم اول خط نوشتیم(برای پیام های چند خطی به این شکل عمل کنید). نکته: زمانی که از اسمارت پلاگین استفاده میکنید نیازی به ساختن دوباره اینستنس Client داخل پلاگین ها نیست و همون اینستنس اولیه که داخل main.py ساختیم کافیه و در اینجا فقط ایمپورت میکنیم.در نهایت این چیزی که تا الان نوشتیم همچین خروجیی رو داره:admin panel tmStep-5 : Add Message@Client.on_message(filters.private & filters.user(admin) & filters.command(&quotadd_msg&quot)) async def add_msg(client, message): chat_id = message.chat.id messages = &quot&quot&quot⚡️ **اضافه کردن پیام** ⚡️ \nلطفا جمله یا جملات خود را ارسال کنید و در انتها کامند زیر را ارسال کنید: \n/save &quot&quot&quot await client.send_chat_action(chat_id=chat_id, action=TYPING) await message.reply(text=messages) query.setbit(&quotadd:msg&quot, 0, 1)خب در کد بالا اومدیم تابع مربوط به اضافه کردن جمله به ربات رو ایجاد کردیم مثل قبل فیلتر ها رو نوشتیم و پیامی رو هم برای کاربر ارسال کردیم الان به کاربر گفتیم جملش رو بفرسته و در اخر save/ رو بفرسته چون میخوایم اگر چند تا پیام جدا هم فرستاد ذخیره کنیم.ولی ما اینجا چیزی رو ذخیره نمیکنیم داخل دیتابیس! پس چجوری باید ورودی بگیریم از کاربر..؟؟بزارید یکم بیشتر توضیح بدم ، ببینید زمانی که میخواید از کاربر چیزی بگیرید و بعدش ذخیرش کنید یا... نیاز دارید که یه هندلر داشته باشید که پیام ها رو بگیر الان این هندلر بالا فقط به کامند add_msg جواب میده و جمله هایی که کاربر میفرسته رو تو اینجا نمیتونیم بگیریم برای همین میایم یه هندلر دیگه مینویسیم که تکست ها رو بگیره و دیگه کامندی رو فیلتر نمیکینم اینجوری تکست ها رو میگیره ولی یه مشکلی دیگه هست، این هندلری که مینویسیم برای گرفتن تکست هر پیامی که بیاد رو میگیره خود کامند هم تکست هستش پس اون رو هم میگیره و اون رو هم اضافه میکنه به دیتابیس(در ادامه اضافه کردن رو میگیم)‌ الان قاتی میشه برای همین نیاز داریم که Step بزاریم مثلا اینجا اخرین خط استپ add:msg رو گذاشتم 1 (داخل دیتابیس برای کلید add:msg مقدار 1 رو ست کردیم) بعدا تو هندلر تکستی که مینویسیم برای گرفتن جمله ها میایم چک میکنیم ببینم stepما چی هستش اگر add:msg برابر 1 اون زمان یعنی، پیام کاربر جمله هایی بوده که قرار اضافه بشه و اضافه میکنیم .اگه بخواید اینجا فقط برای کاربری که الان پیام داده step گذاری کنید در واقع وقتی چند کاربر استفاده میکنن از ربات، ربات قاتی نکنه بینشون اینجا باید بیام و چت ایدی رو هم ذخیره بکنیم همراه با step یعنی : query.setbit(f&quotadd:msg:{chat_id}&quot, 0, 1)الان با استفاده از فرمت استرینگ این چت ایدی رو هم دادیم بهش و فقط برای این یوزر استپ add:msg برابر 1 میشه در نتیجه موقع چک کردن هم برای همون یوزر چک میکنیم به این شکل میتونید یوزر هاتون رو جدا کنید یعنی قاتی نکنه ربات. این نکته مهم بود که بعدا بهش برمیخورید.نکته : برای step گذاری میتونید از دیتابیس یا متغیر گلوبال یا لایبرری pyromod استفاده کنید.در زیر یه آموزش برای کار کردن با لایبرری pyromod قرار دادم که میتونید ببینید: https://vrgl.ir/OwEiB خروجی چیزی که نوشتیم:Add msg tm Step-6 : Get User Inputخب برای دریافت ورودی به یه هندلر دیگه نیازی داریم که داخلش پیام رو ذخیره کنیم:@Client.on_message(filters.private & filters.user(admin), group=1) async def get_input(client, message): msg = message.text if query.getbit(&quotadd:msg&quot, 0) == 1 and msg and not msg.startswith(&quot/&quot): msg_counter = query.incr(&quotNew:Msg&quot) query.sadd(&quotMessages&quot, f&quot{msg_counter}:{msg}&quot) elif msg == &quot/save&quot: chat_id = message.chat.id await client.send_chat_action(chat_id=chat_id, action=TYPING) await message.reply(text=&quot&quot&quot⚡️ **اضافه کردن پیام** ⚡️ \n✅ جمله های شما با موفقیت ذخیره شد \n.&quot&quot&quot) query.setbit(&quotadd:msg&quot, 0, 0)خب در کد بالا اومدیم مثل قبل هندلر مون رو تعریف کردیم و بعد داخل تابع اومدیم چک کردیم ببینیم اون مقدار add:msg برابر 1 هستش یا نه اگر 1 بود و همچنین با / شروع نشده بود پیام کاربر(درواقع اگه کامند نبود) بیا وارد شرط ما بشه.نکته: دقت کنید اینجا چون اون کامند ها معنی کامند رو نمیدن یعنی همه چی تکست هستش اگه شرط نزاریم و چک نکنیم که با / شروع شده یا نه اگه کامند باشه متن کاربر اون رو هم به عنوان جمله اضافه میکنه به دیتابیس پس برای همین مثلا اینجا اون شرط رو علاوه بر چک کردن مقدا add:msg نوشتیم.در اینجا اول اومدیم گفتیم اگر شرطمون برقرار بود بیا یه msg_counter ایجاد کنه برای اینکار ما اومدیم از متود incr ردیس استفاده کریدم، این متود به این شکل هستش که هر زمان صداش بزنید یه شمارنده براتون اضافه میکنه مثلا الان صداش زدیم مقدار New:Msg داخل دیتابیس 1 میشه، دفعه بعدی میشه 2 دفعه بعد 3 و الا اخر... خب ما از این new:msg زمانی که میخوای پیام رو حذف کنیم استفاده میکنیم، بعد اومدیم گفتیم بیا این پیامی که کاربر میفرسته رو به دیتابیس اضافه کن این کانتر رو هم بزار اول اون پیام کاربر الان دیتا اینشکلی ذخیره شده داخل دیتابیس:DBشما میتونید داخل ipython یا شل پایتون و... اینکشلی به دیتابیس ردیس متصل بشید و query بزنید و باهاش کار کنید(البته دیتابیس های دیگه رو هم میتونید!)خب میتونید ببینید که جمله های ما اینشکلی به دیتابیس اضافه شدن، اون msg_counter که تعریف کردیم رو هم دارن که بعدا استفاده میکنیم.نکته: داخل ردیس میتونید با استفاده از متود sadd اون مقادیر رو برای اون کلیدی که میگید داخل یک set ذخیره کنید که الان اینجا کلیدمون Messages هستش و پیام کاربر رو ذخیره میکنیم داخلش این متود اینجا مفید هستش چون دیگه خیالمون راحت میشه که جمله تکراری وارد دیتابیس نمیشه وگرنه میتونستید از متود های rpush یا lpush هم استفاده کنید..اگر راجب متود sadd خواستید بیشتر بدونید متونید اینجا رو نگاه کنید.در ادامه یه شرط نوشتیم که اگر پیام برابر بود با save/ بیاد اون مقدار add:msg داخل دیتابیس رو برابر 0 بکنه و درواقع اون step رو قفل میکنه و دیگه شرط اول برقرار نمیشه، قبلا گفتیم شاید کاربر خواست چند تا پیام بفرسته اینجا هرچند تا بفرسته ذخیره میشه تهش save/ رو فرستاد دیگه ذخیره نمیشه، تهش یه پیامم به کاربر دادیم که جمله هاش ذخیره شد.داخل ترمینال هم همونطور که میبینید پرینتی که انجام دادیم اعمال شده و دقیقا اون پیام های test-0 تا 3 رو به دیتابیس اضافه کرده.Step-6-2: Group@Client.on_message(filters.private & filters.user(admin), group=1)اگر دقت کنید در کد بالا همچین هندلری نوشتیم اینجا ما یه مقدار جدید داریم و اون هم group هستش ، در پایروگرام اگه مثلا شما چند تا هندلر مثل هم داشته باشید موقع اجرا بقیه هندلر ها نادیده گرفته میشن و فقط هندلر اول اجرا میشه مثلا اینجا ما درسته فعلا هندلر مشابه نداریم ولی در اینده خواهیم داشت چون چندین بار میایم از کاربر ورودی میگیرم و هندلر ها هم یک فیلتر یکسانی رو دارن و مشابه هم هستن پس برای همین group رو مشخص میکنیم این group یه مقداری رو میگیره که اولویت اون هندلر رو مشخص میکنه هرچی عددش پایین تر باشه اولویت اجرا شدنش بیشتر یعنی اول اون اجرا میشه هرچی عدد بالاتر باشه یعنی اولویت کمتری داره .نکته: در اینجا شما میتونید به جای ذخیره پیام id پیام رو ذخیره کنید اینجوری در زمان ارسال این جمله ها میتونید عکس با کپشن یا فیلم یا اهنگ و... رو بفرستید چون شما id اون پیام رو ذخیره کردید نه متنش رو پس زمان ارسال هم میتونید بهش بگید که بیا این message رو ارسال کن.خروجی چیزی که نوشتیم تا الان:pyrogram-input-messageStep-7 : Show Messages@Client.on_message(filters.private & filters.user(admin) & filters.command(&quotshow_msg&quot)) async def show_msg(client, message): chat_id = message.chat.id await client.send_chat_action(chat_id=chat_id, action=TYPING) if query.exists(&quotMessages&quot): messages = &quot⚡️ **نمایش جمله ها** ⚡️\n&quot data = sorted(query.smembers(&quotMessages&quot), key=lambda x: x.split(&quot:&quot)[0]) for item in data: item = item.split(&quot:&quot, 1) separator = &quot➖&quot * 12 messages += f&quot`{item[0]}`:{item[1]}\n{separator}\n&quot await message.reply(text=messages) else: await message.reply(text=&quot❌جمله ای یافت نشد❌&quot)خب در کد بالا ما مثل قبل یه هندلر نوشتیم و فیلتر کردیم کامند show_msg رو، بعد اومدیم یه چت اکشن تایپینگ فرستادیم و در ادامه چک کردیم اگر اون کلید messages داخل دیتابیس وجود داشت بیاد اون مسیج/مسیج ها رو به کاربر نشون بده در غیر این صورت بیاد بهش بگه جمله ای یافت نشد.برای اینکه پیامی که به کاربر میفرستیم هم یکم تمیز تر باشه این قسمت for رو اضافه نوشتیم:اول اومدیم یه متغیر تعریف کردیم که در اخر همه جمله های داخل دیتابیس رو با هم بفرستیم و بعد اومدیم از متود smembers ردیس برای گرفتن تمام ولیو های اون کلیدمون استفاده کردیم ولی اینجا مقادیری که بهمون میده چون داخل set هستش ترتیب خاصی ندارن و نمایششون به اون شکل یکم زشت هست پس میایم با متود sorted مرتبشون میکنیم .اینجا داخل sorted کلید رو برابر اون عدد مربوط به هر پیام قرار دادیم تا با توجه به اون عدد ها سورت رو انجام بده و بعد حلقه زدیم روش، در ادامه داخل حلقه اومدیم هر پیام رو با : برای گرفتن اون عدد اسپلیت کردیم و بعد برای اینکه قشنگ تر بشه هر پیام رو با 12 تا ➖ جدا کردیم در ادامه اومدیم اینا رو به متغیر مون که بالا تعریف کردیم اضافه کریم. اینجا از backtick برای مونو اسپیس کردن اون تکست استفاده کردیم راجب تکست فرمتینگ میتونید این لینک رو مطالعه کنید.در نهایت همچین خروجیمون اینشکلیه:show-msg نکته: در هندلر بالاتر اومدیم group رو مشخص کردیم اگر این کار رو انجام نمیدادیم اصلا به این هندلری که الان نوشتیم نمیرسید و اجرا نمیشد چون show_msg هم باز تکست هستش اونجا این رو میگرفت و دیگه وارد هندلر دوم نمیشد تو همون هندلر بالاتر میموند مگر اینکه این هندلر رو بالاتر از اون هندلر قبل بنویسید اینجوری اول این اجرا میشد بعد اون هندلر به این شکل هم میشد ولی ست کردن group بهتر هستش.خب تا اینجا به نظرم برای این قسمت کافی هستش در قسمت بعدی ادامه این کد ها رو میریم جلو و اپشن های دیگه رو هم بهش اضافه میکنیم یه جا هایی رو ادیت میکنیم چون یه مشکلاتی هنوز هست که در قسمت بعدی بهش می پردازیم .اگر نظری انتقادی پیشنهادی چیزی داشتید میتونید کامنت بزارید✍️میتویند سورس کدی که نوشتیم رو هم در این لینک مشاهد کنید :‌ https://github.com/zankoAn/Pyrogram-Bolg-Project قسمت سوم: https://vrgl.ir/ZMRBE