{"id":333834,"date":"2022-05-30T21:00:48","date_gmt":"2022-05-30T21:00:48","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=333834"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=333834","title":{"rendered":"<span>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u043c\u043e\u043a\u0438. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c telegram-\u0431\u043e\u0442 \u043d\u0430 Kotlin<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f9c\/ea1\/036\/f9cea1036a49f7fd9602087af0f946ba.png\" width=\"780\" height=\"439\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f9c\/ea1\/036\/f9cea1036a49f7fd9602087af0f946ba.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041b\u044e\u0431\u0430\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0432\u044b\u0448\u0430\u0435\u0442 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0442\u0440\u0435\u0432\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432. \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0432 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043d\u0430 \u0433\u0440\u0430\u0444\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0441\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c API \u0447\u0435\u0440\u0435\u0437 Flow. \u041f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u0442\u0430\u043a\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c Telegram-\u0431\u043e\u0442, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0435 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0441 \u0432\u0430\u043c\u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0431\u043e\u0442 \u043d\u0430 Kotlin (\u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u043c\u043d\u043e\u0433\u043e\u044f\u0437\u044b\u0447\u043d\u043e\u0441\u0442\u0438) \u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u043c\u043e\u043a\u043e\u0432 \u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u0434\u043b\u044f Flow \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 mockk \u0438 \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0439 <a href=\"https:\/\/github.com\/InsanusMokrassar\/TelegramBotAPI\">\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438<\/a> \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API Telegram \u043d\u0430 Kotlin. <\/p>\n<p>\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043f\u043e\u043d\u044f\u0442\u0438\u044f API \u0434\u043b\u044f Telegram. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0431\u043e\u0442 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0435\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a API Telegram (longpolling) \u0438\u043b\u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441 \u0432\u0435\u0431-\u0445\u0443\u043a\u0430 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0438\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 Telegram \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c push-\u043c\u043e\u0434\u0435\u043b\u0438. \u0412 \u043f\u0435\u0440\u0432\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043f\u0443\u0449\u0435\u043d \u043d\u0430 \u043b\u044e\u0431\u043e\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435, \u0438\u043c\u0435\u044e\u0449\u0438\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0442\u0438 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442, \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u043c \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d \u0432\u043d\u0435\u0448\u043d\u0438\u0439 DNS \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0438 \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u043d\u043e \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u044b API \u043e \u043f\u043e\u043b\u043d\u043e\u043c URL \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0432\u0435\u0431-\u0445\u0443\u043a\u0443.<\/p>\n<p>\u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0431\u043e\u0442 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0431\u0440\u0430\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u0432\u044b\u0437\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430 getUpdates, \u043e\u0442\u0432\u0435\u0442\u043e\u043c \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0445 \u0437\u0430 \u0432\u0440\u0435\u043c\u044f, \u043f\u043e\u043a\u0430 \u0431\u043e\u0442 \u0431\u044b\u043b \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0435 \u0447\u0430\u0442\u0430, \u0434\u0430\u0442\u0430-\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438, \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u043c \u0438 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043b\u044d\u0448-\u043a\u043e\u043c\u0430\u043d\u0434). \u041f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u043e\u0442 \u043c\u043e\u0436\u0435\u0442 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a API Telegram \u0441 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (\u0442\u0435\u043a\u0441\u0442, \u0430\u0443\u0434\u0438\u043e-\u0432\u0438\u0434\u0435\u043e, \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b, \u0433\u0435\u043e\u043b\u043e\u043a\u0430\u0446\u0438\u044f, \u0441\u0447\u0435\u0442 \u043d\u0430 \u043e\u043f\u043b\u0430\u0442\u0443, \u043e\u043f\u0440\u043e\u0441\u044b \u0438 \u0438\u0433\u0440\u044b). \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0434\u0438\u0430\u043b\u043e\u0433 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u043d\u044b\u043c, \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u043e\u0442\u0430 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438 \u043f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u0438 \u0434\u0438\u0430\u043b\u043e\u0433\u0430. \u0422\u0430\u043a\u0436\u0435 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043d\u0430\u0431\u043e\u0440 \u043a\u043b\u0430\u0432\u0438\u0448 \u0434\u043b\u044f \u043c\u0435\u043d\u044e \u0438 \u0444\u043b\u0430\u0433\u0438, \u0432\u043b\u0438\u044f\u044e\u0449\u0438\u0435 \u043d\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0441\u044b\u043b\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u043e\u0442\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/InsanusMokrassar\/TelegramBotAPI\">TelegramBotAPI<\/a>, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u043d\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043a\u043e\u0440\u0443\u0442\u0438\u043d \u0438 Flow \u0434\u043b\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u041f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 dependencies-\u0431\u043b\u043e\u043a \u0432 build.gradle.kts:<\/p>\n<pre><code class=\"kotlin\">import org.jetbrains.kotlin.gradle.tasks.KotlinCompile  plugins {     kotlin(\"jvm\") version \"1.6.21\" }  group = \"tech.dzolotov\" version = \"1.0-SNAPSHOT\"  val tgbotapi_version by extra(\"2.0.0\")  repositories {     mavenCentral() }  dependencies {     implementation(\"dev.inmo:tgbotapi:$tgbotapi_version\")     implementation(\"dev.inmo:tgbotapi.utils:$tgbotapi_version\") }  tasks.withType&lt;KotlinCompile> {     kotlinOptions.jvmTarget = \"1.8\" }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u043e\u0431\u0440\u0430\u0442\u0438\u043c\u0441\u044f \u043a \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u043e\u043c\u0443 \u0431\u043e\u0442\u0443 @BotFather \u0441 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 \/newbot \u0438 \u043f\u043e\u0441\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u043e\u0433\u043e \u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 TelegramBot \u0447\u0435\u0440\u0435\u0437 builder-\u0444\u0443\u043d\u043a\u0446\u0438\u044e telegramBot(token) \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0431\u043e\u0442\u0430 <code>buildBehaviourWithFSMAndStartLongPolling&lt;T><\/code>. \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0435\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u043c\u0438 Telegram API \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d \u0442\u0438\u043f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u043a\u0430\u043a \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 State). \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0438\u043c\u0435\u0435\u0442 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440, \u0440\u0430\u0437\u0443\u043c\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c sealed interface \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439. \u041f\u043e\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \/start.<\/p>\n<pre><code class=\"kotlin\">sealed interface BotState : State data class StartedState(override val context:ChatId, val locale:Locale):BotState<\/code><\/pre>\n<p>\u0418 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0446\u0438\u043a\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"kotlin\">suspend fun main() {   val bot = telegramBot(token)     bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {      }.join() }<\/code><\/pre>\n<p>Builder-\u043c\u0435\u0442\u043e\u0434 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 Job \u0438 \u043c\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u043d\u0435\u043c\u0443 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u044f.  \u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0448\u0430\u0433\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b start, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044e \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f (\u043e\u043d\u043e \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0443, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u043a \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0443 \u0447\u0430\u0442\u0430) \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"kotlin\">suspend fun main() {   val bot = telegramBot(token)   bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {     command(\"start\") {       startChain(StartedState(it.chat.id, Locale.forLanguageTag(\"ru\")))       sendTextMessage(it.chat.id, \"Hello\")     }   }.join() }<\/code><\/pre>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c\u0441\u044f \u043a \u0431\u043e\u0442\u0443 \u0438 \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u0440\u0435\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u0430\u044f. <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/1cd\/4c7\/24b\/1cd4c724b62ce1a51d838d5c25cb4991.png\" width=\"948\" height=\"127\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1cd\/4c7\/24b\/1cd4c724b62ce1a51d838d5c25cb4991.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0448\u0430\u0433\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043c\u043d\u043e\u0433\u043e\u044f\u0437\u044b\u0447\u043d\u043e\u0441\u0442\u0438 (\u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442 Locale). \u0414\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0442\u0440\u043e\u043a \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u0434\u043b\u044f Kotlin, \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d de.comahe.i18n4k, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0438\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0441 \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u043c\u0438 \u0438\u0437 properties-\u0444\u0430\u0439\u043b\u043e\u0432. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 build.gradle.kts:<\/p>\n<pre><code class=\"kotlin\">import org.jetbrains.kotlin.gradle.tasks.KotlinCompile  plugins {     kotlin(\"jvm\") version \"1.6.21\"     id(\"de.comahe.i18n4k\") version \"0.4.0\" }  group = \"tech.dzolotov\" version = \"1.0-SNAPSHOT\"  val tgbotapi_version by extra(\"2.0.0\") val i18n4k_version by extra(\"0.4.0\") val mockk_version by extra(\"1.12.4\")  repositories {     mavenCentral() }  dependencies {     implementation(\"dev.inmo:tgbotapi:$tgbotapi_version\")     implementation(\"de.comahe.i18n4k:i18n4k-core:$i18n4k_version\")     implementation(\"de.comahe.i18n4k:i18n4k-core-jvm:$i18n4k_version\")     implementation(\"dev.inmo:tgbotapi.utils:$tgbotapi_version\") }  tasks.withType&lt;KotlinCompile> {     kotlinOptions.jvmTarget = \"1.8\" }  i18n4k {     sourceCodeLocales = listOf(\"ru\",\"en\") }<\/code><\/pre>\n<p>\u0424\u0430\u0439\u043b\u044b \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 (.properties) \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \/src\/main\/i18n. \u041d\u0443\u0436\u043d\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0442\u044c, \u0447\u0442\u043e properties-\u0444\u0430\u0439\u043b\u044b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 ANSI-\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0435 \u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0435\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438 \u0432 IDE (\u0432 IntelliJ IDEA \u0438 Android Studio: Settings -> Editor -> File Encodings -> \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c Transparent native-to-ascii conversion). <\/p>\n<pre><code class=\"kotlin\">BotMessage_en.properties  hello=Welcome<\/code><\/pre>\n<pre><code class=\"kotlin\">BotMessages_ru.properties  hello=\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441\u0442\u0440\u043e\u043a\u0438 \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0432 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435 BotMessages \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u0435 hello \u0438 \u043c\u0435\u0442\u043e\u0434 toString(locale).<\/p>\n<pre><code class=\"kotlin\">suspend fun start(context: DefaultBehaviourContextWithFSM&lt;BotState>, message:CommonMessage&lt;TextContent>) {   val locale = Locale.forLanguageTag(\"ru\")   context.startChain(StartedState(message.chat.id, locale))   sendTextMessage(it.chat.id, BotMessages.hello.toString(locale)) }  suspend fun main() {   val bot = telegramBot(token)   bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {     command(\"start\") {       val locale = Locale.forLanguageTag(\"ru\")       context.startChain(StartedState(it.chat.id, locale))       context.sendTextMessage(it.chat.id, BotMessages.hello.toString(locale))     }   }.join() }<\/code><\/pre>\n<p>\u041c\u043e\u0436\u043d\u043e \u043b\u0438 \u043a\u0430\u043a-\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043e \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u043c \u044f\u0437\u044b\u043a\u0435? \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u0447\u0442\u043e \u043c\u044b \u0438\u0437 \u043d\u0435\u0433\u043e \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 flow \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u043c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"kotlin\">suspend fun main() {     val bot = telegramBot(token)     val flowUpdatesFilter = FlowsUpdatesFilter()     bot.buildBehaviourWithFSM&lt;BotState>(flowUpdatesFilter=flowUpdatesFilter) {         retrieveAccumulatedUpdates(flowUpdatesFilter)         flowUpdatesFilter.allUpdatesFlow.collect {             println(it)         }     } }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0431\u043e\u0442 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0430 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 Update, \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0435\u0433\u043e \u043e\u0442 API, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"bash\">MessageUpdate(updateId=249090759, data=PrivateContentMessageImpl(messageId=31, from=CommonUser(id=ChatId(chatId=112121111), firstName=Test, lastName=Test, username=Username(username=@testuser), ietfLanguageCode=ru), chat=PrivateChatImpl(id=ChatId(chatId=112121111), username=Username(username=@testuser), firstName=Test, lastName=Test), content=TextContent(text=\/start, textSources=[BotCommandTextSource(source=\/start)]), date=DateTime(1653733956000), editDate=null, hasProtectedContent=false, forwardInfo=null, replyTo=null, replyMarkup=null, senderBot=null))<\/code><\/pre>\n<p>\u042d\u0442\u0430 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043d\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u0430 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u043e\u043a\u043e\u0432 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0441\u0435\u0439\u0447\u0430\u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u043c, \u0447\u0442\u043e \u0432 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u0435 \u0435\u0441\u0442\u044c \u043a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u044f\u0437\u044b\u043a\u043e\u043c, \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u044f\u0437\u044b\u043a\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \/start \u0438 \u0442\u0430\u043a\u0436\u0435 \u0432\u044b\u0434\u0435\u043b\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043b\u043e\u0433\u0438\u043a\u0438.<\/p>\n<pre><code class=\"kotlin\">suspend fun onStart(context: DefaultBehaviourContextWithFSM&lt;BotState>, chatId: ChatId, locale: String?) {     val locale = Locale.forLanguageTag(locale ?: \"ru\")     context.startChain(StartedState(chatId, locale))     context.sendTextMessage(chatId, BotMessages.hello.toString(locale)) }  suspend fun main() {   val bot = telegramBot(token)   bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {     command(\"start\") {       onStart(this, it.chat.id, it.from?.asCommonUser()?.languageCode)     }   }.join() } <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043c\u043d\u043e\u0436\u043a\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u0443\u044e \u0448\u0443\u0442\u043a\u0443 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \/joke (\u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/sv443.net\/jokeapi\/v2\/\">Joke API<\/a>, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e REST-\u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0439 \u0448\u0443\u0442\u043a\u0438 \u043f\u0440\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 <a href=\"https:\/\/v2.jokeapi.dev\/joke\/Programming\">https:\/\/v2.jokeapi.dev\/joke\/Programming<\/a>). \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 API \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430:<\/p>\n<pre><code class=\"json\">{     \"error\": false,     \"category\": \"Programming\",     \"type\": \"twopart\",     \"setup\": \"Hey baby I wish your name was asynchronous...\",     \"delivery\": \"... so you'd give me a callback.\",     \"flags\": {         \"nsfw\": false,         \"religious\": false,         \"political\": false,         \"racist\": false,         \"sexist\": false,         \"explicit\": false     },     \"id\": 54,     \"safe\": true,     \"lang\": \"en\" }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u043e\u0434\u043d\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u044b\u0445 \u0448\u0443\u0442\u043e\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u0440\u0443\u0433\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 (\u043e\u0442\u043c\u0435\u0447\u0430\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u0436\u043d\u044b\u0435 \u043f\u043e\u043b\u044f)<\/p>\n<pre><code class=\"json\">{     \"category\": \"Programming\",     \"type\": \"single\",     \"joke\": \"The generation of random numbers is too important to be left to chance.\", }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 json-\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 kotlinx-serialization-json \u0432 build.gradle.kts:<\/p>\n<pre><code class=\"kotlin\">plugins {     kotlin(\"jvm\") version \"1.6.21\"     id(\"de.comahe.i18n4k\") version \"0.4.0\"     id(\"org.jetbrains.kotlin.plugin.serialization\") version \"1.6.21\" }  dependencies {     \/\/...\u0437\u0434\u0435\u0441\u044c \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u0435\u043c \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438...     implementation(\"io.ktor:ktor-serialization-kotlinx-json:2.0.1\") }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u044b\u0439 data-\u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043e\u0442\u0432\u0435\u0442\u0430:<\/p>\n<pre><code class=\"kotlin\">@kotlinx.serialization.Serializable data class Joke(val type: String, val setup: String? = null, val delivery: String? = null, val joke: String? = null)<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0439 \u0448\u0443\u0442\u043a\u0438 (\u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0433\u043e unit-\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f):<\/p>\n<pre><code class=\"kotlin\">val jokeApi = \"https:\/\/v2.jokeapi.dev\/joke\/Programming\"  suspend fun getJoke(client:HttpClient):String? {     return try {         val response: HttpResponse = client.get(jokeApi)         val joke = Json { ignoreUnknownKeys = true }.decodeFromString(Joke.serializer(), response.bodyAsText())         if (joke.type == \"twopart\") {             joke.setup + \"\\n\" + joke.delivery         } else {             joke.joke.toString()         }     } catch (e:Exception) {         null     } }<\/code><\/pre>\n<p>\u0418 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043a\u043e\u043c\u0430\u043d\u0434\u044b joke \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0432 \u043e\u0442\u0432\u0435\u0442\u043d\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0439 \u0448\u0443\u0442\u043a\u0438:<\/p>\n<pre><code class=\"kotlin\">command(\"joke\") {     sendTextMessage(it.chat.id, getJoke()!!) }<\/code><\/pre>\n<p>\u041c\u044b \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u0433\u043e\u0442\u043e\u0432\u044b \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0442\u0435\u0441\u0442\u0430\u043c. \u0418 \u0437\u0434\u0435\u0441\u044c \u043d\u0430\u0441 \u0436\u0434\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u0432\u0441\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u0431\u043e\u0442\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c\u0438 (\u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0438\u0445 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e, \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c);<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u044b \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 (startChain \u0438 sendTextMessage), \u0447\u0442\u043e \u043e\u0441\u043b\u043e\u0436\u043d\u044f\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0431\u043e\u0442\u0430 (\u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0437\u0430\u043f\u0440\u043e\u0441 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0432 API \u0438 \u0432\u044b\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443 \u0438\u0437-\u0437\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0434\u0438\u0430\u043b\u043e\u0433\u0430 \u0441 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c);<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442 \u0438\u043c\u0438\u0442\u0430\u0446\u0438\u044e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438\/\u0438\u043b\u0438 \u043a\u043e\u043c\u0430\u043d\u0434), \u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u0441\u0442\u0440\u043e\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u043e\u0442\u043e\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u043e\u0431\u043e\u0439 Flow&lt;Update>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e. \u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043e\u0440\u0443\u0442\u0438\u043d \u0432 Kotlin \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 kotlinx-coroutines, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430 (TestDispatcher) \u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043a\u043e\u0440\u0443\u0442\u0438\u043d \u0432 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 JUnit \u0442\u0435\u0441\u0442\u043e\u0432 \u0441 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u0435\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 (\u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u0434\u0435\u0440\u0436\u0435\u043a, \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u044b\u0445 \u0447\u0435\u0440\u0435\u0437 delay). <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u0431\u043b\u043e\u043a dependencies \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 kotlin.test \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 (\u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JUnitPlatform) \u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 kotlinx-coroutines-test.<\/p>\n<pre><code class=\"kotlin\">dependencies {     testImplementation(kotlin(\"test\"))     testImplementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1\")     \/\/\u0437\u0434\u0435\u0441\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0440\u0430\u043d\u0435\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 }  tasks.test {     useJUnitPlatform() }<\/code><\/pre>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f Unit-\u0442\u0435\u0441\u0442\u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u0438 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0439 \u0448\u0443\u0442\u043a\u0438. \u0414\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c launcher-\u0444\u0443\u043d\u043a\u0446\u0438\u044e runTest \u0438\u0437 kotlinx-coroutines-test:<\/p>\n<pre><code class=\"kotlin\">import kotlin.test.Test import kotlin.test.assertNotNull  class TestBot {     @Test     fun testJoke() = runTest {         val joke = getJoke()         assertNotNull(joke)     } }<\/code><\/pre>\n<p>\u041d\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u043a \u0442\u0430\u043a\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043a \u0440\u0435\u0441\u0443\u0440\u0441\u0430\u043c \u0441\u0435\u0442\u0438 \u0438 \u0442\u0435\u0441\u0442 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0432\u0430\u043b\u0435\u043d \u0432 \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0438\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 REST API \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u0414\u043b\u044f \u043f\u043e\u0434\u043c\u0435\u043d\u044b \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 ktor-client-mock, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u0432 dependencies:<\/p>\n<pre><code class=\"go\">testImplementation(\"io.ktor:ktor-client-mock:$ktor_version\")<\/code><\/pre>\n<p>\u0418 \u043e\u043f\u0438\u0448\u0435\u043c \u043f\u043e\u0434\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u043e\u0442\u0432\u0435\u0442\u0430 \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 get-\u043c\u0435\u0442\u043e\u0434\u0430 \u043a \u0430\u0434\u0440\u0435\u0441\u0443 jokeApi:<\/p>\n<pre><code class=\"kotlin\">@Test fun testSingleJoke() = runTest {   val expectJoke = \"What did the fish get on his math test? A sea plus.\"   val client = HttpClient(MockEngine) {     engine {       addHandler { request ->         when (request.url) {           Url(jokeApi) -> {             respond(\"\"\"{               \"type\": \"single\",               \"joke\": \"$exceptJoke\"             }\"\"\".trimMargin())           }           else -> error(\"Unhandled ${request.url}\")         }       }     }   }   val actualJoke = getJoke(client)   assertEquals(expectJoke, actualJoke) }<\/code><\/pre>\n<p>\u0418 \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0434\u0432\u0443\u0445\u0441\u0442\u0440\u043e\u0447\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442:<\/p>\n<pre><code class=\"kotlin\">@Test fun testTwoPartJoke() = runTest {     val setup = \"Why cant gandalf mark tests?\"     val delivery = \"Because he always tells the students 'YOU\u2026 SHALL NOT PASS!'\"     val client = HttpClient(MockEngine) {         engine {             addHandler { request ->                 when (request.url) {                     Url(jokeApi) -> {                         respond(\"\"\"{                             \"type\": \"twopart\",                             \"setup\": \"$setup\",                             \"delivery\": \"$delivery\"                             }\"\"\".trimMargin())                     }                     else -> error(\"Unhandled ${request.url}\")                 }             }         }     }     val actualJoke = getJoke(client)     assertEquals(setup+\"\\n\"+delivery, actualJoke) } <\/code><\/pre>\n<p>\u041d\u043e \u043d\u0430\u0448\u0430 \u0446\u0435\u043b\u044c \u043f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e &#8212; \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u0431\u043e\u0442\u0430 \u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u043d\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u043d\u0443\u0436\u043d\u043e \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e \u043f\u043e\u0434\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API Telegram. \u0417\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 mock \u0438 spy-\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432. Mock-\u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043f\u043e\u0434\u043c\u0435\u043d\u044f\u044e\u0442 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432\u043e \u0432\u0441\u0435\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445, \u043a\u043e\u0433\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0437\u0430\u043c\u0435\u043d\u044b \u0447\u0435\u0440\u0435\u0437 ClassLoader). Spy-\u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f\u0445, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0430\u0441\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043d\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 mockk, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u0435\u043c \u0431\u043e\u043b\u0435\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Mockito, \u043d\u043e \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u0431\u043e\u043b\u0435\u0435 \u0443\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430 \u0434\u043b\u044f Kotlin.<\/p>\n<p>\u041f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 build.gradle.kts:<\/p>\n<pre><code class=\"kotlin\">val mockk_version by extra(\"1.12.4\")  dependencies {   \/\/...\u0437\u0434\u0435\u0441\u044c \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u0438\u043c \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438...   testImplementation(\"io.mockk:mockk:$mockk_version\") }<\/code><\/pre>\n<p>\u0418 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0438\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0435 \u0438\u0437 \u0431\u043e\u0442\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0434\u0432\u043e\u0439\u043d\u0438\u043a\u043e\u0432 (spy- \u0438 mock-\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432):<\/p>\n<pre><code class=\"kotlin\">fun testBot() = runTest {     val bot = spyk&lt;TelegramBot>()     coEvery { bot.execute&lt;Request&lt;Message>>(any()) } returns mockk()     val chatId = ChatId(0)      bot.buildBehaviourWithFSM&lt;BotState> {         onStart(this, chatId, \"ru\")         coVerify { bot.execute(SendTextMessage(chatId, \"\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c\")) }         onStart(this, chatId, \"en\")         coVerify { bot.execute(SendTextMessage(chatId, \"Welcome\")) }     } }<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0442\u0435\u0441\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0434\u0432\u043e\u0439\u043d\u0438\u043a \u0434\u043b\u044f \u043a\u043b\u0430\u0441\u0441\u0430-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 Telegram API (TelegramBot) \u0438 \u0434\u0435\u043a\u043b\u0430\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043b\u044e\u0431\u043e\u0439 \u0432\u044b\u0437\u043e\u0432 execute \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c (\u0432 \u043d\u0435\u0433\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u043e\u0442\u0432\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0443) \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u0443\u0441\u0442\u043e\u0439 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 mockk \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u0430\u043a \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 (\u0442\u043e\u0433\u0434\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 every { call(args) } returns value), \u0442\u0430\u043a \u0438 \u0441 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c\u0438 (\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e coEvery { call(args) returns value). \u0414\u0430\u043b\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 (buildBehaviourWithFSM) \u0438 \u0432 \u043d\u0435\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \/start. \u0417\u0430\u0442\u0435\u043c \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 mockk \u043c\u044b \u0443\u0431\u0435\u0436\u0434\u0430\u0435\u043c\u0441\u044f, \u0447\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0431\u044b\u043b\u043e \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0435 \u043a \u043c\u0435\u0442\u043e\u0434\u0443 execute \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0431\u043e\u0442\u0430 \u0441 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u043c \u0441\u043e\u0431\u043e\u0439 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0443, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c\u0443 \u0432 ChatId \u0441 \u0442\u0435\u043a\u0441\u0442\u043e\u043c &#171;\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c&#187; \u0438 &#171;Welcome&#187; (\u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 locale en).<\/p>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u044d\u0442\u0430\u043f\u043e\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0433\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0430 \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \/start. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c mock-\u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u043b\u044f \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 StatesManager \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0444\u0430\u043a\u0442 \u0432\u044b\u0437\u043e\u0432\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0437\u0430\u043c\u0435\u043d\u044b \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f.<\/p>\n<pre><code class=\"kotlin\">@Test fun testState() = runTest {     val chatId = ChatId(0)     val locale = Locale.forLanguageTag(\"ru\")    \/\/\u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0434\u0432\u043e\u0439\u043d\u0438\u043a \u0434\u043b\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 API Telegram     val bot = spyk&lt;TelegramBot>()     \/\/\u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (\u043d\u043e \u0437\u0430\u043f\u043e\u043c\u043d\u0438\u043c \u0444\u0430\u043a\u0442)     coEvery { bot.execute&lt;Request&lt;Message>>(any()) } returns mockk()     \/\/\u0431\u0443\u0434\u0435\u043c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0432 FSM (Finite State Machine)     val statesManager = spyk&lt;StatesManager&lt;BotState>>()     \/\/\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 (\u0441 \u043d\u0430\u0448\u0438\u043c statesManager)     bot.buildBehaviourWithFSM(statesManager = statesManager) {         \/\/\u0438\u043c\u0438\u0442\u0438\u0440\u0443\u0435\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u043a\u043e\u043c\u0430\u043d\u0434\u0430 \/start)         onStart(this, chatId, \"ru\")         \/\/\u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u0431\u044b\u043b\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f         coVerify { statesManager.startChain(StartedState(chatId, locale)) }         \/\/\u0438 \u0447\u0442\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u043e\u0441\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u043e\u0442\u0432\u0435\u0442         val message = SendTextMessage(chatId, \"\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c\")         coVerify { bot.execute(message) }     } }<\/code><\/pre>\n<p>\u041d\u043e \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 &#8212; \u043f\u043e\u0434\u043c\u0435\u043d\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0417\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0432 \u043d\u0430\u0448\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0439-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432 \u043a mock-\u043e\u0431\u044a\u0435\u043a\u0442\u0443 \u0431\u043e\u0442\u0430. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430:<\/p>\n<pre><code class=\"kotlin\">suspend fun botSetup(context: DefaultBehaviourContextWithFSM&lt;BotState>) {     context.apply {         command(\"start\") {             onStart(this, it.chat.id, it.from?.asCommonUser()?.languageCode)         }         command(\"joke\") {             sendTextMessage(it.chat.id, getJoke(HttpClient(CIO))!!)         }     } }<\/code><\/pre>\n<p>\u0438 \u0432\u044b\u0437\u043e\u0432\u0435\u043c \u0435\u0435 \u0432\u043d\u0443\u0442\u0440\u0438 builder-\u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430:<\/p>\n<pre><code class=\"kotlin\">suspend fun main() {     val bot = telegramBot(token)     bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {         botSetup(this)     }.join() }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u0442\u0435\u0441\u0442\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0434\u043c\u0435\u043d\u044b \u0432\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0430 Flow&lt;Update>, \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e:<\/p>\n<pre><code class=\"kotlin\">@Test fun testStartCommand() = runTest  \/\/\u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u0434\u0432\u043e\u0439\u043d\u0438\u043a \u0434\u043b\u044f API Telegram \u0438 \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043b\u044e\u0431\u044b\u0435 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f     val bot = spyk&lt;TelegramBot>()     coEvery { bot.execute&lt;Request&lt;Message>>(any()) } returns mockk()      val chatId = ChatId(0)     \/\/\u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a \u043a\u0430\u043a \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439     val user = mockk&lt;CommonUser>()     every { user.languageCode } returns \"ru\"     val locale = Locale.forLanguageTag(\"ru\")      \/\/\u0431\u0443\u0434\u0435\u043c \u0442\u0430\u043a\u0436\u0435 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f FSM (Finite State Machine)     val statesManager = spyk&lt;StatesManager&lt;BotState>>()          \/\/\u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u043e\u0442\u043e\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0431\u043e\u0442\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043f\u043e\u0442\u043e\u043a\u0430 \u043a\u0430\u043a upstreamUpdates     val flow = MutableSharedFlow&lt;Update>()     bot.buildBehaviourWithFSM(statesManager=statesManager, upstreamUpdatesFlow = flow) {         botSetup(this)     }     \/\/\u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442 \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e\u0439 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0439 \u0438\u0437 \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0432\u044b\u0448\u0435)     val msg = PrivateContentMessageImpl(0L, user, chat = PrivateChatImpl(chatId),                                          content = TextContent(\"\/start\",                                                                textSources=listOf(BotCommandTextSource(source=\"\/start\"))),                                          date= DateTime.now(), editDate = null,                                          hasProtectedContent = false, forwardInfo = null,                                          replyTo = null, replyMarkup = null, senderBot = null) \/\/\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u043e\u0442\u0443 flow.emit(MessageUpdate(0L, msg))      \/\/\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u0432\u0435\u0442\u0430 \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f     coVerify { statesManager.startChain(StartedState(chatId, locale)) }     val message = SendTextMessage(chatId, \"\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c\")     coVerify { bot.execute(message) } }<\/code><\/pre>\n<p>\u041d\u0443 \u0438 \u0432 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0439 (\u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043d\u0435\u0442, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c mock) \u0448\u0443\u0442\u043a\u0438. \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0435\u043c\u0441\u044f \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 mockk \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u043b\u044f \u0438\u043c\u0438\u0442\u0430\u0446\u0438\u0438 HttpClient(CIO), \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u043c\u0435\u0442\u043e\u0434\u0430 botSetup \u0438 \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0443\u0434\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 http-\u043a\u043b\u0438\u0435\u043d\u0442\u0430.<\/p>\n<pre><code class=\"kotlin\">suspend fun botSetup(context: DefaultBehaviourContextWithFSM&lt;BotState>, httpClient: HttpClient) {     context.apply {         command(\"start\") {             onStart(this, it.chat.id, it.from?.asCommonUser()?.languageCode)         }         command(\"joke\") {             sendTextMessage(it.chat.id, getJoke(httpClient)!!)         }     } }  suspend fun main() {     val bot = telegramBot(token)     bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {         botSetup(this, HttpClient(CIO))     }.join() }<\/code><\/pre>\n<p>\u0418 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c MockEngine \u0434\u043b\u044f \u043f\u043e\u0434\u043c\u0435\u043d\u044b \u043e\u0442\u0432\u0435\u0442\u0430 http-\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 spy-\u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u043b\u044f Telegram API \/ StatesManager \u0441 \u043f\u043e\u0434\u043c\u0435\u043d\u043e\u0439 \u043e\u0442\u0432\u0435\u0442\u0430 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u0432\u044b\u0437\u043e\u0432\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 mockk:<\/p>\n<pre><code class=\"kotlin\">@Test fun testJokeCommand() = runTest {     val exceptJoke =         \"Programming is 10% science, 20% ingenuity, and 70% getting the ingenuity to work with the science.\"     val client = HttpClient(MockEngine) {         engine {             addHandler { request ->                 when (request.url) {                     Url(jokeApi) -> {                         respond(                             \"\"\"{                             \"type\": \"single\",                             \"joke\": \"$exceptJoke\"                             }\"\"\".trimMargin()                         )                     }                     else -> error(\"Unhandled ${request.url}\")                 }             }         }     }      val bot = spyk&lt;TelegramBot>()     coEvery { bot.execute&lt;Request&lt;Message>>(any()) } returns mockk()      val chatId = ChatId(0)     val user = mockk&lt;CommonUser>()     every { user.languageCode } returns \"en\"      val statesManager = spyk&lt;StatesManager&lt;BotState>>()     val flow = MutableSharedFlow&lt;Update>()      bot.buildBehaviourWithFSM(statesManager = statesManager, upstreamUpdatesFlow = flow) {         botSetup(this, client)     }     val msg = PrivateContentMessageImpl(         0L, user,         chat = PrivateChatImpl(chatId), content = TextContent(             \"\/joke\",             textSources = listOf(BotCommandTextSource(source = \"\/joke\"))         ),         date = DateTime.now(), editDate = null, hasProtectedContent = false,         forwardInfo = null, replyTo = null, replyMarkup = null, senderBot = null     )     flow.emit(MessageUpdate(0L, msg))      coVerify { bot.execute(SendTextMessage(chatId, exceptJoke)) } }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 mockk \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432\u044b\u0437\u043e\u0432\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0438\/\u0438\u043b\u0438 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0439 \u043a \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u0431\u043e\u0440\u0430 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 (\u043a\u0430\u043a \u0441 \u0447\u0438\u0441\u043b\u0430\u043c\u0438, \u0442\u0430\u043c \u0438 \u0441\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u043c\u0438), \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0434\u043c\u0435\u043d\u0443 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u044b \u043f\u0440\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u043a\u043e\u0440\u0443\u0442\u0438\u043d. \u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u043d\u0430 <a href=\"https:\/\/mockk.io\/\"><u>\u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435<\/u><\/a>.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0445\u043e\u0447\u0443 \u043f\u0440\u0438\u0433\u043b\u0430\u0441\u0438\u0442\u044c \u0432\u0441\u0435\u0445 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0445 \u043d\u0430 <a href=\"https:\/\/otus.pw\/GVRP\/\"><u>\u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u0443\u0440\u043e\u043a<\/u><\/a> \u043a\u0443\u0440\u0441\u0430 Kotlin QA Engineer, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u043e\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443\u0442 \u043f\u0440\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 Kotlin \u0438 Java, \u0430 \u0442\u0430\u043a \u0436\u0435 \u0438\u0445 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0432 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438.   <\/p>\n<p><a href=\"https:\/\/otus.pw\/GVRP\/\"><u>\u0423\u0437\u043d\u0430\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e \u043a\u0443\u0440\u0441\u0435.<\/u><\/a><\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/otus\/blog\/668352\/\"> https:\/\/habr.com\/ru\/company\/otus\/blog\/668352\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u041b\u044e\u0431\u0430\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0432\u044b\u0448\u0430\u0435\u0442 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u0442\u0440\u0435\u0432\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432. \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0432 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438, \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043d\u0430 \u0433\u0440\u0430\u0444\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0441\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c API \u0447\u0435\u0440\u0435\u0437 Flow. \u041f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u0442\u0430\u043a\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c Telegram-\u0431\u043e\u0442, \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u043d\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0435 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0441 \u0432\u0430\u043c\u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0431\u043e\u0442 \u043d\u0430 Kotlin (\u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u043c\u043d\u043e\u0433\u043e\u044f\u0437\u044b\u0447\u043d\u043e\u0441\u0442\u0438) \u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u043c\u043e\u043a\u043e\u0432 \u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u0434\u043b\u044f Flow \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 mockk \u0438 \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0439 <a href=\"https:\/\/github.com\/InsanusMokrassar\/TelegramBotAPI\">\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438<\/a> \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API Telegram \u043d\u0430 Kotlin. <\/p>\n<p>\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043f\u043e\u043d\u044f\u0442\u0438\u044f API \u0434\u043b\u044f Telegram. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0431\u043e\u0442 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0435\u0433\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a API Telegram (longpolling) \u0438\u043b\u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441 \u0432\u0435\u0431-\u0445\u0443\u043a\u0430 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0438\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 Telegram \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c push-\u043c\u043e\u0434\u0435\u043b\u0438. \u0412 \u043f\u0435\u0440\u0432\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043f\u0443\u0449\u0435\u043d \u043d\u0430 \u043b\u044e\u0431\u043e\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435, \u0438\u043c\u0435\u044e\u0449\u0438\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0442\u0438 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442, \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u043c \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d \u0432\u043d\u0435\u0448\u043d\u0438\u0439 DNS \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0438 \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u043d\u043e \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u044b API \u043e \u043f\u043e\u043b\u043d\u043e\u043c URL \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0432\u0435\u0431-\u0445\u0443\u043a\u0443.<\/p>\n<p>\u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0431\u043e\u0442 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0431\u0440\u0430\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u0432\u044b\u0437\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430 getUpdates, \u043e\u0442\u0432\u0435\u0442\u043e\u043c \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0445 \u0437\u0430 \u0432\u0440\u0435\u043c\u044f, \u043f\u043e\u043a\u0430 \u0431\u043e\u0442 \u0431\u044b\u043b \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0435 \u0447\u0430\u0442\u0430, \u0434\u0430\u0442\u0430-\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438, \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u043c \u0438 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043b\u044d\u0448-\u043a\u043e\u043c\u0430\u043d\u0434). \u041f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0431\u043e\u0442 \u043c\u043e\u0436\u0435\u0442 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a API Telegram \u0441 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (\u0442\u0435\u043a\u0441\u0442, \u0430\u0443\u0434\u0438\u043e-\u0432\u0438\u0434\u0435\u043e, \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b, \u0433\u0435\u043e\u043b\u043e\u043a\u0430\u0446\u0438\u044f, \u0441\u0447\u0435\u0442 \u043d\u0430 \u043e\u043f\u043b\u0430\u0442\u0443, \u043e\u043f\u0440\u043e\u0441\u044b \u0438 \u0438\u0433\u0440\u044b). \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0434\u0438\u0430\u043b\u043e\u0433 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u043d\u044b\u043c, \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0431\u043e\u0442\u0430 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438 \u043f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u0438 \u0434\u0438\u0430\u043b\u043e\u0433\u0430. \u0422\u0430\u043a\u0436\u0435 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043d\u0430\u0431\u043e\u0440 \u043a\u043b\u0430\u0432\u0438\u0448 \u0434\u043b\u044f \u043c\u0435\u043d\u044e \u0438 \u0444\u043b\u0430\u0433\u0438, \u0432\u043b\u0438\u044f\u044e\u0449\u0438\u0435 \u043d\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0441\u044b\u043b\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u043e\u0442\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0438\u043f\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/InsanusMokrassar\/TelegramBotAPI\">TelegramBotAPI<\/a>, \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u043d\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043a\u043e\u0440\u0443\u0442\u0438\u043d \u0438 Flow \u0434\u043b\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u041f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 dependencies-\u0431\u043b\u043e\u043a \u0432 build.gradle.kts:<\/p>\n<pre><code class=\"kotlin\">import org.jetbrains.kotlin.gradle.tasks.KotlinCompile  plugins {     kotlin(\"jvm\") version \"1.6.21\" }  group = \"tech.dzolotov\" version = \"1.0-SNAPSHOT\"  val tgbotapi_version by extra(\"2.0.0\")  repositories {     mavenCentral() }  dependencies {     implementation(\"dev.inmo:tgbotapi:$tgbotapi_version\")     implementation(\"dev.inmo:tgbotapi.utils:$tgbotapi_version\") }  tasks.withType&lt;KotlinCompile> {     kotlinOptions.jvmTarget = \"1.8\" }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u043e\u0431\u0440\u0430\u0442\u0438\u043c\u0441\u044f \u043a \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u043e\u043c\u0443 \u0431\u043e\u0442\u0443 @BotFather \u0441 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 \/newbot \u0438 \u043f\u043e\u0441\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u043e\u0433\u043e \u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 TelegramBot \u0447\u0435\u0440\u0435\u0437 builder-\u0444\u0443\u043d\u043a\u0446\u0438\u044e telegramBot(token) \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432\u044b\u0437\u043e\u0432\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0431\u043e\u0442\u0430 <code>buildBehaviourWithFSMAndStartLongPolling&lt;T><\/code>. \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0434\u043e\u043b\u0433\u043e\u0436\u0438\u0432\u0443\u0449\u0435\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u043c\u0438 Telegram API \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d \u0442\u0438\u043f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (\u043a\u0430\u043a \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 State). \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0438\u043c\u0435\u0435\u0442 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440, \u0440\u0430\u0437\u0443\u043c\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c sealed interface \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439. \u041f\u043e\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \/start.<\/p>\n<pre><code class=\"kotlin\">sealed interface BotState : State data class StartedState(override val context:ChatId, val locale:Locale):BotState<\/code><\/pre>\n<p>\u0418 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0446\u0438\u043a\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"kotlin\">suspend fun main() {   val bot = telegramBot(token)     bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {      }.join() }<\/code><\/pre>\n<p>Builder-\u043c\u0435\u0442\u043e\u0434 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 Job \u0438 \u043c\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u043d\u0435\u043c\u0443 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u044f.  \u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0448\u0430\u0433\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b start, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044e \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f (\u043e\u043d\u043e \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0443, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u043a \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0443 \u0447\u0430\u0442\u0430) \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"kotlin\">suspend fun main() {   val bot = telegramBot(token)   bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {     command(\"start\") {       startChain(StartedState(it.chat.id, Locale.forLanguageTag(\"ru\")))       sendTextMessage(it.chat.id, \"Hello\")     }   }.join() }<\/code><\/pre>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c\u0441\u044f \u043a \u0431\u043e\u0442\u0443 \u0438 \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u0440\u0435\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u0430\u044f. <\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0448\u0430\u0433\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043c\u043d\u043e\u0433\u043e\u044f\u0437\u044b\u0447\u043d\u043e\u0441\u0442\u0438 (\u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442 Locale). \u0414\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0442\u0440\u043e\u043a \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u0434\u043b\u044f Kotlin, \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d de.comahe.i18n4k, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0438\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0441 \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u043c\u0438 \u0438\u0437 properties-\u0444\u0430\u0439\u043b\u043e\u0432. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 build.gradle.kts:<\/p>\n<pre><code class=\"kotlin\">import org.jetbrains.kotlin.gradle.tasks.KotlinCompile  plugins {     kotlin(\"jvm\") version \"1.6.21\"     id(\"de.comahe.i18n4k\") version \"0.4.0\" }  group = \"tech.dzolotov\" version = \"1.0-SNAPSHOT\"  val tgbotapi_version by extra(\"2.0.0\") val i18n4k_version by extra(\"0.4.0\") val mockk_version by extra(\"1.12.4\")  repositories {     mavenCentral() }  dependencies {     implementation(\"dev.inmo:tgbotapi:$tgbotapi_version\")     implementation(\"de.comahe.i18n4k:i18n4k-core:$i18n4k_version\")     implementation(\"de.comahe.i18n4k:i18n4k-core-jvm:$i18n4k_version\")     implementation(\"dev.inmo:tgbotapi.utils:$tgbotapi_version\") }  tasks.withType&lt;KotlinCompile> {     kotlinOptions.jvmTarget = \"1.8\" }  i18n4k {     sourceCodeLocales = listOf(\"ru\",\"en\") }<\/code><\/pre>\n<p>\u0424\u0430\u0439\u043b\u044b \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 (.properties) \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \/src\/main\/i18n. \u041d\u0443\u0436\u043d\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0442\u044c, \u0447\u0442\u043e properties-\u0444\u0430\u0439\u043b\u044b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 ANSI-\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0435 \u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0435\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438 \u0432 IDE (\u0432 IntelliJ IDEA \u0438 Android Studio: Settings -> Editor -> File Encodings -> \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c Transparent native-to-ascii conversion). <\/p>\n<pre><code class=\"kotlin\">BotMessage_en.properties  hello=Welcome<\/code><\/pre>\n<pre><code class=\"kotlin\">BotMessages_ru.properties  hello=\u0414\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441\u0442\u0440\u043e\u043a\u0438 \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0432 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435 BotMessages \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u0435 hello \u0438 \u043c\u0435\u0442\u043e\u0434 toString(locale).<\/p>\n<pre><code class=\"kotlin\">suspend fun start(context: DefaultBehaviourContextWithFSM&lt;BotState>, message:CommonMessage&lt;TextContent>) {   val locale = Locale.forLanguageTag(\"ru\")   context.startChain(StartedState(message.chat.id, locale))   sendTextMessage(it.chat.id, BotMessages.hello.toString(locale)) }  suspend fun main() {   val bot = telegramBot(token)   bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {     command(\"start\") {       val locale = Locale.forLanguageTag(\"ru\")       context.startChain(StartedState(it.chat.id, locale))       context.sendTextMessage(it.chat.id, BotMessages.hello.toString(locale))     }   }.join() }<\/code><\/pre>\n<p>\u041c\u043e\u0436\u043d\u043e \u043b\u0438 \u043a\u0430\u043a-\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043e \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u043c \u044f\u0437\u044b\u043a\u0435? \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u0447\u0442\u043e \u043c\u044b \u0438\u0437 \u043d\u0435\u0433\u043e \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 flow \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u043c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"kotlin\">suspend fun main() {     val bot = telegramBot(token)     val flowUpdatesFilter = FlowsUpdatesFilter()     bot.buildBehaviourWithFSM&lt;BotState>(flowUpdatesFilter=flowUpdatesFilter) {         retrieveAccumulatedUpdates(flowUpdatesFilter)         flowUpdatesFilter.allUpdatesFlow.collect {             println(it)         }     } }<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0431\u043e\u0442 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0430 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 Update, \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0435\u0433\u043e \u043e\u0442 API, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"bash\">MessageUpdate(updateId=249090759, data=PrivateContentMessageImpl(messageId=31, from=CommonUser(id=ChatId(chatId=112121111), firstName=Test, lastName=Test, username=Username(username=@testuser), ietfLanguageCode=ru), chat=PrivateChatImpl(id=ChatId(chatId=112121111), username=Username(username=@testuser), firstName=Test, lastName=Test), content=TextContent(text=\/start, textSources=[BotCommandTextSource(source=\/start)]), date=DateTime(1653733956000), editDate=null, hasProtectedContent=false, forwardInfo=null, replyTo=null, replyMarkup=null, senderBot=null))<\/code><\/pre>\n<p>\u042d\u0442\u0430 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043d\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u0430 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u043e\u043a\u043e\u0432 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0441\u0435\u0439\u0447\u0430\u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u043c, \u0447\u0442\u043e \u0432 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u0435 \u0435\u0441\u0442\u044c \u043a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u044f\u0437\u044b\u043a\u043e\u043c, \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u044f\u0437\u044b\u043a\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \/start \u0438 \u0442\u0430\u043a\u0436\u0435 \u0432\u044b\u0434\u0435\u043b\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043b\u043e\u0433\u0438\u043a\u0438.<\/p>\n<pre><code class=\"kotlin\">suspend fun onStart(context: DefaultBehaviourContextWithFSM&lt;BotState>, chatId: ChatId, locale: String?) {     val locale = Locale.forLanguageTag(locale ?: \"ru\")     context.startChain(StartedState(chatId, locale))     context.sendTextMessage(chatId, BotMessages.hello.toString(locale)) }  suspend fun main() {   val bot = telegramBot(token)   bot.buildBehaviourWithFSMAndStartLongPolling&lt;BotState> {     command(\"start\") {       onStart(this, it.chat.id, it.from?.asCommonUser()?.languageCode)     }   }.join() } <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043c\u043d\u043e\u0436\u043a\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u0443\u044e \u0448\u0443\u0442\u043a\u0443 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \/joke (\u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/sv443.net\/jokeapi\/v2\/\">Joke API<\/a>, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e REST-\u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0439 \u0448\u0443\u0442\u043a\u0438 \u043f\u0440\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 <a href=\"https:\/\/v2.jokeapi.dev\/joke\/Programming\">https:\/\/v2.jokeapi.dev\/joke\/Programming<\/a>). \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 API \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430:<\/p>\n<pre><code class=\"json\">{     \"error\": false,     \"category\": \"Programming\",     \"type\": \"twopart\",     \"setup\": \"Hey baby I wish your name was asynchronous...\",     \"delivery\": \"... so you'd give me a callback.\",     \"flags\": {         \"nsfw\": false,         \"religious\": false,         \"political\": false,         \"racist\": false,         \"sexist\": false,<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-333834","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/333834","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=333834"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/333834\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=333834"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=333834"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=333834"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}