IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime23/69-dars14 daqiqa
Mundarija (24)

Media, fayllar va formatlash: Telegram botda rasm, hujjat va HTML

Qisqacha: Faylni Telegram'ga bir marta yuklang — javobda file_id keladi. Keyingi safar baytlarni emas, shu satrni yuboring: kanonda 300 KB rasm yuklash 30.8 ms, file_id bilan 2.4 ms bo'ldi. Bot API orqali 50 MB gacha yuborasiz va 20 MB gacha yuklab olasiz (getFile); kattaroq fayl uchun o'z local Bot API serveringiz kerak. Matnni HTML bilan formatlasangiz, foydalanuvchi matnidagi &, <, > ni ekranlang — aks holda Telegram xabarni rad etadi.

Bu darsda

  • Fayl yuborishning uch yo'lini (yuklash, file_id, URL) va ular farqini tushunasiz.
  • sendMediaGroup bilan albom yuborasiz va uning cheklovlarini bilasiz.
  • Foydalanuvchi yuborgan faylni getFile bilan yuklab olasiz.
  • 20 MB / 50 MB chegaralari va local Bot API server haqida bilasiz.
  • HTML va MarkdownV2 formatlash, ekranlash va formatlash orqali hujumni tushunasiz.

Oldin bilishingiz kerak: Sessiya va ko'p qadamli dialoglar, Fayl yuklash: multipart va Multer, Fayl yuklash xavfsizligi.

Oldingi darslarda bot faqat matn yubordi. Bugun rasm, hujjat va formatlangan xabar bilan ishlaymiz. Bu dars — tajriba darsi: loyiha boti faqat matnli eslatma yuboradi, lekin HTML ekranlash unda bor va bugun uni chuqur ko'ramiz.

1. Nega bu kerak?

Bot ko'p hollarda fayl bilan ishlaydi:

  • do'kon boti mahsulot rasmini yuboradi;
  • buxgalteriya boti PDF hisobot yuboradi;
  • foydalanuvchi chek rasmini yoki hujjatni botga yuboradi, bot uni saqlaydi.

Uchta savol tug'iladi: bir xil rasmni 10 000 foydalanuvchiga qanday tez yuborish? Foydalanuvchi yuborgan faylni qanday olish? Qancha hajmgacha mumkin?

O'xshatish: kutubxona. Kitobni birinchi marta topshirganingizda kutubxonachi unga inventar raqami yopishtiradi. Keyin "shu kitobni falonchiga bering" deyish uchun kitobni ko'tarib yurmaysiz — raqamni aytasiz. file_id — o'sha inventar raqami.

2. Fayl yuborish

2.1 Uch yo'l

Bot API'da sendPhoto, sendDocument, sendAudio, sendVideo, sendVoice metodlari faylni uch shaklda oladi:

Shakl Nima ketadi Qachon
fayl baytlari multipart/form-data (multipart) birinchi marta
file_id satri qisqa satr qayta yuborish
HTTP URL havola, Telegram o'zi yuklaydi ochiq internetdagi fayl

grammY'da baytlar InputFile bilan beriladi — Buffer, fayl yo'li yoki oqim:

js
import { InputFile } from "grammy";

const m1 = await bot.api.sendPhoto(50,
  new InputFile(rasmBuffer, "r.jpg"),
  { caption: "<b>birinchi</b>", parse_mode: "HTML" });

const fileId = m1.photo.at(-1).file_id;
await bot.api.sendPhoto(50, fileId, { caption: "file_id bilan" });

m1.photo — massiv: Telegram rasmni bir nechta o'lchamda saqlaydi, .at(-1) — eng kattasi. Kanon mock'i bitta o'lcham qaytaradi; haqiqiy Telegram bir nechta qaytaradi (hujjat bo'yicha).

2.2 Kanon o'lchovi

Kurs tajribasi 300 KB rasmni avval multipart bilan, keyin file_id bilan yubordi:

text
yuklash (300 KB multipart) 30.8 ms; file_id bilan 2.4 ms; file_id: SOXTAALVQR06…
sendPhoto chaqiruvlarida photo parametri: string, string (1-si multipart fayl, 2-si satr)
  • Vaqt 13 barobar kam: baytlar qayta ketmadi. Bu — lokal mock, tarmoq yo'q. Haqiqiy internetda 300 KB'ni Telegram serveriga yuklash ancha uzoq, farq yanada katta bo'ladi.
  • SOXTA… — mock yaratgan id; haqiqiy file_id boshqacha ko'rinadi va har ishga tushishda boshqa. Uni hech qachon "taxmin" qilmang.
  • Ikkinchi qator qiziq: ikkala chaqiruvda ham photo — satr. Birinchisida grammY uni attach://<nom> shaklida yuboradi va fayl baytlarini multipart'ning alohida qismiga qo'yadi. Kanon mock'i dastlab buni bilmagan va xato bergan — tuzatilgan.

Millisekundlar har ishga tushishda boshqacha chiqadi. Aniq raqamni emas, farqning tartibini eslab qoling: file_id bilan yuborish o'n barobardan ko'p tez.

2.3 file_id qoidalari

Telegram hujjati bo'yicha:

  • file_id — faylni yuklab olish yoki qayta yuborish uchun. U botga xos: boshqa bot sizning file_id ingizni ishlata olmaydi.
  • file_unique_id — bir fayl uchun vaqt o'tsa ham va turli botlarda ham bir xil. Lekin u bilan fayl yuborib yoki yuklab bo'lmaydi — faqat "bu o'sha fayl" deb solishtirish uchun.

Amaliy naqsh: mahsulot rasmini birinchi yuborganda file_id ni bazaga yozing. Keyin 10 000 foydalanuvchiga — faqat satr.

3. Albom: sendMediaGroup

Bir nechta rasmni bitta "albom" qilib yuborish:

js
const g = await bot.api.sendMediaGroup(50, [
  { type: "photo", media: fileId, caption: "albom" },
  { type: "photo", media: new InputFile(boshqaRasm, "b.jpg") },
]);

Kanon:

text
sendMediaGroup: 2 xabar, bitta media_group_id: true
1 ta media: Bad Request: wrong number of messages to send
  • Natija — xabarlar massivi: albom Telegram uchun alohida xabarlar, ularni umumiy media_group_id birlashtiradi.
  • Hujjatga ko'ra albomda 2–10 element. Bitta element — xato (mock xato matni haqiqiy serverniki bilan solishtirilmagan).
  • file_id va yangi fayl bitta albomda aralashishi mumkin.

Bot qabul qilganda ham albom alohida update'lar bo'lib keladi — har rasm o'z message i bilan, bir xil media_group_id. Ularni birlashtirish — sizning vazifangiz (masalan, qisqa vaqt kutib, id bo'yicha yig'ish).

4. Faylni yuklab olish: getFile

Foydalanuvchi botga hujjat yubordi. Update'da faqat file_id bor, baytlar yo'q. Yuklab olish ikki qadam:

  1. getFile(file_id) → file_path (Telegram serveridagi nisbiy yo'l).
  2. GET https://api.telegram.org/file/bot<token>/<file_path> — baytlar.
js
const f = await bot.api.getFile(fileId);
const javob = await fetch(
  `https://api.telegram.org/file/bot${TOKEN}/${f.file_path}`,
);
const baytlar = Buffer.from(await javob.arrayBuffer());

Kanon (mock manzili bilan):

text
getFile: {"file_path":"photos/file_1.jpg","file_size":307200} → yuklab olish 200 307200 bayt
21 MB getFile: Bad Request: file is too big (cloud Bot API limiti 20 MB; local Bot API server — 2000 MB, hujjat bo'yicha, sinalmagan)
noto'g'ri file_id: Bad Request: wrong file identifier/HTTP URL specified

Diqqat: Yuklab olish havolasida bot tokeni bor. Uni logga yozmang va foydalanuvchiga ko'rsatmang. Faylni o'zingiz yuklab, o'z omboringizga (Object storage: S3 va MinIO) qo'ying va o'z havolangizni bering.

Foydalanuvchi faylidan keyingi qadam — Fayl yuklash xavfsizligi darsidagi hamma narsa: hujjat nomi va MIME turi mijozdan keladi, haqiqiy turni sehrli baytlardan aniqlang. Telegram faylni tekshirmaydi.

5. Hajm chegaralari

flowchart TD
  F[Fayl] --> Y{Bot yuboradimi?}
  Y -->|ha| H{50 MB dan kichikmi?}
  H -->|ha| C[Bot API cloud]
  H -->|yo'q| L[local Bot API server]
  Y -->|yo'q, oladi| G{20 MB dan kichikmi?}
  G -->|ha| C
  G -->|yo'q| L
  C --> I[file_id saqlanadi]
  L --> I

Diagrammaga qarang: yuborish va yuklab olishning chegarasi har xil. Telegram hujjati (2026-10-28 da o'qildi):

Amal Cloud Bot API Local Bot API server
bot fayl yuboradi 50 MB gacha 2000 MB gacha
bot fayl yuklab oladi 20 MB gacha cheklovsiz
xabar matni 1–4096 belgi
rasm/hujjat izohi (caption) 0–1024 belgi

Matn chegaralari — "entity'lar tahlil qilingandan keyingi" belgilar, ya'ni <b> teglari hisobga kirmaydi.

Local Bot API server — Telegram'ning ochiq kodli serveri (tdlib/telegram-bot-api). Uni o'z serveringizda ishga tushirasiz, bot api.telegram.org o'rniga unga ulanadi (grammY'da apiRoot). Katta fayllar shu server orqali o'tadi. Bu kursda sinalmagan — faqat hujjat bo'yicha.

Kanon mock'i 21 MB faylni qabul qildi (50 MB dan kichik), lekin getFile da "file is too big" berdi — chegaralarning farqi aynan shu.

Tekshirib ko'ring: Foydalanuvchi botga 30 MB video yubordi. Bot uni qabul qilib, boshqa chatga qayta yubora oladimi? Yuklab ola oladimi?

Javob

Qayta yubora oladi — file_id bilan; baytlar Telegram ichida qoladi, hajm chegarasi tegmaydi. Yuklab ololmaydi — getFile 20 MB dan katta faylni rad etadi (kanon: 21 MB → "file is too big"). Yuklab olish kerak bo'lsa — local Bot API server.

6. Formatlash

6.1 parse_mode

Xabar matnida qalin, kursiv, havola, kod bo'lishi mumkin. Buni ikki "til"da yozish mumkin: parse_mode: "HTML" yoki "MarkdownV2". Uchinchi yo'l — entities maydoni (ofsetlar bilan), lekin u qo'lda noqulay.

HTML'da faqat bir nechta teg ruxsat etilgan: <b>, <i>, <u>, <s>, <a href>, <code>, <pre>, <blockquote>, <tg-spoiler> va yana bir nechtasi. <div>, <br>, <p> — yo'q; yangi qator — oddiy \n.

6.2 HTML ekranlash

Telegram hujjati: teg yoki HTML entity'ning qismi bo'lmagan barcha <, > va & belgilari mos entity bilan almashtirilishi kerak (&lt;, &gt;, &amp;). Kanon html() funksiyasi aynan shu uchtasini ekranlaydi:

js
const html = (s) =>
  s.replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");

console.log(html("1 < 2 & 3 > 2"));
console.log(html("<b>Ali</b>"));
console.log(`<b>${html("Hisobot <tayyorlash> & yuborish")}</b>`);
console.log(html("&lt;"));

Konsolda:

text
1 &lt; 2 &amp; 3 &gt; 2
&lt;b&gt;Ali&lt;/b&gt;
<b>Hisobot &lt;tayyorlash&gt; &amp; yuborish</b>
&amp;lt;
  • & birinchi almashtiriladi. Aks holda < dan hosil bo'lgan &lt; dagi & yana &amp; ga aylanib, &amp;lt; bo'lardi.
  • Oxirgi qator — matnda haqiqatan &lt; yozilgan bo'lsa, u o'zgarmasdan ko'rinadi: &amp;lt; ekranda &lt; bo'ladi. To'g'ri.
  • Uchinchi qator — naqsh: o'zingizning teglaringiz (<b>) ekranlanmaydi, foydalanuvchi matni esa doim html() dan o'tadi.

Kanon html() qo'shtirnoqni (") ekranlamaydi — Telegram hujjati faqat uchta belgini talab qiladi. Lekin <a href="..."> atributiga foydalanuvchi matnini qo'ysangiz, " ham ekranlanishi kerak (&quot; — hujjatda qo'llab-quvvatlanadigan entity). Kanon modulida atributga foydalanuvchi matni tushmaydi.

6.3 Ekranlanmasa nima bo'ladi

Kanon testi 1 < 2 matnini parse_mode: "HTML" bilan yuboradi va 400 kutadi. Biz shuni mock bilan alohida ishga tushirdik (ekranlangan variant bilan birga):

text
400 Bad Request: can't parse entities: Unsupported start tag at byte offset 2
yuborildi: <b>1 &lt; 2</b>

Xato matnining boshi (can't parse entities) — kanon testida tekshirilgan; bayt ofseti va qolgan so'zlar — mock'ning taxmini, haqiqiy server bilan solishtirilmagan. Muhimi: xabar umuman yuborilmadi. Foydalanuvchi vazifasiga < qo'ysa, unga eslatma kelmaydi — jim xato.

6.4 Hujumchi nigohi

Ekranlash nafaqat xatodan, balki formatlash orqali aldashdan ham himoya qiladi. Hujumchi vazifa nomini shunday yozadi:

text
<a href="https://example.com/soxta-kirish">Hisobingiz bloklandi, bu yerni bosing</a>

Bot uni ekranlamasdan <b>Eslatma:</b> ${matn} ichiga qo'ysa, foydalanuvchi botning rasmiy xabarida chiroyli havola ko'radi — bot unga ishonadi. Bu fishing. html() dan o'tgan matn esa teglar bilan birga oddiy matn bo'lib ko'rinadi.

Bu — XSS g'oyasining chatdagi ko'rinishi: foydalanuvchi ma'lumoti va sizning belgilash tilingiz aralashmasin.

6.5 MarkdownV2

MarkdownV2 da maxsus belgilar ko'p. Telegram hujjati: _ * [ ] ( ) ~ \ > # + - = | { } . !belgilari matnda` bilan ekranlanishi kerak. Oddiy jumla Narx: 35 000 so'm. ham nuqta tufayli rad etiladi:

js
const MAXSUS = "_*[]()~`>#+-=|{}.!";
const md2 = (s) =>
  [...s].map((c) => (MAXSUS.includes(c) || c === "\\")
    ? `\\${c}` : c).join("");

console.log(md2("Narx: 35 000 so'm."));
console.log(md2("1+1=2 (to'g'ri!)"));
console.log(md2("fayl_nomi.txt"));

Konsolda:

text
Narx: 35 000 so'm\.
1\+1\=2 \(to'g'ri\!\)
fayl\_nomi\.txt

Uchta belgiga qarshi o'n sakkizta. Shuning uchun kanon HTML'ni tanladi: ekranlash sodda va xato qilish qiyin.

grammY'da formatlashning uchinchi yo'li ham bor — @grammyjs/parse-mode plagini (fmt shablonlari entities yasaydi). Kanon uni o'rnatmadi.

7. Ko'p uchraydigan xatolar

Xato Natija To'g'risi
har safar faylni qayta yuklash sekin, trafik file_id ni bazada saqlash
file_unique_id bilan yuborish xato yuborish — file_id
20 MB dan katta faylni getFile "file is too big" local Bot API server
yuklab olish URL'ini logga yozish token oqadi faylni o'z omboringizga
foydalanuvchi matni ekranlanmagan xabar ketmaydi yoki fishing html()
& ni oxirida almashtirish &amp;lt; & birinchi
MarkdownV2 da nuqta "can't parse entities" HTML yoki to'liq ekranlash
albomda 1 ta yoki 11 ta element xato 2–10

8. Mashqlar

Mashqlar kurs/mashqlar/29/23-media/ da.

1-mashq (oson): yo'lni tanlang

Har holatda qaysi yo'l: yuklash, file_id, URL yoki local Bot API server?

  1. Har kuni ertalab 5 000 obunachiga bir xil reklama rasmi.
  2. Foydalanuvchi yuborgan 45 MB arxivni serverga saqlash.
  3. Ochiq saytdagi https://example.com/menyu.jpg ni bir marta yuborish.
  4. Yangi yaratilgan PDF hisobotni birinchi marta yuborish.
Yechim
  1. Birinchi marta yuklash, keyin file_id — 4 999 marta baytlarsiz.
  2. Local Bot API server — cloud getFile 20 MB dan kattasini bermaydi.
  3. URL — Telegram o'zi yuklaydi (hujjatdagi URL cheklovlari bilan).
  4. Yuklash (InputFile) — fayl hali Telegram'da yo'q.

2-mashq (o'rta): izohni kesish

izohTayyorla(matn) yozing: foydalanuvchi matnini html() dan o'tkazadi va <b>Hisobot:</b> prefiksi bilan birlashtiradi. Ekranlashdan oldingi matn 1 000 belgidan uzun bo'lsa — 999 belgi + …. Chiqaring: natija uzunligi va oxirgi 12 belgisi; "a<b" uchun natija. Ishora: Telegram chegarani entity'lardan keyingi belgilarda o'lchaydi, ya'ni &lt; bitta belgi; shuning uchun kesish ekranlashdan oldin qilinadi.

Yechim
js
const html = (s) =>
  s.replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");

function izohTayyorla(matn) {
  const qisqa = matn.length > 1000 ? `${matn.slice(0, 999)}…` : matn;
  return `<b>Hisobot:</b> ${html(qisqa)}`;
}

const uzun = izohTayyorla("x<".repeat(800));
console.log(uzun.length, uzun.slice(-12));
console.log(izohTayyorla("a<b"));
text
2513 x&lt;x&lt;x…
<b>Hisobot:</b> a&lt;b

uzun.length — 2513: kesilgan 999 belgida 500 ta x va 499 ta < bor, har < ekranlangach 4 belgi (&lt;) bo'ldi, ustiga … va 16 belgili prefiks. Telegram esa uni bitta < deb sanaydi: Hisobot: (9) + 1 000 = 1 009 belgi — 1024 ga sig'adi. Agar avval ekranlab, keyin kessak, &lt; o'rtasidan kesilib, "can't parse entities" bo'lishi mumkin edi.

3-mashq (qiyin): albomni yig'ish

Bot alohida update'lar oladi: har birida { chat, media_group_id, file_id } (media_group_id bo'lmasligi ham mumkin). albomlarniYig(update'lar) funksiyasini yozing: bir media_group_id li rasmlarni bitta albomga, guruhsizlarini — alohida "yakka"ga yig'adi. Natija — { tur, chat, fayllar } massivi, kelish tartibida. Ishora: Map tartibni saqlaydi (Map).

Yechim
js
function albomlarniYig(updatelar) {
  const natija = new Map();
  let yakka = 0;
  for (const u of updatelar) {
    const kalit = u.media_group_id
      ? `${u.chat}:${u.media_group_id}`
      : `yakka:${yakka++}`;
    if (!natija.has(kalit)) {
      natija.set(kalit, {
        tur: u.media_group_id ? "albom" : "yakka",
        chat: u.chat,
        fayllar: [],
      });
    }
    natija.get(kalit).fayllar.push(u.file_id);
  }
  return [...natija.values()];
}

const kelgan = [
  { chat: 7, media_group_id: "g1", file_id: "A" },
  { chat: 7, file_id: "B" },
  { chat: 7, media_group_id: "g1", file_id: "C" },
  { chat: 9, media_group_id: "g1", file_id: "D" },
];
for (const a of albomlarniYig(kelgan)) {
  console.log(a.tur, a.chat, a.fayllar.join(","));
}
text
albom 7 A,C
yakka 7 B
albom 9 D

Kalitga chat ham kiradi: media_group_id faqat bitta chat ichida ma'noga ega deb hisoblaymiz — begona chatdagi tasodifiy bir xil id albomni aralashtirmasin. Haqiqiy botda update'lar vaqt bo'yicha keladi, shuning uchun yig'ish qisqa kutish bilan qilinadi.

9. Loyiha qadami: tajriba va html() moduli

Bu dars — tajriba. vazifalar eslatma boti faqat matn yuboradi: vazifa nomi va muddati. Fayl biriktirish (vazifa ilovalari) ilovaning o'zida, Fayl yuklash darslaridagi modullar orqali. Shuning uchun sendPhoto, getFile loyihaga kirmadi — ehtiyoj yo'q.

Lekin formatlash loyihada bor. bot.ts dagi html() (formatlangan):

ts
/** Telegram HTML parse_mode uchun: faqat `&`, `<`, `>`
 * (hujjat talabi). */
export const html = (s: string): string =>
  s.replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");

U uch joyda ishlatiladi: ro'yxat xabari (royxatXabari), dialog oxiridagi "Qo'shildi" va bildirishnoma yuboruvchisi. Kanon testi uni mock'ning HTML qoidasi bilan tekshiradi (formatlangan):

ts
test("html va muddat yordamchilari", () => {
  assert.equal(html("a < b & c > d"), "a &lt; b &amp; c &gt; d");
  assert.equal(htmlTekshir(`<b>${html("<script>")}</b>`), undefined);
  assert.match(htmlTekshir("<b>a < b</b>") ?? "",
    /can't parse entities/);
  assert.match(htmlTekshir("<div>x</div>") ?? "",
    /Unsupported start tag "div"/);
  // ... muddatTahlil tekshiruvlari
});

htmlTekshir — mock ichidagi Telegram HTML qoidasining nusxasi: undefined — xato yo'q. Ekranlangan <script> o'tdi, ekranlanmagan < va <div> rad etildi. Biz testlarni ishga tushirdik (Node 24.21.0): ✔ html va muddat yordamchilari, fayl bo'yicha 9/9.

Commit — bu darsda yangi kod yo'q, html() oldingi commit'larda bor.

10. Real ishda

  • Do'kon botlari mahsulot rasmlarining file_id sini bazada saqlaydi — katalog bir zumda yuboriladi.
  • Hujjat qabul qiluvchi botlar (ariza, chek) faylni darhol o'z omboriga ko'chiradi va tekshiradi.
  • Katta media bilan ishlaydigan loyihalar (video kurs botlari) local Bot API server ishlatadi.
  • Intervyuda: "file_id va file_unique_id farqi?", "Foydalanuvchi matnini HTML xabarga qanday xavfsiz qo'yasiz?"

Xulosa

  • Fayl bir marta yuklanadi, keyin file_id bilan: kanonda 30.8 → 2.4 ms.
  • file_id — botga xos; file_unique_id — faqat solishtirish uchun.
  • Albom — 2–10 element, alohida xabarlar bitta media_group_id bilan.
  • Cloud Bot API: yuborish 50 MB, yuklab olish 20 MB; kattasi — local Bot API server (sinalmagan).
  • HTML'da foydalanuvchi matni html() dan o'tadi: & birinchi; aks holda xabar ketmaydi yoki fishing.

Keyingi dars: Guruh, kanal botlari va inline rejim — bot guruhga qo'shilganini bilamiz, privacy mode'ni tushunamiz va istalgan chatdan inline so'rovga javob beramiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!