12 β Maxsus xususiyatlar¶
β¬ οΈ Oldingi: 11 β Loyiha tuzilishi va konfiguratsiya Β· π README Β· Keyingi: 13 β Webhook va running mode β‘οΈ
Bu bobda: botingizni "tirik" qiladigan amaliy maxsus xususiyatlarni yig'amiz. Foydalanuvchi yuborgan faylni yuklab olish (
getFile->file_path->downloadUrl/file->url()/file->save()), bir necha media kelganda media group (albom) ni qabul qilish, lokatsiya va kontakt ni so'rash va o'qish (onMessageType(MessageType::LOCATION/CONTACT)+request_location/request_contacttugmalari), bot buyruqlar menyusi nisetMyCommandsva scope bilan moslash (default, shaxsiy chat, aniq foydalanuvchi), xabarni forward / copy qilish, jonli lokatsiya (live location) nionEditedMessageorqali kuzatish va WebApp tugmasi orqali Mini App'ni ochish (to'liq mavzu β 23-bob). Oxirida bularni birlashtiruvchi aralash amaliy retseptlar.Halol eslatma: bu bobdagi bot mantig'i β fayl yuklab olish oqimi, lokatsiya/kontakt handlerlari,
request_*tugmalari,setMyCommands+ scope, forward/copy va jonli lokatsiya βNutgram::fake()(FakeNutgram) yordamida offline sinab ko'rilgan (phpunit: 8 test / 22 assertion o'tdi). Jonli qism β faylning Telegram serveridan haqiqiy yuklab olinishi, lokatsiyaning real qurilmadan kelishi, menyuning Telegram ilovasida ko'rinishi β faqat ishlayotgan token va internetda namoyon bo'ladi; uni "ishladi" deb soxtalashtirmaymiz, balki tushuntirish sifatida beramiz.
Foydalanuvchi yuborgan faylni yuklab olish¶
Telegram fayllarni o'ziga xos tarzda beradi. Foydalanuvchi rasm, hujjat yoki ovozli xabar yuborganda, sizning botingiz faylning o'zini emas, balki uning qisqa identifikatori β file_id ni oladi. Faylni serveringizga yuklab olish uchun ikki qadam kerak:
getFile($file_id)β bu Telegram'danFileobyektini qaytaradi; uning eng muhim maydoni βfile_path(Telegram serveridagi nisbiy yo'l).- To'liq URL'ni qurish (
downloadUrl/file->url()) yoki to'g'ridan-to'g'ri diskka saqlash (downloadFile/file->save()).
Eng qisqa yo'l: file->save()¶
Nutgram'da File obyektining o'zida save() metodi bor β u ichida downloadFile ni chaqiradi, demak siz bitta satrda faylni diskka tushirasiz:
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Properties\MessageType;
$bot->onMessageType(MessageType::DOCUMENT, function (Nutgram $bot) {
$doc = $bot->message()->document; // Document obyekti
$file = $bot->getFile($doc->file_id); // 1-qadam: File (file_path bilan)
$file->save(__DIR__ . '/storage/' . $doc->file_name); // 2-qadam: diskka yozish
$bot->sendMessage("Fayl qabul qilindi: {$doc->file_name}");
});
save() ga katalog bersangiz (mavjud papka yo'li), Nutgram fayl nomini avtomatik tanlaydi:
URL kerak bo'lsa: file->url() / downloadUrl()¶
Faylni diskka emas, balki uning to'g'ridan-to'g'ri yuklash havolasini olmoqchi bo'lsangiz:
$file = $bot->getFile($doc->file_id);
$url = $file->url(); // yoki: $bot->downloadUrl($file)
// $url -> https://api.telegram.org/file/bot<TOKEN>/documents/file_5.pdf
Xavfsizlik β diqqat! Bu URL ichida bot tokeningiz bor (
/file/bot<TOKEN>/...). Kim bu havolani bilsa, tokenni ham biladi va botingizni nazoratga oladi. Shuning uchun bu URL'ni foydalanuvchiga hech qachon yubormang va logga ham yozmang. Uni faqat serveringiz ichida ishlating β masalan, Guzzle/cURL bilan o'zingiz yuklab olib, so'ng o'chiring.
onMessageType β turi bo'yicha ushlash¶
Yuqorida onMessageType(MessageType::DOCUMENT, ...) ishlatdik. Bu handler β xabar aynan shu turda bo'lganda ishga tushadi. MessageType enum'ida (SergiX44\Nutgram\Telegram\Properties\MessageType) bizga kerakli turlar:
| Tur | message maydoni | Qaysi fayl |
|---|---|---|
MessageType::PHOTO |
->photo (massiv: o'lchamlar) |
rasm |
MessageType::DOCUMENT |
->document |
istalgan fayl |
MessageType::VIDEO |
->video |
video |
MessageType::AUDIO |
->audio |
musiqa |
MessageType::VOICE |
->voice |
ovozli xabar |
MessageType::LOCATION |
->location |
lokatsiya |
MessageType::CONTACT |
->contact |
kontakt |
Rasm nozikligi:
message()->photoβ bu massiv (PhotoSize[]), chunki Telegram bir rasmni bir necha o'lchamda saqlaydi. Eng katta (eng sifatli) nusxa β odatda massivning oxirgi elementi:$bot->message()->photo[count(...)-1]->file_idyokiend($bot->message()->photo)->file_id.
$bot->onMessageType(MessageType::PHOTO, function (Nutgram $bot) {
$photos = $bot->message()->photo; // PhotoSize massivi
$largest = end($photos); // eng katta nusxa
$file = $bot->getFile($largest->file_id);
$file->save(__DIR__ . '/storage/photo_' . $largest->file_unique_id . '.jpg');
$bot->sendMessage('Rasm saqlandi.');
});
Cheklov:
getFileorqali yuklab olinadigan fayl hajmi 20 MB bilan chegaralangan (Telegram Bot API qoidasi). Undan kattaroq fayllar uchun o'zingizning lokal Bot API serveringizni ishga tushirib, Nutgram'niisLocal: truerejimida sozlash kerak (deploy mavzusi β 17-bob).
Media group (albom) qabul qilish¶
5-bobda biz albomni yuborishni ko'rdik (sendMediaGroup). Endi teskari tomon: foydalanuvchi bir necha rasmni bitta albom qilib yuborsa, Telegram ularni alohida-alohida xabarlar qilib jo'natadi, lekin har biriga bir xil media_group_id beradi. Botingiz bu xabarlarni guruhlash uchun shu ID'ga qaraydi.
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Properties\MessageType;
$bot->onMessageType(MessageType::PHOTO, function (Nutgram $bot) {
$msg = $bot->message();
if ($msg->media_group_id !== null) {
// Albomning bir qismi β vaqtincha to'plamga yig'amiz
$key = 'album:' . $msg->media_group_id;
$items = $bot->getUserData($key) ?? [];
$items[] = end($msg->photo)->file_id; // eng katta nusxa file_id'si
$bot->setUserData($key, $items);
// Soddalik uchun: birinchi rasmda javob beramiz
if (count($items) === 1) {
$bot->sendMessage('Albom qabul qilinmoqda...');
}
} else {
$bot->sendMessage('Bitta rasm qabul qilindi.');
}
});
Amaliy tafsilot: har bir albom xabari alohida update bo'lib kelgani uchun, "albom to'liq keldi" degan aniq signal yo'q. Real loyihada odatda qisqa debounce (masalan, 1-2 soniya kutib, shu vaqt ichida kelgan bir xil
media_group_idli xabarlarni yig'ish) ishlatiladi. Bu β keshga (Redis) bog'liq bo'lib, ko'pincha rejalashtirilgan/kechiktirilgan vazifa bilan birga qilinadi (15-bob). Yuqoridagi misol esa albom xabarlarinigetUserData/setUserData(11-bobda ko'rgan holat saqlash) bilan yig'ishning sodda ko'rinishi.
media_group_id β bu string (yoki yo'q bo'lsa null), demak uni shart bilan tekshirib, oddiy bitta rasmdan ajratamiz.
Lokatsiya va kontakt qabul qilish¶
Telegram foydalanuvchidan ikki maxsus narsani β joriy joylashuvi (lokatsiya) va telefon raqami (kontakt) ni so'rashga imkon beradi. Buning ikki tomoni bor:
- So'rash:
ReplyKeyboardMarkupichidagi maxsus tugmalar (request_location/request_contact). - Qabul qilish:
onMessageType(MessageType::LOCATION / CONTACT)handlerlari.
So'rash: request tugmalari¶
Bu tugmalar faqat ReplyKeyboardMarkup ichida ishlaydi β inline tugma bunday so'rovni yubora olmaydi (Telegram cheklovi).
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Types\Keyboard\KeyboardButton;
use SergiX44\Nutgram\Telegram\Types\Keyboard\ReplyKeyboardMarkup;
$bot->onCommand('joylashuv', function (Nutgram $bot) {
$kb = ReplyKeyboardMarkup::make(resize_keyboard: true, one_time_keyboard: true)
->addRow(
KeyboardButton::make('π Lokatsiyani yuborish', request_location: true),
)
->addRow(
KeyboardButton::make('π± Raqamni yuborish', request_contact: true),
);
$bot->sendMessage('Iltimos, joylashuvingiz yoki raqamingizni yuboring:', reply_markup: $kb);
});
Foydalanuvchi tugmani bossa, Telegram ruxsat so'raydi va tasdiqlasa β lokatsiya yoki kontakt xabar sifatida sizning botingizga keladi. Foydalanuvchi tugmani bosmasdan, klaviaturadagi "biriktirma -> Location/Contact" orqali ham yuborishi mumkin β handler ikkala holatda ham ishlaydi.
Qabul qilish: LOCATION va CONTACT handlerlari¶
use SergiX44\Nutgram\Telegram\Properties\MessageType;
$bot->onMessageType(MessageType::LOCATION, function (Nutgram $bot) {
$loc = $bot->message()->location; // Location obyekti
$bot->sendMessage(sprintf(
"Joylashuvingiz qabul qilindi:\nKenglik: %.5f\nUzunlik: %.5f",
$loc->latitude,
$loc->longitude,
));
});
$bot->onMessageType(MessageType::CONTACT, function (Nutgram $bot) {
$c = $bot->message()->contact; // Contact obyekti
$bot->sendMessage(
"Raqamingiz: {$c->phone_number}\n" .
"Ism: {$c->first_name}"
);
});
Location obyektining maydonlari: latitude, longitude, hamda jonli lokatsiya uchun live_period, heading, horizontal_accuracy. Contact obyektida: phone_number, first_name, last_name (ixtiyoriy), user_id (agar kontakt Telegram foydalanuvchisi bo'lsa), vcard.
Xavfsizlik nozikligi:
contact->user_idβ bu kontaktning Telegram ID'si, lekin u xabarni yuboruvchi ID'siga teng bo'lishi shart emas! Foydalanuvchi boshqa odamning kontaktini ham yuborishi mumkin. Agar siz "foydalanuvchining o'z raqamini" tasdiqlamoqchi bo'lsangiz,contact->user_id === $bot->userId()ekanini tekshiring.
Bot buyruqlar menyusi: setMyCommands va scope¶
Telegram'da matn maydoni yonidagi "/" tugmasini bosganda chiqadigan buyruqlar ro'yxati β bu setMyCommands orqali o'rnatiladigan menyu. Eng kuchli jihati β uni scope (qamrov) bilan turli foydalanuvchilarga turlicha ko'rsatish mumkin: hammaga bir xil, shaxsiy chatlarga boshqacha, adminga esa maxfiy buyruqlarni qo'shimcha.
Oddiy menyu (hammaga)¶
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Types\Command\BotCommand;
$bot->setMyCommands([
BotCommand::make('start', 'Botni ishga tushirish'),
BotCommand::make('yordam', 'Yordam olish'),
BotCommand::make('til', 'Tilni o\'zgartirish'),
]);
BotCommand::make($command, $description) β birinchi argument buyruq nomi (boshida / yozilmaydi, faqat start), ikkinchisi β menyuda ko'rinadigan tavsif. Bu odatda bir marta (deploy paytida yoki maxsus /setup buyrug'ida) chaqiriladi, har handlerda emas.
Scope bilan: adminlarga alohida menyu¶
setMyCommands ikkinchi argumenti β scope. Scope sinflari SergiX44\Nutgram\Telegram\Types\Command\ ichida:
use SergiX44\Nutgram\Telegram\Types\Command\BotCommand;
use SergiX44\Nutgram\Telegram\Types\Command\BotCommandScopeChat;
use SergiX44\Nutgram\Telegram\Types\Command\BotCommandScopeAllPrivateChats;
// 1) Shaxsiy chatlardagilar uchun:
$bot->setMyCommands(
[
BotCommand::make('profil', 'Profilim'),
BotCommand::make('balans', 'Balansim'),
],
scope: new BotCommandScopeAllPrivateChats(),
);
// 2) Aniq bir adminga (uning user_id si bo'yicha):
$adminId = 123456789;
$bot->setMyCommands(
[
BotCommand::make('panel', 'Admin panel'),
BotCommand::make('statistika', 'Statistika'),
BotCommand::make('broadcast', 'Ommaviy xabar'),
],
scope: BotCommandScopeChat::make($adminId),
);
Nutgram nozikligi (
makestatic emas!): bu kitob versiyasida (Nutgram 4.46) parametr qabul qiladigan scope'lar βBotCommandScopeChat::make($chatId),BotCommandScopeChatMember::make(...)βstaticmake()ga ega, ya'ni::make(...)ishlatiladi. Lekin parametrsiz scope'lar βBotCommandScopeAllPrivateChats,BotCommandScopeAllGroupChats,BotCommandScopeAllChatAdministrators,BotCommandScopeDefaultβ damake()staticemas, shuning uchun ularninew BotCommandScopeAllPrivateChats()deb yaratish kerak (::make()ishlamaydi, "non-static method ... cannot be called statically" xatosini beradi). Bu nozik tafsilotni bilmaslik chalkashlikka olib keladi, shuning uchun aniqlab oldik.
Scope'lar qanday birlashadi? Telegram eng aniq (eng tor) scope'dan boshlab buyruqlar ro'yxatini quradi. Demak admin /panel (Chat scope), /profil (AllPrivateChats), /start (default) β uchchalasini ham ko'radi. Oddiy foydalanuvchi esa faqat /profil va /start ni ko'radi. Shu tufayli adminga maxfiy buyruqlarni faqat uning Chat scope'iga qo'shasiz β boshqalar ularni menyusida umuman ko'rmaydi (lekin diqqat: bu menyuni yashiradi, kirishni emas β buyruqning o'zini handlerda haqlik bilan himoyalash kerak; bu β middleware mavzusi, 9-bob).
Menyu tugmasi (chap pastdagi tugma)¶
Matn maydonining chap tomonidagi menyu tugmasini ham boshqarish mumkin (setChatMenuButton). U buyruqlarni ko'rsatishi yoki WebApp ochishi mumkin β pastdagi "WebApp tugmasi" bo'limida ko'ramiz.
Xabarni forward va copy qilish¶
Bu metodlarni 5-bobda qisqacha ko'rgandik; bu yerda ularni amaliy retsept bilan mustahkamlaymiz, chunki ko'p maxsus xususiyatlar (e'lon tarqatish, kanaldan ko'chirish) shularga tayanadi.
forwardMessage β xabarni "Forwarded from ..." yorlig'i bilan, asl manbani ko'rsatib uzatadi:
copyMessage β mazmunni nusxalaydi, lekin yorliqsiz (go'yo yangi xabar). Caption'ni qayta yozish ham mumkin:
$bot->copyMessage(
chat_id: $maqsadChatId,
from_chat_id: $manbaChatId,
message_id: $xabarId,
caption: 'Yangilangan izoh', // ixtiyoriy
);
Foydali amaliy holat: "savol-javob" boti. Foydalanuvchi botga yozadi, bot xabarni admin chatiga
copyMessagebilan ko'chiradi (manbani yashirib), admin javobini esa qaytadan foydalanuvchigacopyMessageqiladi β natijada anonim ko'prik hosil bo'ladi.
Jonli lokatsiya (live location)¶
Foydalanuvchi oddiy lokatsiya o'rniga jonli lokatsiya (15 daqiqadan 8 soatgacha real vaqtda yangilanadigan) yuborishi mumkin. Bunda eng muhim tushuncha: koordinata o'zgarganda Telegram yangi xabar yubormaydi, balki o'sha xabarni tahrirlaydi. Demak bu β edited_message update'i. Uni onEditedMessage bilan ushlaymiz:
use SergiX44\Nutgram\Nutgram;
$bot->onEditedMessage(function (Nutgram $bot) {
$loc = $bot->message()->location; // edited_message ham message() orqali keladi
if ($loc === null) {
return; // tahrir lokatsiya bilan bog'liq emas
}
// Jonli lokatsiya yangilandi β yangi koordinata
$bot->sendMessage(sprintf(
'Yangi joylashuv: %.5f, %.5f',
$loc->latitude,
$loc->longitude,
));
});
Nozik nuqta:
onEditedMessageβ har qanday xabar tahriri uchun ishlaydi (matn tahriri ham). Shu sababli ichidaif ($loc === null) return;bilan faqat lokatsiyali tahrirlarni ajratamiz. Birinchi (jonli) lokatsiya esa odatdagidekonMessageType(MessageType::LOCATION)orqali keladi β unilocation->live_period !== nullbo'yicha jonli ekanidan ajratish mumkin.
Bot o'zi jonli lokatsiya yuborib, uni yangilab turishi ham mumkin: sendLocation(..., live_period: 600) bilan yuborib, keyin editMessageLiveLocation(latitude, longitude, message_id: ...) bilan yangilaysiz va stopMessageLiveLocation bilan to'xtatasiz. Bu β kuryer/taksi kuzatuvi kabi botlar uchun.
WebApp tugmasi orqali Mini App'ni ochish¶
Maxsus xususiyatlarning eng zamonaviysi β Telegram Web App (Mini App): bot ichida to'liq HTML/JS sahifa ochiladigan tugma. Bu yerda faqat kirish nuqtasini ko'rsatamiz; to'liq mavzu β 23-bob (asoslari), 24-bob (xavfsizlik: initData) va 25-bob (backend).
Mini App'ni ochishning ikki asosiy yo'li:
1) Inline tugma orqali (xabar ichida):
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardButton;
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardMarkup;
use SergiX44\Nutgram\Telegram\Types\WebApp\WebAppInfo;
$bot->onCommand('app', function (Nutgram $bot) {
$kb = InlineKeyboardMarkup::make()->addRow(
InlineKeyboardButton::make(
'Ilovani ochish',
web_app: WebAppInfo::make(url: 'https://example.com/miniapp'),
),
);
$bot->sendMessage('Mini App tayyor:', reply_markup: $kb);
});
2) Menyu tugmasi orqali (chap pastdagi doimiy tugma):
use SergiX44\Nutgram\Telegram\Types\Command\MenuButtonWebApp;
use SergiX44\Nutgram\Telegram\Types\WebApp\WebAppInfo;
// Diqqat: MenuButtonWebApp::make() bu versiyada static EMAS β `new` ishlatamiz
$bot->setChatMenuButton(menu_button: new MenuButtonWebApp(
text: 'Ilova',
web_app: WebAppInfo::make(url: 'https://example.com/miniapp'),
));
Eslatma: WebApp tugmasidagi URL HTTPS bo'lishi shart (Telegram talabi). Mini App backend'i foydalanuvchini tasdiqlash uchun har so'rovda
initDatani serverda tekshiradi β bu xavfsizlikning kaliti va 24-bobda batafsil. Hozircha shuni yodda tuting: WebApp tugmasi β bu shunchaki HTTPS sahifani ochadigan kirish nuqtasi.
Aralash amaliy retsept: "Profil to'ldirish" boti¶
Endi bir nechta xususiyatni bitta oqimga birlashtiramiz: foydalanuvchidan rasm, lokatsiya va kontakt so'rab, hammasini yig'ib, profil sifatida saqlaymiz. (To'liq ko'p-qadamli oqim uchun Conversations β 8-bob; bu yerda soddalashtirilgan handler-asosli ko'rinish.)
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Properties\MessageType;
use SergiX44\Nutgram\Telegram\Types\Command\BotCommand;
use SergiX44\Nutgram\Telegram\Types\Keyboard\KeyboardButton;
use SergiX44\Nutgram\Telegram\Types\Keyboard\ReplyKeyboardMarkup;
// 1) Menyuni o'rnatamiz (bir marta)
$bot->onCommand('setup', fn (Nutgram $bot) => $bot->setMyCommands([
BotCommand::make('profil', 'Profil to\'ldirish'),
]));
// 2) Profil to'ldirishni boshlash
$bot->onCommand('profil', function (Nutgram $bot) {
$bot->setUserData('profil', []); // bo'sh profil
$kb = ReplyKeyboardMarkup::make(resize_keyboard: true)
->addRow(KeyboardButton::make('π Lokatsiya', request_location: true))
->addRow(KeyboardButton::make('π± Raqam', request_contact: true));
$bot->sendMessage('Avval rasm yuboring, so\'ng lokatsiya va raqam:', reply_markup: $kb);
});
// 3) Rasm
$bot->onMessageType(MessageType::PHOTO, function (Nutgram $bot) {
$p = $bot->getUserData('profil');
if ($p === null) return; // profil rejimida emas
$p['photo'] = end($bot->message()->photo)->file_id;
$bot->setUserData('profil', $p);
$bot->sendMessage('Rasm qabul qilindi. Endi lokatsiya yuboring.');
});
// 4) Lokatsiya
$bot->onMessageType(MessageType::LOCATION, function (Nutgram $bot) {
$p = $bot->getUserData('profil');
if ($p === null) return;
$loc = $bot->message()->location;
$p['lat'] = $loc->latitude;
$p['lon'] = $loc->longitude;
$bot->setUserData('profil', $p);
$bot->sendMessage('Joylashuv qabul qilindi. Endi raqam yuboring.');
});
// 5) Kontakt -> yakun
$bot->onMessageType(MessageType::CONTACT, function (Nutgram $bot) {
$p = $bot->getUserData('profil');
if ($p === null) return;
$p['phone'] = $bot->message()->contact->phone_number;
$bot->setUserData('profil', $p);
$bot->sendMessage(sprintf(
"Profil to'ldirildi!\nRasm: %s\nJoylashuv: %.4f, %.4f\nRaqam: %s",
isset($p['photo']) ? 'bor' : 'yo\'q',
$p['lat'] ?? 0,
$p['lon'] ?? 0,
$p['phone'],
));
$bot->setUserData('profil', null); // tozalash
});
Bu oqim handler-asosli va soddalashtirilgan (har qadamda
getUserData('profil')borligini tekshiramiz). Real loyihada Conversations (8-bob) toza, izchil holat boshqaruvini beradi; ma'lumotni doimiy saqlash uchun esa bazaga yozasiz (10-bob).
Offline tekshiruv: FakeNutgram bilan¶
Bu bobning barcha mantig'i jonli token bo'lmasa ham sinab ko'rildi. Nutgram::fake() haqiqiy HTTP so'rovlarni emas, balki ularni yozib boradigan soxta bot beradi; lokatsiya/kontakt/hujjat update'larini hearMessage([...]) bilan "kiritamiz", getFile javobini esa willReceive([...]) bilan soxtalashtiramiz. (To'liqroq β 16-bob.)
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Properties\MessageType;
$bot = Nutgram::fake();
$bot->onMessageType(MessageType::DOCUMENT, function (Nutgram $bot) {
$doc = $bot->message()->document;
$file = $bot->getFile($doc->file_id);
$bot->sendMessage("Fayl: {$doc->file_name}, yo'l: {$file->file_path}");
});
// getFile javobini soxtalashtiramiz:
$bot->willReceive([
'file_id' => 'DOC123',
'file_unique_id' => 'u1',
'file_size' => 2048,
'file_path' => 'documents/file_5.pdf',
]);
// Hujjatli xabarni "kiritamiz":
$bot->hearMessage([
'document' => [
'file_id' => 'DOC123',
'file_unique_id' => 'u1',
'file_name' => 'hisobot.pdf',
'mime_type' => 'application/pdf',
],
])->reply();
// getFile chaqirilgani va to'g'ri javob yuborilgani tasdiqlanadi
$bot->assertCalled('getFile', 1);
$bot->assertReply('sendMessage', [
'text' => 'Fayl: hisobot.pdf, yo\'l: documents/file_5.pdf',
], index: 1); // index 0 β getFile, index 1 β sendMessage
Tekshiruvga oid muhim nozik nuqta:
getFileham bir API chaqiruvi, shuning uchun u so'rovlar tarixida 0-indeksda turadi,sendMessageesa 1-indeksda.assertReply(..., index: 1)ni shuning uchun aniq ko'rsatdik. Lokatsiya/kontakt handlerlarida esa boshqa API chaqiruv yo'q, shuning uchunassertReplyText('...')indekssiz ishlaydi.
Lokatsiya handlerini tekshirish:
$bot = Nutgram::fake();
$bot->onMessageType(MessageType::LOCATION, function (Nutgram $bot) {
$loc = $bot->message()->location;
$bot->sendMessage(sprintf('Lokatsiya: %.4f, %.4f', $loc->latitude, $loc->longitude));
});
$bot->hearMessage(['location' => ['latitude' => 41.3111, 'longitude' => 69.2797]])->reply();
$bot->assertReplyText('Lokatsiya: 41.3111, 69.2797');
Bu bobning kodi aynan shunday testlar bilan tekshirildi (fayl yuklash mantig'i, lokatsiya/kontakt handlerlari,
request_*tugmalari serializatsiyasi,setMyCommands+ scope, forward/copy, jonli lokatsiya β jami 8 test / 22 assertion o'tdi). Demak handler mantig'i haqiqatan ishlaydi. Faqat faylning Telegram serveridan real yuklab olinishi, lokatsiyaning haqiqiy qurilmadan kelishi va menyuning ilovada ko'rinishi jonli muhitda tasdiqlanadi.
Mashqlar¶
Oson¶
onMessageType(MessageType::VOICE, ...)bilan ovozli xabarni qabul qilib, "Ovozli xabar qabul qilindi" deb javob yozuvchi handler yozing.MessageType::PHOTOhandleridagimessage()->photonima uchun massiv ekanini va eng katta nusxani qanday olishni bir-ikki jumlada tushuntiring.BotCommand::make('start', 'Boshlash')da buyruq nomining boshida/yozilishi kerakmi? Javobingizni asoslang.request_location: truetugmasi qaysi klaviatura turida ishlaydi βReplyKeyboardMarkupmi yokiInlineKeyboardMarkup? Nega?file->url()qaytargan havolada nima xavfli, nega uni foydalanuvchiga yubormaslik kerak β bir jumlada yozing.MessageType::CONTACThandleridacontact->phone_numbervacontact->first_nameni o'qib, bitta xabarda yuboruvchi handler yozing.
O'rta¶
- Foydalanuvchi hujjat yuborsa, uni
__DIR__ . '/storage/'gafile->save()bilan saqlab, "saqlandi" deb javob yozuvchi handler yozing. /menyubuyrug'iga ikkitarequesttugmali (request_location,request_contact)ReplyKeyboardMarkupyuboruvchi handler yozing (ikkala tugma alohida qatorda).setMyCommandsni ikki marta chaqiring: birinchisi default (hammaga), ikkinchisiBotCommandScopeAllPrivateChatsbilan (shaxsiy chatlarga). Har birida 2 ta buyruq bo'lsin.AllPrivateChatsni qanday yaratasiz β::make()mi yokinew?onEditedMessagehandler yozing: agar tahrirlangan xabardalocationbo'lsa, yangi koordinatani yuborsin; bo'lmasa, hech narsa qilmasin.forwardMessagevacopyMessagefarqini ko'rsatuvchi ikki handler yozing va izohda qaysi biri manbani yashirishini ayting.contact->user_id === $bot->userId()tekshiruvi nima uchun kerakligini (foydalanuvchi boshqaning kontaktini yuborishi mumkinligini) tushuntiring va shu tekshiruvli kontakt handlerini yozing.
Qiyin¶
- Albom qabul qiluvchi handler yozing:
media_group_idbor bo'lsa, shu ID bo'yichasetUserData('album:'.$id, [...])ga rasmfile_idlarini yig'sin; yo'q bo'lsa, "bitta rasm" deb javob bersin. Nutgram::fake()bilan hujjat yuklash handlerini tekshiruvchi test yozing:willReceivebilangetFilejavobini bering,hearMessagebilan hujjatli xabar kiriting, so'ngassertCalled('getFile', 1)vaassertReply('sendMessage', [...], index: 1)bilan tasdiqlang.- "Anonim ko'prik" boti yozing: foydalanuvchi yozgan xabarni admin chatiga
copyMessagebilan ko'chirsin (manbani yashirib). Admin ID'sini o'zgaruvchidan oling. - Bobning "Profil to'ldirish" retseptini Conversations (8-bob) bilan qayta yozing:
start->photo->location->contact->endqadamlari bilan. (Eslatma: live emas, oddiy oqim.)
Yechimlar
Oson¶
1.
$bot->onMessageType(MessageType::VOICE, fn (Nutgram $bot) =>
$bot->sendMessage('Ovozli xabar qabul qilindi'));
2. message()->photo massiv, chunki Telegram bir rasmni bir necha o'lchamda (thumbnail'dan to to'liq sifatgacha) saqlaydi. Eng katta (eng sifatli) nusxa β massivning oxirgi elementi: end($bot->message()->photo)->file_id.
3. Yo'q, / yozilmaydi. BotCommand::make('start', ...) da faqat start beriladi; Telegram menyuda uni /start qilib ko'rsatadi. /start deb yozsangiz, buyruq //start bo'lib buziladi.
4. Faqat ReplyKeyboardMarkup da. Inline tugmalar callback_data, url yoki web_app bilan ishlaydi, lekin lokatsiya/kontakt so'rovini yubora olmaydi β bu Telegram'ning maxsus reply-tugma imkoniyati.
5. file->url() ichida bot tokeni bor (/file/bot<TOKEN>/...); kim havolani bilsa, tokenni biladi va botni egallaydi. Shuning uchun uni foydalanuvchiga yubormaslik kerak.
6.
$bot->onMessageType(MessageType::CONTACT, function (Nutgram $bot) {
$c = $bot->message()->contact;
$bot->sendMessage("Raqam: {$c->phone_number}, Ism: {$c->first_name}");
});
O'rta¶
1.
$bot->onMessageType(MessageType::DOCUMENT, function (Nutgram $bot) {
$doc = $bot->message()->document;
$file = $bot->getFile($doc->file_id);
$file->save(__DIR__ . '/storage/' . $doc->file_name);
$bot->sendMessage("{$doc->file_name} saqlandi");
});
2.
$bot->onCommand('menyu', function (Nutgram $bot) {
$kb = ReplyKeyboardMarkup::make(resize_keyboard: true)
->addRow(KeyboardButton::make('π Lokatsiya', request_location: true))
->addRow(KeyboardButton::make('π± Raqam', request_contact: true));
$bot->sendMessage('Tanlang:', reply_markup: $kb);
});
3. AllPrivateChats parametrsiz, shuning uchun new BotCommandScopeAllPrivateChats() (uning make() static emas).
$bot->setMyCommands([
BotCommand::make('start', 'Boshlash'),
BotCommand::make('yordam', 'Yordam'),
]);
$bot->setMyCommands([
BotCommand::make('profil', 'Profilim'),
BotCommand::make('balans', 'Balansim'),
], scope: new BotCommandScopeAllPrivateChats());
4.
$bot->onEditedMessage(function (Nutgram $bot) {
$loc = $bot->message()->location;
if ($loc === null) return;
$bot->sendMessage(sprintf('Yangi joylashuv: %.5f, %.5f', $loc->latitude, $loc->longitude));
});
5.
// forwardMessage β "Forwarded from ..." yorlig'i bilan, manbani OSHKOR qiladi.
$bot->onCommand('uzat', fn (Nutgram $bot) => $bot->forwardMessage(
chat_id: $bot->chatId(), from_chat_id: $bot->chatId(), message_id: 100,
));
// copyMessage β yorliqsiz "toza" nusxa, manbani YASHIRADI.
$bot->onCommand('nusxa', fn (Nutgram $bot) => $bot->copyMessage(
chat_id: $bot->chatId(), from_chat_id: $bot->chatId(), message_id: 100,
));
6. Foydalanuvchi tugma orqali o'z raqamini yuboradi, lekin biriktirma orqali boshqa odamning kontaktini ham yuborishi mumkin β u holda contact->user_id jo'natuvchiga teng bo'lmaydi. "O'z raqamini" tasdiqlash uchun tekshiramiz:
$bot->onMessageType(MessageType::CONTACT, function (Nutgram $bot) {
$c = $bot->message()->contact;
if ($c->user_id !== $bot->userId()) {
$bot->sendMessage('Iltimos, o\'zingizning raqamingizni yuboring.');
return;
}
$bot->sendMessage("Raqamingiz tasdiqlandi: {$c->phone_number}");
});
Qiyin¶
1.
$bot->onMessageType(MessageType::PHOTO, function (Nutgram $bot) {
$msg = $bot->message();
if ($msg->media_group_id !== null) {
$key = 'album:' . $msg->media_group_id;
$items = $bot->getUserData($key) ?? [];
$items[] = end($msg->photo)->file_id;
$bot->setUserData($key, $items);
} else {
$bot->sendMessage('Bitta rasm');
}
});
2.
$bot = Nutgram::fake();
$bot->onMessageType(MessageType::DOCUMENT, function (Nutgram $bot) {
$doc = $bot->message()->document;
$file = $bot->getFile($doc->file_id);
$bot->sendMessage("Fayl: {$doc->file_name}, yo'l: {$file->file_path}");
});
$bot->willReceive([
'file_id' => 'DOC123', 'file_unique_id' => 'u1',
'file_size' => 2048, 'file_path' => 'documents/file_5.pdf',
]);
$bot->hearMessage(['document' => [
'file_id' => 'DOC123', 'file_unique_id' => 'u1',
'file_name' => 'hisobot.pdf', 'mime_type' => 'application/pdf',
]])->reply();
$bot->assertCalled('getFile', 1);
$bot->assertReply('sendMessage', [
'text' => 'Fayl: hisobot.pdf, yo\'l: documents/file_5.pdf',
], index: 1);
3.
$adminChatId = 999000111; // admin chat ID
$bot->onMessage(function (Nutgram $bot) use ($adminChatId) {
// Faqat oddiy foydalanuvchidan kelgan xabarni adminga ko'chiramiz
if ($bot->chatId() === $adminChatId) return;
$bot->copyMessage(
chat_id: $adminChatId,
from_chat_id: $bot->chatId(),
message_id: $bot->messageId(),
);
});
// copyMessage manbani yashiradi -> admin kim yozganini "forward" yorlig'isiz ko'radi
// (real botda foydalanuvchi ID'sini alohida xabarda yoki bazada bog'lab qo'yiladi).
4.
use SergiX44\Nutgram\Conversations\Conversation;
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Properties\MessageType;
class ProfilForma extends Conversation
{
public ?string $photo = null;
public ?float $lat = null;
public ?float $lon = null;
public function start(Nutgram $bot)
{
$bot->sendMessage('Rasm yuboring:');
$this->next('photo');
}
public function photo(Nutgram $bot)
{
if ($bot->message()?->getType() !== MessageType::PHOTO) {
$bot->sendMessage('Iltimos, rasm yuboring.');
return; // shu qadamda qolamiz
}
$this->photo = end($bot->message()->photo)->file_id;
$bot->sendMessage('Endi lokatsiya yuboring:');
$this->next('location');
}
public function location(Nutgram $bot)
{
$loc = $bot->message()?->location;
if ($loc === null) {
$bot->sendMessage('Iltimos, lokatsiya yuboring.');
return;
}
$this->lat = $loc->latitude;
$this->lon = $loc->longitude;
$bot->sendMessage('Endi raqam yuboring:');
$this->next('contact');
}
public function contact(Nutgram $bot)
{
$c = $bot->message()?->contact;
if ($c === null) {
$bot->sendMessage('Iltimos, raqam yuboring.');
return;
}
$bot->sendMessage(sprintf(
"Profil tayyor!\nRasm: %s\nJoylashuv: %.4f, %.4f\nRaqam: %s",
$this->photo ? 'bor' : 'yo\'q', $this->lat, $this->lon, $c->phone_number,
));
$this->end();
}
}
// Boshlash:
$bot->onCommand('profil', fn (Nutgram $bot) => ProfilForma::begin($bot));
β¬ οΈ Oldingi: 11 β Loyiha tuzilishi va konfiguratsiya Β· π README Β· Keyingi: 13 β Webhook va running mode β‘οΈ