3. Kod
Mały bot z notatkami: /note buy milk zapisuje notatkę, /notes pokazuje twoje ostatnie pięć.
package.json
{
"name": "my-grammy-bot",
"private": true,
"main": "index.js",
"dependencies": {
"grammy": "^1.46.0",
"mysql2": "^3.24.6"
}
}
index.js
const { Bot } = require("grammy");
const mysql = require("mysql2/promise");
// One pool for the whole bot. The values come from the Environment pane.
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
connectionLimit: 5,
});
const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN);
bot.command("note", async (ctx) => {
const text = ctx.match.trim();
if (!text) return ctx.reply("Write your note after the command, for example: /note buy milk");
if (text.length > 500) return ctx.reply("A note can be up to 500 characters.");
await pool.execute("INSERT INTO notes (user_id, text) VALUES (?, ?)", [ctx.from.id, text]);
await ctx.reply("Saved. Send /notes to see your last five.");
});
bot.command("notes", async (ctx) => {
const [rows] = await pool.execute(
"SELECT text FROM notes WHERE user_id = ? ORDER BY id DESC LIMIT 5",
[ctx.from.id],
);
if (rows.length === 0) return ctx.reply("You have no notes yet.");
await ctx.reply(rows.map((row) => `- ${row.text}`).join("\n"));
});
// Without this, an error in one command stops the whole bot.
bot.catch((err) => {
console.error(`Error while handling update ${err.ctx.update.update_id}:`, err.error);
});
async function main() {
await pool.query(
"CREATE TABLE IF NOT EXISTS notes (" +
"id INT AUTO_INCREMENT PRIMARY KEY, " +
"user_id BIGINT NOT NULL, " +
"text VARCHAR(500) NOT NULL, " +
"created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
"KEY user_id (user_id))",
);
console.log("Database ready");
// bot.start() only returns when the bot stops, so the ready line goes in onStart.
await bot.start({ onStart: (me) => console.log(`Logged in as @${me.username}`) });
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
Co robi: main() tworzy tabelę notes , jeśli jej brakuje, i wypisuje Database ready , zanim bot.start() zapyta Telegram o wiadomości. Pula jest tworzona raz, na górze pliku, i używa jej każde polecenie: pool.execute() pożycza połączenie, wykonuje zapytanie i oddaje połączenie. ctx.match to tekst po poleceniu: grammY usuwa spacje przed nim i zachowuje resztę tak, jak wpisano, razem z podziałami wierszy. bot.catch() ma tu znaczenie: według dokumentacji grammY bez obsługi błędów błąd w handlerze zatrzymuje bota uruchomionego przez bot.start(), a błąd bazy danych jest dokładnie takim błędem.
Symbole zastępcze ? wysyłają notatkę do bazy oddzielnie od SQL, więc notatka taka jak '); DROP TABLE notes; -- zostaje zapisana jako zwykły tekst. W naszym teście zapisała się jako wiersz 2, a tabela została. Nigdy nie buduj SQL ze zwykłego ciągu szablonowego.
Zapisuj ID Telegrama jako BIGINT
Dokumentacja Bot API Telegrama mówi, że ID użytkownika lub czatu „może mieć więcej niż 32 znaczące bity”, ale co najwyżej 52, więc 64-bitowa liczba całkowita jest bezpieczna. Strona o ID podaje zakresy: ID użytkowników sięgają 1.099.511.627.775, a ID grup, supergrup i kanałów są ujemne. Supergrupy i kanały mieszczą się od -1.000.000.000.001 do -1.997.852.516.352 (na przykład -1001234567890). Typ INT w MySQL kończy się na 2.147.483.647. W naszym teście, przy tym samym ścisłym trybie SQL, którego używa nasz serwer bazy, zapis ID użytkownika 7.000.000.001 w kolumnie INT nie powiódł się. mysql2 zwrócił kod błędu ER_WARN_DATA_OUT_OF_RANGE (errno 1264) z tym komunikatem:
Out of range value for column 'user_id' at row 1
Kolumna BIGINT go zapisała, podobnie jak -1001234567890 . Użyj zwykłego BIGINT, a nie BIGINT UNSIGNED, żeby mieściły się też ID czatów.
4. Uruchom
- Wgraj
index.js i package.json w Files. Nie wgrywaj node_modules.
- Naciśnij Uruchom i otwórz Console. Start instaluje pakiety z
package.json, a potem pokazuje [PlayNHost] Starting your bot, Database ready i Logged in as @your_bot. Ta ostatnia linia zmienia status w Przegląd na Online w Telegramie.
- W Telegramie wyślij swojemu botowi
/note buy milk, a potem /notes. Aby zobaczyć wiersze, otwórz stronę bazy i kliknij Otwórz w phpMyAdmin.
W naszym teście zastępnik Telegrama wysłał te polecenia i dostał te odpowiedzi:
| Wysłano | Odpowiedź |
/note buy milk | Saved. Send /notes to see your last five. |
/note | Write your note after the command, for example: /note buy milk |
/note i 501 znaków | A note can be up to 500 characters. |
/notes | Zapisane notatki, od najnowszej |
/notes od drugiego użytkownika | You have no notes yet. |
Wiele wiadomości naraz
Dokumentacja grammY mówi, że bot.start() przetwarza wszystkie aktualizacje po kolei: jedno polecenie kończy się, zanim zacznie następne. W naszym teście bot miał 1 otwarte połączenie po odpowiedzi na 8 poleceń. connectionLimit: 5 ma znaczenie, gdy twój kod wykonuje zapytania równolegle lub gdy przechodzisz na grammY runner, który przetwarza aktualizacje współbieżnie: pula nigdy nie otwiera więcej niż 5 połączeń, a dodatkowe zapytanie czeka na wolne.
Nieudane zapytanie nie zatrzymuje bota
W naszym teście usunęliśmy tabelę, gdy bot działał, i wysłaliśmy /notes. bot.catch() wypisało Error while handling update 500001: Error: Table '...notes' doesn't exist z code: 'ER_NO_SUCH_TABLE', bot nie wysłał odpowiedzi na to polecenie i działał dalej: gdy tabela wróciła, następne /note zostało zapisane. Osoba, która wysłała polecenie, nic nie widzi, więc jeśli zapytanie może się nie udać, owiń je w handlerze w try/catch i odpowiedz krótką wiadomością.
Spokojna noc nie psuje puli
Nasz serwer bazy zamyka połączenie, które było bezczynne przez 8 godzin. Bot, który tak długo nie dostaje wiadomości, na przykład w nocy, zastaje swoje połączenia z puli zamknięte. Pula mysql2 radzi sobie z tym sama. Sprawdziliśmy to z limitem bezczynności obniżonym do 5 sekund: po 7 spokojnych sekundach następne /note zostało zapisane bez błędu.
Limity ważne dla twojej puli
- 20 połączeń naraz na użytkownika bazy. Liczy się każdy proces, który używa tego samego loginu. Połączenie 21 jest odrzucane z błędem
ER_USER_LIMIT_REACHED. W naszym teście pula z connectionLimit: 30 zakończyła się tym błędem, gdy 30 zapytań działało naraz. Pula 5 trzyma bota daleko poniżej limitu.
- Zapytanie trwające dłużej niż 120 sekund jest zatrzymywane. MariaDB kończy je błędem 1969,
Query execution was interrupted (max_statement_time exceeded). W naszym wcześniejszym teście, na koncie skonfigurowanym tak samo, nastąpiło to po 120,03 sekundach. Zapytania bota zwykle trwają milisekundy, więc dotyczy to tylko zapytania, które wymknęło się spod kontroli.
- 500 MB na bazę. Strona Bazy danych pokazuje, ile jest użyte. Przy 500 MB zapisy są zatrzymywane, dopóki nie usuniesz wierszy.
- Kopie zapasowe serwera nie obejmują bazy danych. Eksportuj ją od czasu do czasu z phpMyAdmin.
Błędy i co oznaczają
To kody błędów, które mysql2 zwrócił w naszym teście:
ER_ACCESS_DENIED_ERROR ... Access denied for user: zły DB_USER lub DB_PASSWORD. Po Zresetuj hasło na karcie Połączenie stare hasło przestaje działać od razu: zamień DB_PASSWORD i uruchom bota ponownie.
ECONNREFUSED: nic nie przyjęło połączenia pod tym hostem i portem. Sprawdź DB_HOST i DB_PORT. Brak DB_HOST też kończy się tutaj, bo mysql2 łączy się wtedy z localhost, gdzie nie działa żadna baza: brakuje zmiennej w Środowisko, albo bot nie został uruchomiony ponownie po jej dodaniu.
ENOTFOUND: DB_HOST jest źle napisany, więc taka nazwa nie istnieje.
ETIMEDOUT: nic nie odpowiedziało na czas. Niezależnie od komunikatu połączenie z twojego komputera się nie uda: baza przyjmuje połączenia tylko z serwerów PlayNHost i z phpMyAdmin.
ER_USER_LIMIT_REACHED ... has exceeded the 'max_user_connections' resource (current value: 20): za dużo otwartych połączeń, zobacz limity wyżej. Na przykład druga kopia bota albo pula tworzona dla każdej wiadomości.
ER_WARN_DATA_OUT_OF_RANGE (errno 1264, Out of range value for column 'user_id' at row 1): kolumna ID jest typu INT. Zmień ją na BIGINT.
ER_NO_SUCH_TABLE (errno 1146): brakuje tabeli. Kod wyżej tworzy ją przy starcie; jeśli utworzyłeś ją ręcznie, sprawdź jej nazwę.
Zły token wychodzi po części z bazą danych. Z naszym fałszywym tokenem wobec prawdziwego Telegrama bot wypisał Database ready, a potem zatrzymał się po około ćwierć sekundy z błędem GrammyError: Call to 'deleteWebhook' failed! (401: Unauthorized). Skopiuj token ponownie od @BotFather i wklej go na stronie Bot , panel Telegram. Więcej błędów Telegrama: Jak naprawić błąd 409 Conflict bota Telegram.
Ile pamięci zajmuje
W naszym teście działający bot zajmował 67,6 MB pamięci (rezydentnej) po odpowiedzi na 8 poleceń, z otwartą pulą, w kontenerze ograniczonym do 512 MB jak nasz najmniejszy plan bota. W naszym wcześniejszym teście hostingu samo grammY miało 60 MB (ten bot zatrzymał się na odrzuconym tokenie, zanim obsłużył jakąkolwiek wiadomość), więc różnica, około 8 MB, to mysql2, pula i obsłużone polecenia. Instalacja grammY i mysql2 przez npm od zera miała szczyt 140 MB, razem z pamięcią podręczną plików.
To samo z inną biblioteką
Potrzebujesz serwera dla swojego bota Telegram z wbudowaną bazą danych? Zobacz plany botów Telegram.