{"id":296874,"date":"2020-01-08T21:00:12","date_gmt":"2020-01-08T21:00:12","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=296874"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=296874","title":{"rendered":"\u0411\u043e\u0442 \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u043e\u0432. \u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0444\u0443\u0442\u0431\u043e\u043b \u0441 \u043d\u043e\u0432\u044b\u043c\u0438 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f\u043c\u0438"},"content":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/483194\/\">\n<h2 id=\"vvedenie\">\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<p>  <\/p>\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043e\u043f\u0438\u0448\u0443 \u0441\u0432\u043e\u0435\u0433\u043e \u0447\u0430\u0442-\u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 telegram \u0438 \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 VK \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c NodeJS.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u0443 \u043c\u043d\u043e\u0433\u0438\u0445 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0434\u043e\u043b\u0436\u043d\u043e \u0432\u044b\u0440\u0432\u0430\u0442\u044c\u0441\u044f \u0447\u0442\u043e-\u0442\u043e \u0432\u0440\u043e\u0434\u0435: &#171;\u0414\u043e\u043a\u043e\u043b\u0435!&#187; \u0438\u043b\u0438 &#171;\u0427\u0442\u043e, \u043e\u043f\u044f\u0442\u044c ?!&#187;.<br \/>  \u0414\u0430, \u043f\u043e\u0445\u043e\u0436\u0438\u0435 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0443\u0436\u0435 \u0431\u044b\u043b\u0438 \u0438 \u043d\u0430 \u0445\u0430\u0431\u0440\u0435 \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435. \u041d\u043e, \u0442\u0435\u043c \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u044f \u0441\u0447\u0438\u0442\u0430\u044e, \u0447\u0442\u043e \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u0430. \u041a\u0440\u0430\u0442\u043a\u043e \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u0441 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0431\u043e\u0442\u0430:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u0430\u0440\u043a\u0430\u0441\u0430 \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043d\u0430\u0431\u0438\u0440\u0430\u044e\u0449\u0438\u0439 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e\u0441\u0442\u044c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a <a href=\"https:\/\/nestjs.com\">NestJS<\/a>.<\/li>\n<li>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/www.npmjs.com\/package\/telegraf\">telegraf<\/a> \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API Telegram.<\/li>\n<li>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/www.npmjs.com\/package\/node-vk-bot-api\">node-vk-bot-api<\/a> \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API VK.<\/li>\n<li>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/www.npmjs.com\/package\/typeorm\">typeorm<\/a> \u0434\u043b\u044f \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043b\u043e\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445.<\/li>\n<li>\u0422\u0435\u0441\u0442\u044b \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c <a href=\"https:\/\/www.npmjs.com\/package\/mocha\">mocha<\/a> \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0430\u0441\u0441\u0435\u0440\u0442\u043e\u0432 <a href=\"https:\/\/www.npmjs.com\/package\/chai\">chai<\/a> .<\/li>\n<li>CI \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Travis CI \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 GitHub Actions \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f \u0434\u043e\u043a\u0435\u0440-\u043e\u0431\u0440\u0430\u0437\u043e\u0432.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u0431\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u043e\u0434\u0440\u0443\u0436\u0438\u0442\u044c \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u043e\u0442\u0430 \u0441 Viber \u0434\u0435\u043b\u0430\u044f \u0435\u0433\u043e \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u043c \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445 \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438. <\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043c \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442 \u0443\u0437\u043d\u0430\u0442\u044c \u0447\u0442\u043e \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h2 id=\"postanovka-zadachi\">\u041f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438<\/h2>\n<p>  <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u044f \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u044e \u0444\u0443\u0442\u0431\u043e\u043b. \u041d\u0430 \u043b\u044e\u0431\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435 \u0440\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f. \u042f\u0432\u043b\u044f\u044f\u0441\u044c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043a\u0430\u043d\u0430\u043b\u043e\u0432 \u0438 \u0447\u0430\u0442\u043e\u0432 \u0432 vk, viber \u0438 telegram \u0447\u0430\u0441\u0442\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u0438\u0434\u0435\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u0443: <\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/jt\/c7\/h8\/jtc7h8oxebenbzwymug3y2oi0oo.png\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u043c\u044b \u0437\u0434\u0435\u0441\u044c \u0432\u0438\u0434\u0438\u043c:<\/p>\n<p>  <\/p>\n<ol>\n<li>&#171;\u041f&#187; \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0441\u0435\u0431\u044f.<\/li>\n<li>\u041f\u043e\u0441\u043b\u0435 \u043d\u0435\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u043e\u0441\u044c \u0435\u0449\u0435 3 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u0441\u0440\u0435\u0434\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u044b\u043b \u043d\u0435\u043a\u0442\u043e &#171;\u0421&#187;.<\/li>\n<li>&#171;\u041f&#187; \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0441\u0432\u043e\u0435\u0433\u043e \u0442\u043e\u0432\u0430\u0440\u0438\u0449\u0430 \u043f\u043e \u0438\u043c\u0435\u043d\u0438 &#171;\u0414\u0438\u043c\u043e\u043d&#187;.<\/li>\n<li>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e &#171;\u041f&#187; \u043d\u0435 \u043f\u043e\u043b\u0435\u043d\u0438\u043b\u0441\u044f \u0438 \u043f\u043e\u0434\u0441\u0447\u0438\u0442\u0430\u043b \u0447\u0442\u043e \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0430\u0436 5 \u0447\u0435\u043b\u043e\u0432\u0435\u043a.<\/li>\n<li>\u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0431\u044b\u043b\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u043d\u0435\u0434\u043e\u043b\u0433\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u044f \u0442\u043e\u0436\u0435 \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u0431\u0435\u0433\u0430\u0442\u044c \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0441\u0435\u0431\u044f.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412 \u043e\u0441\u043e\u0431\u043e \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043b\u044e\u0434\u0438 \u043c\u043e\u0433\u0443\u0442 \u0441\u0442\u0430\u0432\u0438\u0442\u044c &#171;+&#187; \u0430 \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0432\u043e\u0435 \u0443\u0447\u0430\u0441\u0442\u0438\u0435, \u043f\u0440\u0438\u0433\u043b\u0430\u0448\u0430\u0442\u044c \u0434\u0440\u0443\u0437\u0435\u0439 \u043d\u0435 \u0438\u0437 \u0447\u0430\u0442\u0430 \u0438\u043b\u0438 \u043e\u0442\u043c\u0435\u0447\u0430\u0442\u044c\u0441\u044f \u0437\u0430 \u0434\u0440\u0443\u0433\u0438\u0445 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u0447\u0430\u0442\u0430 \u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0447\u0438\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438. \u0412 \u0438\u0442\u043e\u0433\u0435 \u044d\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u0432\u0441\u0435 \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u0440\u0438\u0435\u0437\u0436\u0430\u044e\u0442 \u0438\u0433\u0440\u0430\u0442\u044c \u0432\u044b\u044f\u0441\u043d\u044f\u0435\u0442\u0441\u044f \u0447\u0442\u043e:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0412\u0430\u0441\u044f \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043b +1 \u0438\u043c\u0435\u044f \u0432\u0432\u0438\u0434\u0443 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u0431\u044f, \u043d\u043e \u0438 \u0441\u0432\u043e\u0435\u0433\u043e \u0434\u0440\u0443\u0433\u0430 \u0413\u043e\u0448\u0443.<\/li>\n<li>\u041a\u043e\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0447\u0442\u043e \u043f\u0440\u0438\u0439\u0434\u0435\u0442, \u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0442\u043e\u0440 \u0421\u043f\u0438\u0440\u0438\u0434\u043e\u043d \u043f\u043e\u0441\u0447\u0438\u0442\u0430\u043b \u0433\u043b\u0430\u0437\u0430\u043c\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043b\u044e\u0441\u0438\u043a\u0438.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u0438\u043c\u0435\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043d\u0435\u0440\u0430\u0432\u043d\u044b\u0435 \u043f\u043e \u043a\u043e\u043b-\u0432\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, &#171;\u043b\u0438\u0448\u043d\u0435\u0433\u043e \u0413\u043e\u0448\u0443&#187; \u0438 \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0448\u0435\u0433\u043e \u041a\u043e\u043b\u044e.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0445 \u043a\u043e\u043b\u043b\u0438\u0437\u0438\u0439 \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u0431\u043e\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u0430\u0432 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u043f\u0440\u0435\u0434\u0441\u0442\u043e\u044f\u0449\u0435\u0439 \u0438\u0433\u0440\u044b \u0438 \u0443\u0434\u043e\u0431\u043d\u043e \u0432 \u043d\u0435\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c(\u0441\u044f)\/\u0443\u0434\u0430\u043b\u044f\u0442\u044c(\u0441\u044f).<\/p>\n<p>  <\/p>\n<p>\u0418\u0442\u0430\u043a, \u0432 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e \u043c\u043e\u0435\u043c\u0443 \u043c\u043d\u0435\u043d\u0438\u044e \u0431\u043e\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u043b \u0443\u043c\u0435\u0442\u044c:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0411\u044b\u0442\u044c \u0432\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u043c \u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b-\u0432\u043e \u0447\u0430\u0442\u043e\u0432. \u0425\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0447\u0430\u0442\u0430 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438.<\/li>\n<li>\u041f\u043e \u043a\u043e\u043c\u0430\u043d\u0434\u0435 <em>\/event_add<\/em> \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u0438 \u043f\u0443\u0441\u0442\u044b\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0438\u0433\u0440\u043e\u043a\u043e\u0432. \u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c.<\/li>\n<li>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 <em>\/event_remove<\/em> \u0434\u043e\u043b\u0436\u043d\u0430 \u043e\u0442\u043c\u0435\u043d\u044f\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435.<\/li>\n<li>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 <em>\/add<\/em> \u0434\u043e\u043b\u0436\u043d\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u043e\u0433\u043e \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430 \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0435\u0441\u043b\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0432\u044b\u0437\u0432\u0430\u043d\u0430 \u0431\u0435\u0437 \u043a\u0430\u043a\u043e\u0433\u043e-\u043b\u0438\u0431\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430, \u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0434\u043e\u043b\u0436\u0435\u043d \u0442\u043e\u0442, \u043a\u0442\u043e \u043d\u0430\u0431\u0440\u0430\u043b \u043a\u043e\u043c\u0430\u043d\u0434\u0443. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u0435\u043b\u043e\u0432\u0435\u043a \u0447\u044c\u0435 \u0438\u043c\u044f \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b.<\/li>\n<li>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 <em>\/remove<\/em> \u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u043f\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u043c, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u043c \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b <em>\/add<\/em>.<\/li>\n<li>\u041a\u043e\u043c\u0430\u043d\u0434\u0430 <em>\/info<\/em> \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u0442\u043e \u0443\u0436\u0435 \u0437\u0430\u043f\u0438\u0441\u0430\u043b\u0441\u044f \u043d\u0430 \u0438\u0433\u0440\u0443.<\/li>\n<li>\u041e\u0447\u0435\u043d\u044c \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u0431\u043e\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u043b \u043d\u0430 \u0442\u043e\u043c \u0436\u0435 \u044f\u0437\u044b\u043a\u0435 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0443 \u0438\u0433\u0440\u043e\u043a\u0430 (\u0438\u043b\u0438 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e).<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0432 \u0438\u0442\u043e\u0433\u0435, \u043c\u043e\u0436\u043d\u043e \u0441\u0440\u0430\u0437\u0443 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u043c\u0443 \u0440\u0430\u0437\u0434\u0435\u043b\u0443 &#171;\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438 \u0432\u044b\u0432\u043e\u0434\u044b&#187;. \u041d\u0443 \u0430 \u0435\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0434\u0435\u0442\u0430\u043b\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438, \u0442\u043e \u0434\u0430\u043d\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0438\u0437\u043b\u043e\u0436\u0435\u043d\u0430 \u043d\u0438\u0436\u0435.<\/p>\n<p>  <\/p>\n<h2 id=\"proektirovanie-i-razrabotka\">\u041f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430<\/h2>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0433\u043e\u043b\u043e\u0432\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0441\u0445\u0435\u043c\u0430:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/v7\/j4\/gz\/v7j4gzjufgbeuayina59yy8blia.jpeg\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043b\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432\u0441\u044e \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0443 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c\u0438 \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u044b \u0438 \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u043d\u0438\u0445 \u043b\u043e\u0433\u0438\u043a\u0443 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u043c\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e\u0449\u0438\u043c\u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 API, \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043a \u0435\u0434\u0438\u043d\u043e\u043c\u0443 \u0432\u0438\u0434\u0443.<\/p>\n<p>  <\/p>\n<h3 id=\"interfeys-i-modeli-soobscheniy\">\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/h3>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0441\u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <em>IMessage<\/em>, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u044f\u044e\u0442 \u043a\u043b\u0430\u0441\u0441\u044b, \u0445\u0440\u0430\u043d\u044f\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c \u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u044d\u0442\u0438\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">export interface IMessage {     chatId: number;     lang: string;     text: string;     fullText: string;     command: string;     name: string;     getReplyStatus: () =&gt; string;     getReplyData: () =&gt; any;     setStatus: (status: string) =&gt; IMessage;     withData: (data: any) =&gt; IMessage;     answer: (args: any) =&gt; string | void; }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 <em>BaseMessage<\/em>, \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e\u0449\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0438\u043c\u0435\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0432\u0438\u0434:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">BaseMessage<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {IMessage} from '..\/message\/i-message';  export class BaseMessage implements IMessage {     public chatId: number;     public lang: string;     public text: string;     public fullText: string;     public command: string;      protected firstName: string;     protected lastName: string;      protected replyStatus: string;     protected replyData: any;      get name(): string {         const firstName: string = this.firstName || '';         const lastName: string = this.lastName || '';          return `${firstName} ${lastName}`.trim();     }      public getReplyStatus(): string {         return this.replyStatus;     }      public getReplyData(): any {         return this.replyData;     }      public setStatus(status: string): IMessage {         this.replyStatus = status;         return this;     }      public withData(data: any): IMessage {         this.replyData = data;         return this;     }      public answer(args: any): string | void {         throw new Error('not implemented');     } } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u041a\u043b\u0430\u0441\u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0434\u043b\u044f Telegram:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">TelegramMessage<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {BaseMessage} from '..\/message\/base.message'; import {IMessage} from '..\/message\/i-message';  export class TelegramMessage extends BaseMessage implements IMessage {     private ctx: any;      constructor(ctx) {         super();          this.ctx = ctx;          const {message} = this.ctx.update;         this.chatId = message.chat.id;         this.fullText = message.text;         this.command = this.ctx.command;         this.text = this.fullText.replace(`\/${this.command}`, '');         this.lang = message.from.language_code;         this.firstName = message.from.first_name;         this.lastName = message.from.last_name;     }      public answer(args: any): string | void {         return this.ctx.replyWithHTML(args);     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0438 \u0434\u043b\u044f VK:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">VKMessage<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {BaseMessage} from '..\/message\/base.message'; import {IMessage} from '..\/message\/i-message';  export class VKMessage extends BaseMessage implements IMessage {     private ctx: any;      constructor(ctx) {         super();          this.ctx = ctx;          const {message} = this.ctx;         this.chatId = this.getChatId(this.ctx);         this.fullText = message.text;         this.command = this.ctx.command;         this.text = this.fullText.replace(`\/${this.command}`, '');         this.lang = 'ru';         this.firstName = message.from.first_name;         this.lastName = message.from.last_name;     }      public answer(args: any) {         const answer: string = `${args}`.replace(\/&lt;\\\/?(strong|i)&gt;\/gm, '');         this.ctx.reply(answer);     }      private getChatId({message, bot}): number {         const peerId: number = +`${message.peer_id}`.replace(\/[0-9]0+\/, '');         const groupId: number = bot.settings.group_id;         return peerId + groupId;     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u041d\u0430 \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u0441\u0442\u043e\u0438\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<ol>\n<li>\n<p><em>chatId<\/em> \u2014 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0447\u0430\u0442\u0430. \u0414\u043b\u044f Telegram \u043e\u043d \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0432 \u044f\u0432\u043d\u043e\u043c \u0432\u0438\u0434\u0435 \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u041e\u0434\u043d\u0430\u043a\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 VK \u0442\u0430\u043a\u043e\u0433\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0432 \u044f\u0432\u043d\u043e\u043c \u0432\u0438\u0434\u0435 \u043d\u0435\u0442. \u041a\u0430\u043a \u0436\u0435 \u0431\u044b\u0442\u044c? \u0427\u0442\u043e\u0431\u044b \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430 \u044d\u0442\u043e\u0442 \u0432\u043e\u043f\u0440\u043e\u0441 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c \u043a\u0430\u043a \u0431\u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0438\u0440\u0443\u0435\u0442 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 VK. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0431\u043e\u0442 \u0432 \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u043e\u0439 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0435 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u043e\u0442 \u043b\u0438\u0446\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043e\u043d \u0437\u0430\u0432\u043e\u0434\u0438\u0442\u0441\u044f. \u0422.\u0435. \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u043e\u0442\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430 <em>group_id<\/em>. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0438 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 <em>peer_id<\/em> (\u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/vk.com\/dev\/messages.send\">\u0437\u0434\u0435\u0441\u044c<\/a>) \u043a\u0430\u043a \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u043e\u0439 \u0431\u0435\u0441\u0435\u0434\u044b \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435. \u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u044d\u0442\u0438\u0445 \u0434\u0432\u0443\u0445 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0441\u0432\u043e\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0447\u0430\u0442\u0430 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">private getChatId({message, bot}): number {     const peerId: number = +(`${message.peer_id}`.replace(\/[0-9]0+\/, ''));     const groupId: number = bot.settings.group_id;     return peerId + groupId; }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u043d\u0435 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0435\u043d, \u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043c \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438.<\/p>\n<p>  <\/li>\n<li>\n<p><em>fullText<\/em> \u0438 <em>text<\/em>. \u041a\u0430\u043a \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445, <em>fullText<\/em> \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043f\u043e\u043b\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0432 \u0442\u043e \u0432\u0440\u0435\u043c\u044f \u043a\u0430\u043a <em>text<\/em> \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0443 \u0447\u0430\u0441\u0442\u044c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0434\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b.<\/p>\n<p>  <\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u0443\u0434\u043e\u0431\u043d\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435 <em>text<\/em> \u0434\u043b\u044f \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0435\u0439\u0441\u044f \u0432 \u043d\u0435\u043c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432\u0440\u043e\u0434\u0435 \u0434\u0430\u0442\u044b \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438\u043b\u0438 \u0438\u043c\u0435\u043d\u0438 \u0438\u0433\u0440\u043e\u043a\u0430.<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 Telegram \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f VK \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 \u043d\u0430\u0431\u043e\u0440 \u0442\u0435\u0433\u043e\u0432 \u0434\u043b\u044f \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 \u0432\u0440\u043e\u0434\u0435 <code>&lt;b&gt;<\/code> \u0438\u043b\u0438 <code>&lt;i&gt;<\/code>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438 \u043e\u0442\u0432\u0435\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u044d\u0433\u0438 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0443\u0434\u0430\u043b\u044f\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0445 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439.<\/li>\n<\/ol>\n<p>  <\/p>\n<h3 id=\"adaptery-dlya-priema-i-otpravki-soobscheniy\">\u0410\u0434\u0430\u043f\u0442\u0435\u0440\u044b \u0434\u043b\u044f \u043f\u0440\u0438\u0435\u043c\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/h3>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Telegram \u0438 VK \u043f\u0440\u0438\u0448\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/gz\/-z\/2y\/gz-z2y0x-x9_78hjdhg2mul9byu.jpeg\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u042d\u0442\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u0438 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API Telegram \u0438 VK \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b long-polling, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0438\u043c\u0435\u0442\u044c \u0441\u0432\u044f\u0437\u044c \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434 \u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0449\u0438\u0445 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">TelegramService<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import Telegraf from 'telegraf';  import {Injectable} from '@nestjs\/common'; import * as SocksAgent from 'socks5-https-client\/lib\/Agent'; import {ConfigService} from '..\/common\/config.service'; import {AppEmitter} from '..\/common\/event-bus.service'; import {TelegramMessage} from '.\/telegram.message';  @Injectable() export class TelegramService {     private bot: Telegraf&lt;any&gt;;      constructor(config: ConfigService, appEmitter: AppEmitter) {         const botToken: string = config.get('TELEGRAM_BOT_TOKEN');          this.bot = config.get('TELEGRAM_USE_PROXY')             ? new Telegraf(botToken, {                   telegram: {agent: this.getProxy(config)},               })             : new Telegraf(botToken);          this.getCommandEventMapping(appEmitter).forEach(([command, event]) =&gt; {             this.bot.command(command, ctx =&gt; {                 ctx.command = command;                 appEmitter.emit(event, new TelegramMessage(ctx));             });         });     }      public launch(): void {         this.bot.launch();     }      private getProxy(config: ConfigService): SocksAgent {         return new SocksAgent({             socksHost: config.get('TELEGRAM_PROXY_HOST'),             socksPort: config.get('TELEGRAM_PROXY_PORT'),             socksUsername: config.get('TELEGRAM_PROXY_LOGIN'),             socksPassword: config.get('TELEGRAM_PROXY_PASSWORD'),         });     }      private getCommandEventMapping(         appEmitter: AppEmitter,     ): Array&lt;[string, string]&gt; {         return [             ['event_add', appEmitter.EVENT_ADD],             ['event_remove', appEmitter.EVENT_REMOVE],             ['info', appEmitter.EVENT_INFO],             ['add', appEmitter.PLAYER_ADD],             ['remove', appEmitter.PLAYER_REMOVE],         ];     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">VKService<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import * as VkBot from 'node-vk-bot-api';  import {Injectable} from '@nestjs\/common'; import {ConfigService} from '..\/common\/config.service'; import {AppEmitter} from '..\/common\/event-bus.service'; import {VKMessage} from '.\/vk.message';  @Injectable() export class VKService {     private bot: VkBot&lt;any&gt;;      constructor(config: ConfigService, appEmitter: AppEmitter) {         const botToken: string = config.get('VK_TOKEN');          this.bot = new VkBot(botToken);          this.getCommandEventMapping(appEmitter).forEach(([command, event]) =&gt; {             this.bot.command(`\/${command}`, async ctx =&gt; {                 const [from] = await this.bot.execute('users.get', {                     user_ids: ctx.message.from_id,                 });                 ctx.message.from = from;                 ctx.command = command;                 appEmitter.emit(event, new VKMessage(ctx));             });         });     }      public launch(): void {         this.bot.startPolling();     }      private getCommandEventMapping(         appEmitter: AppEmitter,     ): Array&lt;[string, string]&gt; {         return [             ['event_add', appEmitter.EVENT_ADD],             ['event_remove', appEmitter.EVENT_REMOVE],             ['info', appEmitter.EVENT_INFO],             ['add', appEmitter.PLAYER_ADD],             ['remove', appEmitter.PLAYER_REMOVE],         ];     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u041d\u0430 \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u0441\u0442\u043e\u0438\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0418\u0437-\u0437\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0443 Telegram (\u043f\u0440\u0438\u0432\u0435\u0442 \u0420\u043e\u0441\u043a\u043e\u043c\u043d\u0430\u0434\u0437\u043e\u0440) \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u043a\u0441\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0430\u043a\u0435\u0442 <a href=\"https:\/\/www.npmjs.com\/package\/socks5-https-client\">socks5-https-client<\/a>.<\/li>\n<li>\u041f\u0440\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u0435 \u0441\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0441 \u0442\u0435\u043a\u0441\u0442\u043e\u043c \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u044b\u043b\u043e \u043f\u0440\u0438\u043d\u044f\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435.<\/li>\n<li>\u0414\u043b\u044f VK \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e API \u0432\u044b\u0437\u043e\u0432\u0430:<br \/> \n<pre><code class=\"javascript\">const [from] = await this.bot.execute('users.get', {     user_ids: ctx.message.from_id, });<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>  <\/p>\n<h3 id=\"model-dannyh\">\u041c\u043e\u0434\u0435\u043b\u044c \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0430\u044f\u0432\u043b\u0435\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0431\u043e\u0442\u0430 \u0431\u044b\u043b\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043d\u0430\u043b\u0438\u0447\u0438\u044f \u0442\u0440\u0435\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439:<\/p>\n<p>  <\/p>\n<ol>\n<li><code>Chat<\/code> \u2014 \u0447\u0430\u0442 \u0438\u043b\u0438 \u0431\u0435\u0441\u0435\u0434\u0430.<\/li>\n<li><code>Event<\/code> \u2014 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0434\u043b\u044f \u0447\u0430\u0442\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u043e\u0435 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u0434\u0430\u0442\u0443 \u0438 \u0432\u0440\u0435\u043c\u044f.<\/li>\n<li><code>Player<\/code> \u2014 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a \u0438\u043b\u0438 \u0438\u0433\u0440\u043e\u043a \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0449\u0438\u0439 \u0443\u0447\u0430\u0441\u0442\u0438\u0435 \u0432 \u0441\u043e\u0431\u044b\u0442\u0438\u0438.<\/li>\n<\/ol>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/yj\/dc\/rw\/yjdcrwzw4bsie2im9fuelyb590k.jpeg\" alt=\"\u0421\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445\"><\/p>\n<p>  <\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 <a href=\"https:\/\/www.npmjs.com\/package\/typeorm\">typeorm<\/a> \u0438\u043c\u0435\u044e\u0442 \u0432\u0438\u0434:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Chat<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {     Entity,     PrimaryGeneratedColumn,     Column,     OneToMany,     JoinColumn,     Index, } from 'typeorm'; import {Event} from '.\/event';  @Entity() export class Chat {     @PrimaryGeneratedColumn()     id: number;      @Index({unique: true})     @Column()     chatId: number;      @OneToMany(type =&gt; Event, event =&gt; event.chat)     @JoinColumn()     events: Event[]; }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Event<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {     Entity,     PrimaryGeneratedColumn,     Column,     OneToMany,     ManyToOne,     JoinColumn, } from 'typeorm'; import {Chat} from '.\/chat'; import {Player} from '.\/player';  @Entity() export class Event {     @PrimaryGeneratedColumn()     id: number;      @Column()     date: Date;      @Column()     active: boolean;      @ManyToOne(type =&gt; Chat, chat =&gt; chat.events)     chat: Chat;      @OneToMany(type =&gt; Player, player =&gt; player.event)     @JoinColumn()     players: Player[]; }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Player<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {     Entity,     PrimaryGeneratedColumn,     Column,     ManyToOne,     JoinColumn, } from 'typeorm'; import {Event} from '.\/event';  @Entity() export class Player {     @PrimaryGeneratedColumn()     id: number;      @Column()     name: string;      @ManyToOne(type =&gt; Event, event =&gt; event.players)     @JoinColumn()     event: Event; }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u043d\u043e \u0435\u0449\u0435 \u0440\u0430\u0437 \u043f\u0440\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0440\u0430\u0431\u043e\u0442\u044b \u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043e\u043c\u0430\u043d\u0434 \u0441 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0441 \u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438 <em>Chat<\/em>, <em>Event<\/em> \u0438 <em>Player<\/em>.<\/p>\n<p>  <\/p>\n<ol>\n<li>\u041f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043b\u044e\u0431\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u0411\u0414 \u0447\u0430\u0442\u0430 (<em>Chat<\/em>) \u0441 <code>chatId<\/code>. \u0415\u0441\u043b\u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0435\u0442, \u0442\u043e \u043e\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f.<\/li>\n<li>\u0414\u043b\u044f \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f \u043b\u043e\u0433\u0438\u043a\u0438 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0447\u0430\u0442\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 (<em>Event<\/em>). \u0422.\u0435. \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b <code>\/event_add<\/code> \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u043e\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u0434\u0430\u0442\u043e\u0439.<br \/>  \u0422\u0435\u043a\u0443\u0449\u0435\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 (\u0435\u0441\u043b\u0438 \u043e\u043d\u043e \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442) \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c.<\/li>\n<li><code>\/event_remove<\/code> \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0447\u0430\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0438 \u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u0442 \u0435\u0433\u043e.<\/li>\n<li><code>\/info<\/code> \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0447\u0430\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435, \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044e \u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432 (<em>Player<\/em>).<\/li>\n<li><code>\/add<\/code> \u0438 <code>\/remove<\/code> \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0441 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044f \u0438 \u0443\u0434\u0430\u043b\u044f\u044f \u0438\u0437 \u043d\u0435\u0433\u043e \u0438\u0433\u0440\u043e\u043a\u043e\u0432.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">StorageService<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable} from '@nestjs\/common'; import {InjectConnection} from '@nestjs\/typeorm'; import {Connection, Repository, UpdateResult} from 'typeorm';  import {Chat} from '.\/models\/chat'; import {Event} from '.\/models\/event'; import {Player} from '.\/models\/player';  @Injectable() export class StorageService {     private chatRepository: Repository&lt;Chat&gt;;     private eventRepository: Repository&lt;Event&gt;;     private playerRepository: Repository&lt;Player&gt;;      constructor(@InjectConnection() private readonly dbConnection: Connection) {         this.chatRepository = this.dbConnection.getRepository(Chat);         this.eventRepository = this.dbConnection.getRepository(Event);         this.playerRepository = this.dbConnection.getRepository(Player);     }      public get connection() {         return this.dbConnection;     }      public async ensureChat(chatId: number): Promise&lt;Chat&gt; {         let chat: Chat = await this.chatRepository.findOne({chatId});          if (chat) {             return chat;         }          chat = new Chat();         chat.chatId = chatId;          return this.chatRepository.save(chat);     }      public markChatEventsInactive(chat: Chat): Promise&lt;UpdateResult&gt; {         return this.dbConnection             .createQueryBuilder()             .update(Event)             .set({active: false})             .where({chat})             .execute();     }      public appendChatActiveEvent(chat: Chat, date: Date): Promise&lt;Event&gt; {         const event: Event = new Event();         event.chat = chat;         event.active = true;         event.date = date;          return this.eventRepository.save(event);     }      public findChatActiveEvent(chat: Chat): Promise&lt;Event | null&gt; {         return this.eventRepository.findOne({where: {chat, active: true}});     }     public getPlayers(event: Event): Promise&lt;Player[]&gt; {         return this.playerRepository.find({where: {event}});     }      public findPlayer(event: Event, name: string): Promise&lt;Player | null&gt; {         return this.playerRepository.findOne({where: {event, name}});     }      public addPlayer(event: Event, name: string): Promise&lt;Player&gt; {         const player: Player = new Player();         player.event = event;         player.name = name;          return this.playerRepository.save(player);     }      public removePlayer(player: Player): Promise&lt;Player&gt; {         return this.playerRepository.remove(player);     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<h3 id=\"shablonizaciya-otvetov\">\u0428\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u043e\u0432<\/h3>\n<p>  <\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 <em>StorageService<\/em> \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0442\u0430\u043a\u0436\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0432 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441 <em>TemplateService<\/em> \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0438 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u044f <a href=\"https:\/\/handlebarsjs.com\">handlebars<\/a> \u0448\u0430\u0431\u043b\u043e\u043d\u043e\u0432 \u0434\u043b\u044f \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u043d\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u044f\u0437\u044b\u043a\u0430\u0445.<\/li>\n<li>\u0412\u044b\u0431\u043e\u0440 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u043e\u0442\u0432\u0435\u0442\u0430 \u0438 \u044f\u0437\u044b\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/li>\n<li>\u0417\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c\u0438 \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b.<\/li>\n<\/ol>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">TemplateService<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import * as path from 'path'; import * as fs from 'fs'; import * as handlebars from 'handlebars'; import {readDirDeepSync} from 'read-dir-deep'; import {Injectable, Logger} from '@nestjs\/common';  export interface IParams {     action: string;     status: string;     lang?: string; }  @Injectable() export class TemplateService {     private readonly DEFAULT_LANG: string = 'en';     private readonly TEMPLATE_PATH: string = 'templates';      private logger: Logger;     private templatesMap: Map&lt;string, (d: any) =&gt; string&gt;;      constructor(logger: Logger) {         this.logger = logger;          this.load();     }      public apply(params: IParams, data: any): string {         this.logger.log(             `apply template: ${params.action} ${params.status} ${params.lang}`,         );          let template = this.getTemplate(params);          if (!template) {             params.lang = this.DEFAULT_LANG;             template = this.getTemplate(params);         }          if (!template) {             throw new Error('template-not-found');         }          return template(data);     }      private getTemplate(params: IParams): (data: any) =&gt; string {         const {lang, action, status} = params;         return this.templatesMap.get(this.getTemplateKey(lang, action, status));     }      private load() {         const templatesDir: string = path.join(             process.cwd(),             this.TEMPLATE_PATH,         );         const templateFileNames: string[] = readDirDeepSync(templatesDir);          this.templatesMap = templateFileNames.reduce((acc, fileName) =&gt; {             const template = fs.readFileSync(fileName, {encoding: 'utf-8'});              const [, lang, action, status] = fileName                 .replace(\/\\.hbs$\/, '')                 .split('\/');             return acc.set(                 this.getTemplateKey(lang, action, status),                 handlebars.compile(template),             );         }, new Map());     }      private getTemplateKey(         lang: string,         action: string,         status: string,     ): string {         return `${lang}-${action}-${status}`;     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u041d\u0430 \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u0441\u0442\u043e\u0438\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<ol>\n<li>\n<p>\u0424\u0430\u0439\u043b\u044b \u0448\u0430\u0431\u043b\u043e\u043d\u043e\u0432 \u043b\u0435\u0436\u0430\u0442 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>.\/templates<\/code> \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u043f\u043e \u044f\u0437\u044b\u043a\u0443, \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044e (\u043a\u043e\u043c\u0430\u043d\u0434\u0435) \u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u0443 \u043e\u0442\u0432\u0435\u0442\u0430.<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">- templates  - en      - event_add         - invalid_date.hbs         - success.hbs      - event_info  - ru<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432\u0441\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u044b \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u0430\u043c\u044f\u0442\u044c \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e\u0442 Map \u0441 \u043a\u043b\u044e\u0447\u0430\u043c\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0434\u043d\u043e\u0437\u043d\u0430\u0447\u043d\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u044e\u0442 \u0448\u0430\u0431\u043b\u043e\u043d:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">private getTemplateKey(lang: string, action: string, status: string): string {   return `${lang}-${action}-${status}`; }<\/code><\/pre>\n<p>  <\/li>\n<li>\n<p>\u0412 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0435\u0441\u0442\u044c 2 \u043d\u0430\u0431\u043e\u0440\u0430 \u0448\u0430\u0431\u043b\u043e\u043d\u043e\u0432: \u0434\u043b\u044f \u0440\u0443\u0441\u0441\u043a\u043e\u0433\u043e \u0438 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u043e\u0432.<br \/>  \u041f\u0440\u0435\u0434\u0443\u0441\u043c\u043e\u0442\u0440\u0435\u043d fallback \u043d\u0430 \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439 \u044f\u0437\u044b\u043a (\u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439) \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0433\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0434\u043b\u044f \u044f\u0437\u044b\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f.<\/p>\n<p>  <\/li>\n<li>\n<p>\u0421\u0430\u043c\u0438 <a href=\"https:\/\/handlebarsjs.com\">handlebars<\/a> \u0448\u0430\u0431\u043b\u043e\u043d\u044b, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">Player &lt;strong&gt;{{name}}&lt;\/strong&gt; will take part in the game  List of players: {{#each players}} {{index}}: &lt;i&gt;{{name}}&lt;\/i&gt; {{\/each}} Total: &lt;strong&gt;{{total}}&lt;\/strong&gt;<\/code><\/pre>\n<p>  <\/p>\n<p>\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u043a\u0430\u043a \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0435 placeholder-\u044b, \u0442\u0430\u043a \u0438 <a href=\"https:\/\/tlgrm.ru\/docs\/bots\/api#formatting-options\">\u043d\u0430\u0431\u043e\u0440 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0445 \u0442\u0435\u0433\u043e\u0432<\/a> \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0432 Telegram.<\/p>\n<p>  <\/li>\n<\/ol>\n<p>  <\/p>\n<h3 id=\"servisy-obrabotki-komand-actions\">\u0421\u0435\u0440\u0432\u0438\u0441\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434 (Actions)<\/h3>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u044b \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u043f\u0440\u0438\u0448\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438 \u0431\u043e\u0442\u0430. \u0421\u0445\u0435\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0441\u0432\u044f\u0437\u044c \u043c\u0435\u0436\u0434\u0443 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0437\u0434\u0435\u0441\u044c:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/b3\/xl\/vl\/b3xlvlw5qc93vkc3n-nylnjwpqa.jpeg\" alt=\"actions\"><\/p>\n<p>  <\/p>\n<p>\u0412 \u0437\u0430\u0434\u0430\u0447\u0438 \u0431\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 <em>BaseAction<\/em> \u0432\u0445\u043e\u0434\u0438\u0442:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043e\u0442 <em>AppEmitter<\/em>.<\/li>\n<li>\u041e\u0431\u0449\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e:<br \/> \n<ul>\n<li>\u043f\u043e\u0438\u0441\u043a \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0447\u0430\u0442\u0430 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430.<\/li>\n<li>\u0412\u044b\u0437\u043e\u0432 \u0448\u0430\u0431\u043b\u043e\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 <code>doAction<\/code> \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e <em>BaseAction<\/em>.<\/li>\n<li>\u041f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u043e\u0432 \u043a \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u043c\u0443 \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f <code>doAction<\/code> \u043e\u0442\u0432\u0435\u0442\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <em>TemplateService<\/em>.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">BaseAction<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable, Logger} from '@nestjs\/common'; import {IMessage} from '..\/message\/i-message'; import {ConfigService} from '..\/common\/config.service'; import {AppEmitter} from '..\/common\/event-bus.service'; import {TemplateService} from '..\/common\/template.service'; import {StorageService} from '..\/storage\/storage.service'; import {Chat} from '..\/storage\/models\/chat';  @Injectable() export class BaseAction {     protected appEmitter: AppEmitter;     protected config: ConfigService;     protected logger: Logger;      protected templateService: TemplateService;     protected storageService: StorageService;      protected event: string;      constructor(         config: ConfigService,         appEmitter: AppEmitter,         logger: Logger,         templateService: TemplateService,         storageService: StorageService,     ) {         this.config = config;         this.logger = logger;          this.appEmitter = appEmitter;         this.templateService = templateService;         this.storageService = storageService;          this.setEvent();          this.logger.log(`subscribe on \"${this.event}\" event`);         this.appEmitter.on(this.event, this.handleEvent.bind(this));     }      protected setEvent(): void {         throw new Error('not implemented');     }      protected async doAction(chat: Chat, message: IMessage): Promise&lt;IMessage&gt; {         throw new Error('not implemented');     }      private async handleEvent(message: IMessage) {         try {             this.logger.log(`\"${this.event}\" event received`);              const chatId: number = message.chatId;             const chat: Chat = await this.storageService.ensureChat(chatId);             message = await this.doAction(chat, message);              message.answer(                 this.templateService.apply(                     {                         action: this.event,                         status: message.getReplyStatus(),                         lang: message.lang,                     },                     message.getReplyData(),                 ),             );         } catch (error) {             this.logger.error(error);             message.answer(error.message);         }     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0417\u0430\u0434\u0430\u0447\u0435\u0439 \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0445 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 <em>BaseAction<\/em> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 <code>doAction<\/code> \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0449\u0435\u0433\u043e \u043d\u0430 \u0432\u0445\u043e\u0434:<\/p>\n<p>  <\/p>\n<ol>\n<li><em>Chat<\/em> \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b\u043b \u0441\u043e\u0437\u0434\u0430\u043d \u0438\u043b\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d \u0432 \u0431\u0430\u0437\u043e\u0432\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435<\/li>\n<li>\u041e\u0431\u044a\u0435\u043a\u0442 \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u044f\u044e\u0449\u0438\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 <em>IMessage<\/em>.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u0442\u0430\u043a\u0436\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f <em>IMessage<\/em>, \u043d\u043e \u0441 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0442\u0432\u0435\u0442\u0430.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">EventAddAction<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable} from '@nestjs\/common';  import * as statuses from '.\/statuses'; import {parseEventDate, formatEventDate} from '..\/common\/utils'; import {BaseAction} from '.\/base.action'; import {Chat} from '..\/storage\/models\/chat'; import {Event} from '..\/storage\/models\/event'; import {IMessage} from '..\/message\/i-message';  @Injectable() export class EventAddAction extends BaseAction {     protected setEvent(): void {         this.event = this.appEmitter.EVENT_ADD;     }      protected async doAction(chat: Chat, message: IMessage): Promise&lt;IMessage&gt; {         await this.storageService.markChatEventsInactive(chat);          const eventDate: Date = parseEventDate(message.text.trim());          if (!eventDate) {             return message.setStatus(statuses.STATUS_INVALID_DATE);         }          const event: Event = await this.storageService.appendChatActiveEvent(             chat,             eventDate,         );          return message.setStatus(statuses.STATUS_SUCCESS).withData({             date: formatEventDate(event.date),         });     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">EventRemoveAction<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable} from '@nestjs\/common';  import * as statuses from '.\/statuses'; import {formatEventDate} from '..\/common\/utils'; import {BaseAction} from '.\/base.action'; import {Chat} from '..\/storage\/models\/chat'; import {Event} from '..\/storage\/models\/event'; import {IMessage} from '..\/message\/i-message';  @Injectable() export class EventRemoveAction extends BaseAction {     protected setEvent(): void {         this.event = this.appEmitter.EVENT_REMOVE;     }      protected async doAction(chat: Chat, message: IMessage): Promise&lt;IMessage&gt; {         const activeEvent: Event = await this.storageService.findChatActiveEvent(             chat,         );         await this.storageService.markChatEventsInactive(chat);          if (activeEvent) {             return message.setStatus(statuses.STATUS_SUCCESS).withData({                 date: formatEventDate(activeEvent.date),             });         } else {             return message.setStatus(statuses.STATUS_NO_EVENT);         }     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">EventInfoAction<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable, Logger} from '@nestjs\/common';  import * as statuses from '.\/statuses'; import {formatEventDate} from '..\/common\/utils'; import {ConfigService} from '..\/common\/config.service'; import {AppEmitter} from '..\/common\/event-bus.service'; import {TemplateService} from '..\/common\/template.service'; import {StorageService} from '..\/storage\/storage.service'; import {BaseAction} from '.\/base.action'; import {PlayerHelper} from '.\/player.helper'; import {Chat} from '..\/storage\/models\/chat'; import {Event} from '..\/storage\/models\/event'; import {Player} from '..\/storage\/models\/player'; import {IMessage} from '..\/message\/i-message';  @Injectable() export class EventInfoAction extends BaseAction {     private playerHelper: PlayerHelper;      constructor(         config: ConfigService,         appEmitter: AppEmitter,         logger: Logger,         templateService: TemplateService,         playerHelper: PlayerHelper,         storageService: StorageService,     ) {         super(config, appEmitter, logger, templateService, storageService);          this.playerHelper = playerHelper;     }      protected setEvent(): void {         this.event = this.appEmitter.EVENT_INFO;     }      protected async doAction(chat: Chat, message: IMessage): Promise&lt;IMessage&gt; {         const activeEvent: Event = await this.storageService.findChatActiveEvent(             chat,         );          if (!activeEvent) {             return message.setStatus(statuses.STATUS_NO_EVENT);         }          const players: Player[] = await this.storageService.getPlayers(             activeEvent,         );          return message.setStatus(statuses.STATUS_SUCCESS).withData({             date: formatEventDate(activeEvent.date),             ...(await this.playerHelper.getPlayersList(activeEvent)),         });     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">PlayerAddAction<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable, Logger} from '@nestjs\/common';  import * as statuses from '.\/statuses'; import {ConfigService} from '..\/common\/config.service'; import {AppEmitter} from '..\/common\/event-bus.service'; import {TemplateService} from '..\/common\/template.service'; import {StorageService} from '..\/storage\/storage.service'; import {BaseAction} from '.\/base.action'; import {PlayerHelper} from '.\/player.helper'; import {Chat} from '..\/storage\/models\/chat'; import {Event} from '..\/storage\/models\/event'; import {Player} from '..\/storage\/models\/player'; import {IMessage} from '..\/message\/i-message';  @Injectable() export class PlayerAddAction extends BaseAction {     private playerHelper: PlayerHelper;      constructor(         config: ConfigService,         appEmitter: AppEmitter,         logger: Logger,         templateService: TemplateService,         playerHelper: PlayerHelper,         storageService: StorageService,     ) {         super(config, appEmitter, logger, templateService, storageService);          this.playerHelper = playerHelper;     }      protected setEvent(): void {         this.event = this.appEmitter.PLAYER_ADD;     }      protected async doAction(chat: Chat, message: IMessage): Promise&lt;IMessage&gt; {         const activeEvent: Event = await this.storageService.findChatActiveEvent(             chat,         );          if (!activeEvent) {             return message.setStatus(statuses.STATUS_NO_EVENT);         }          const name: string = this.playerHelper.getPlayerName(message);         const existedPlayer: Player = await this.storageService.findPlayer(             activeEvent,             name,         );          if (existedPlayer) {             return message                 .setStatus(statuses.STATUS_ALREADY_ADDED)                 .withData({name});         }          const newPlayer: Player = await this.storageService.addPlayer(             activeEvent,             name,         );          return message.setStatus(statuses.STATUS_SUCCESS).withData({             name: newPlayer.name,             ...(await this.playerHelper.getPlayersList(activeEvent)),         });     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">PlayerRemoveAction<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable, Logger} from '@nestjs\/common';  import * as statuses from '.\/statuses'; import {ConfigService} from '..\/common\/config.service'; import {AppEmitter} from '..\/common\/event-bus.service'; import {TemplateService} from '..\/common\/template.service'; import {StorageService} from '..\/storage\/storage.service'; import {BaseAction} from '.\/base.action'; import {PlayerHelper} from '.\/player.helper'; import {Chat} from '..\/storage\/models\/chat'; import {Event} from '..\/storage\/models\/event'; import {Player} from '..\/storage\/models\/player'; import {IMessage} from '..\/message\/i-message';  @Injectable() export class PlayerRemoveAction extends BaseAction {     private playerHelper: PlayerHelper;      constructor(         config: ConfigService,         appEmitter: AppEmitter,         logger: Logger,         templateService: TemplateService,         playerHelper: PlayerHelper,         storageService: StorageService,     ) {         super(config, appEmitter, logger, templateService, storageService);          this.playerHelper = playerHelper;     }      protected setEvent(): void {         this.event = this.appEmitter.PLAYER_REMOVE;     }      protected async doAction(chat: Chat, message: IMessage): Promise&lt;IMessage&gt; {         const activeEvent: Event = await this.storageService.findChatActiveEvent(             chat,         );          if (!activeEvent) {             return message.setStatus(statuses.STATUS_NO_EVENT);         }          const name: string = this.playerHelper.getPlayerName(message);         const existedPlayer: Player = await this.storageService.findPlayer(             activeEvent,             name,         );          if (!existedPlayer) {             return message                 .setStatus(statuses.STATUS_NO_PLAYER)                 .withData({name});         }          await this.storageService.removePlayer(existedPlayer);          return message.setStatus(statuses.STATUS_SUCCESS).withData({             name,             ...(await this.playerHelper.getPlayersList(activeEvent)),         });     } }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u041d\u0430 \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u0441\u0442\u043e\u0438\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u041a\u043e\u043c\u0430\u043d\u0434\u044b <code>\/add<\/code> \u0438 <code>\/remove<\/code> \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\/\u0443\u0434\u0430\u043b\u044f\u044e\u0442 \u0438\u0433\u0440\u043e\u043a\u0430 \u0447\u044c\u0435 \u0438\u043c\u044f \u0438\u0434\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b. \u0415\u0441\u043b\u0438 \u0438\u043c\u044f \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043e, \u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f\/\u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0433\u0440\u043e\u043a \u0432\u044b\u0437\u0432\u0430\u0432\u0448\u0438\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u0443.<\/li>\n<li>\u0412 \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u044b <code>\/add<\/code>, <code>\/remove<\/code> \u0438 <code>\/info<\/code> \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0434\u043b\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430\u044f \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 (1) \u0438 (2) \u0431\u044b\u043b\u0430 \u0432\u044b\u043d\u0435\u0441\u0435\u043d\u0430 \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">PlayerHelper<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import {Injectable} from '@nestjs\/common'; import {StorageService} from '..\/storage\/storage.service'; import {Event} from '..\/storage\/models\/event'; import {Player} from '..\/storage\/models\/player'; import {IMessage} from '..\/message\/i-message';  @Injectable() export class PlayerHelper {     protected storageService: StorageService;      constructor(storageService: StorageService) {         this.storageService = storageService;     }      public getPlayerName(message: IMessage) {         const name = message.text.trim();         return name.length &gt; 0 ? name : message.name;     }      public async getPlayersList(event: Event) {         const players: Player[] = await this.storageService.getPlayers(event);          return {             total: players.length,             players: players.map((player, index) =&gt; ({                 index: index + 1,                 name: player.name,             })),         };     } } <\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<h3 id=\"sobiraem-vse-vmeste\">\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0441\u0435 \u0432\u043c\u0435\u0441\u0442\u0435<\/h3>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0431\u044b\u043b\u043e \u0443\u043f\u043e\u043c\u044f\u043d\u0443\u0442\u043e \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u0430\u0440\u043a\u0430\u0441\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a <a href=\"https:\/\/nestjs.com\">NestJS<\/a>. \u041c\u043d\u0435 \u0432 \u0441\u0432\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0432\u0435\u0437\u043b\u043e \u043b\u0438\u0447\u043d\u043e \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u043d\u0430 <a href=\"https:\/\/www.youtube.com\/watch?v=jo-1EUxMmxc\">\u0434\u043e\u043a\u043b\u0430\u0434\u0435<\/a> \u043e\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u0435\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0439 \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438. <\/p>\n<p>  <\/p>\n<p>\u041c\u043d\u043e\u0433\u0438\u0435 \u0431\u043e\u0439\u043b\u0435\u0440\u043f\u043b\u0435\u0439\u0442\u044b, \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0430 \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 NodeJS \u0447\u0430\u0441\u0442\u043e \u0433\u0440\u0435\u0448\u0430\u0442 \u0442\u0435\u043c, \u0447\u0442\u043e \u043d\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u0432\u043c\u0435\u043d\u044f\u0435\u043c\u043e\u0439 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0441\u0432\u044f\u0437\u0435\u0439 \u043c\u0435\u0436\u0434\u0443 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438. \u0421 \u0440\u043e\u0441\u0442\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u043e\u043b\u0436\u043d\u043e\u0433\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044f, \u043a\u043e\u0434\u043e\u0432\u0430\u044f \u0431\u0430\u0437\u0430 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u0435\u0448\u0430\u043d\u0438\u043d\u0443 \u0438\u0437 require-\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438, \u0437\u0430\u0447\u0430\u0441\u0442\u0443\u044e \u043f\u0440\u0438\u0432\u043e\u0434\u044f \u0434\u0430\u0436\u0435 \u043a \u0446\u0438\u043a\u043b\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c \u0438\u043b\u0438 \u0441\u0432\u044f\u0437\u044f\u043c \u0442\u0430\u043c, \u0433\u0434\u0435 \u0438\u0445 \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c. \u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0448\u0438\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0435 Dependency Injection \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u044b \u0432\u0440\u043e\u0434\u0435 <a href=\"https:\/\/www.npmjs.com\/package\/awilix\">awilix<\/a>, \u043d\u043e <a href=\"https:\/\/nestjs.com\">NestJS<\/a> \u0438\u0434\u0435\u0442 \u0434\u0430\u043b\u044c\u0448\u0435.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e DI \u0438 \u0441\u0440\u0435\u0434\u0441\u0442\u0432 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0445 \u0437\u0430\u0434\u0430\u0447, \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0449\u0438\u0445 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 NodeJS \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u044d\u0442\u043e\u0442 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u043f\u043e \u043c\u043e\u0435\u043c\u0443 \u0441\u043a\u0440\u043e\u043c\u043d\u043e\u043c\u0443 \u043c\u043d\u0435\u043d\u0438\u044e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0443\u044e \u043f\u0430\u0440\u0430\u0434\u0438\u0433\u043c\u0443 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043c\u043e\u0434\u0443\u043b\u0435\u0439, \u0441\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0432\u043e\u043a\u0440\u0443\u0433 \u0442\u0435\u0445 \u0438\u043b\u0438 \u0438\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/l2\/gq\/lc\/l2gqlc0vig_zsywifjjopmt_4wc.jpeg\" alt=\"\u043e\u0431\u0449\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043c\u043e\u0434\u0443\u043b\u0435\u0439\"><\/p>\n<p>  <\/p>\n<h2 id=\"konfiguraciya\">\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f<\/h2>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u0431\u043e\u0442 \u0438\u043c\u0435\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u0434\u0430\u044e\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<ol>\n<li><code>TELEGRAM_BOT_TOKEN<\/code> \u2014 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043b\u044f Telegram \u0431\u043e\u0442\u0430.<\/li>\n<li><code>TELEGRAM_USE_PROXY<\/code> \u2014 \u0444\u043b\u0430\u0433 \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u043a\u0441\u0438 \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a TelegramAPI.<\/li>\n<li><code>TELEGRAM_PROXY_HOST<\/code> \u2014 \u0445\u043e\u0441\u0442 \u043f\u0440\u043e\u043a\u0441\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a TelegramAPI.<\/li>\n<li><code>TELEGRAM_PROXY_PORT<\/code> \u2014 \u043f\u043e\u0440\u0442 \u043f\u0440\u043e\u043a\u0441\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a TelegramAPI.<\/li>\n<li><code>TELEGRAM_PROXY_LOGIN<\/code> \u2014 \u043b\u043e\u0433\u0438\u043d \u0434\u043b\u044f \u043f\u0440\u043e\u043a\u0441\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a TelegramAPI.<\/li>\n<li><code>TELEGRAM_PROXY_PASSWORD<\/code> \u2014 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u043f\u0440\u043e\u043a\u0441\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a TelegramAPI.<\/li>\n<li><code>VK_TOKEN<\/code> \u2014 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043b\u044f VK \u0431\u043e\u0442\u0430.<\/li>\n<li><code>DATABASE_URL<\/code> \u2014 \u0441\u0442\u0440\u043e\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f production \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u041d\u0430 \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u0441\u0442\u043e\u0438\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435:<\/p>\n<p>  <\/p>\n<ol>\n<li><code>TELEGRAM_BOT_TOKEN<\/code> \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f <a href=\"https:\/\/tlgrm.ru\/docs\/bots\">\u043e\u0431\u0449\u0435\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439<\/a> \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0431\u043e\u0442\u0430 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 Telegram.<\/li>\n<li><code>VK_TOKEN<\/code> \u2014 \u043a\u0430\u043a \u0431\u044b\u043b\u043e \u0443\u043f\u043e\u043c\u044f\u043d\u0443\u0442\u043e \u0432\u044b\u0448\u0435, \u0431\u043e\u0442 VK \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 \u043e\u0442 \u0438\u043c\u0435\u043d\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430. \u0422.\u0435. \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043b\u0438 \u0432\u0437\u044f\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e \u0438 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043d\u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0431\u043e\u0442\u0430. \u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u0431 \u044d\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/vk.com\/dev\/bots_docs\">\u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0435<\/a> \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438.<\/li>\n<li><code>DATABASE_URL<\/code> \u2014 \u0432 production \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0438 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f PostgreSQL. \u0412 \u043c\u043e\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043b\u0441\u044f \u043a \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u0435\u043c\u0443 DBaaS \u043f\u043e\u0434 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c <a href=\"https:\/\/www.elephantsql.com\">elephantsql<\/a> \u0438\u043c\u0435\u044e\u0449\u0435\u043c\u0443 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u043f\u043b\u0430\u043d.<\/li>\n<\/ol>\n<p>  <\/p>\n<h2 id=\"ci\">CI<\/h2>\n<p>  <\/p>\n<p>&#171;\u0415\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u2014 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c&#187;. \u042f \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u044e \u044d\u0442\u043e\u0442 \u0434\u0435\u0432\u0438\u0437, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0441 \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c <a href=\"https:\/\/travis-ci.org\">TravisCI<\/a> \u0434\u043b\u044f \u043f\u0440\u043e\u0433\u043e\u043d\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0442\u0438\u043f\u0438\u0447\u043d\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">language: node_js node_js:   - '8'   - '10'   - '12' script:   - npm run lint   - npm run build   - npm run test:cov after_script:   - npm install -g codecov   - codecov<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0437\u0434\u0435\u0441\u044c \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c \u043f\u043e\u043c\u0438\u043c\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u0440\u043e\u0433\u043e\u043d\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u0431\u043e\u0440 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u044f \u043a\u043e\u0434\u0430 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 <a href=\"https:\/\/codecov.io\">codecov<\/a>.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0431\u043e\u0440\u0430 <a href=\"https:\/\/www.docker.com\">Docker<\/a> \u043e\u0431\u0440\u0430\u0437\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0435\u0433\u043e \u0432 <a href=\"https:\/\/hub.docker.com\">Docker Hub<\/a> \u043c\u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043c\u043e\u0433\u043b\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f <a href=\"https:\/\/habr.com\/ru\/users\/Gonf\/\">Gonf<\/a> \u0437\u0434\u0435\u0441\u044c \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 <a href=\"https:\/\/habr.com\/ru\/post\/476368\/\">\u0421\u0442\u0440\u043e\u0438\u043c \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u0439 CI\/CD \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 GitHub Actions \u0438 Python<\/a>. \u041f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0441 \u0435\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u0441\u0432\u043e\u0439 Continuous Deployment, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0440\u0435\u043b\u0438\u0437\u0430 \u0432 Github:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">name: Release  on:   release:     types: [published]  jobs:    build:      runs-on: ubuntu-latest      env:       LOGIN: ${{ secrets.DOCKER_LOGIN }}       NAME: ${{ secrets.DOCKER_NAME }}      steps:     - uses: actions\/checkout@v1     - name: Install Node.js       uses: actions\/setup-node@v1     - name: Login to docker.io       run:  echo ${{ secrets.DOCKER_PWD }} | docker login -u ${{ secrets.DOCKER_LOGIN }} --password-stdin     - name: npm install and build       run: npm ci &amp;&amp; npm run build     - name: Build the Docker image       run: docker build -t $LOGIN\/$NAME:${GITHUB_REF:10} -f Dockerfile .     - name: Push image to docker.io       run: docker push $LOGIN\/$NAME:${GITHUB_REF:10}<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"rezultat-i-vyvody\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438 \u0432\u044b\u0432\u043e\u0434\u044b<\/h2>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u0436\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0432 \u0438\u0442\u043e\u0433\u0435. \u0414\u043b\u044f Telegram \u0440\u0430\u0431\u043e\u0442\u0430 \u0431\u043e\u0442\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<br \/>  \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u0434\u0430\u0442\u0443 \u0438 \u0432\u0440\u0435\u043c\u044f.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/mi\/tr\/nb\/mitrnbq3hcwu33lizpvmbxuirl0.png\" alt=\"demo1\"><\/p>\n<p>  <\/p>\n<p>\u0418\u0433\u0440\u043e\u043a \u0440\u0435\u0448\u0438\u043b \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u0438\u0435.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/4v\/y-\/lk\/4vy-lkg-dungqdpmbou0gop2w4e.png\" alt=\"demo2\"><\/p>\n<p>  <\/p>\n<p>\u0418\u0433\u0440\u043e\u043a \u043e\u0442\u043c\u0435\u043d\u0438\u043b \u0441\u0432\u043e\u0435 \u0443\u0447\u0430\u0441\u0442\u0438\u0435 \u0432 \u0438\u0433\u0440\u0435.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/w8\/i2\/oo\/w8i2oog_swdu2sjvqsqihwu8ww4.png\" alt=\"demo3\"><\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0445\u043e\u0436\u0430\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u0430 \u0438 \u0434\u043b\u044f VK \u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 \u0442\u0435\u0433\u0430\u043c\u0438 <code>&lt;b&gt;<\/code> \u0438 <code>&lt;i&gt;<\/code>.<br \/>  \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u0434\u0430\u0442\u0443 \u0438 \u0432\u0440\u0435\u043c\u044f.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/ak\/cu\/fo\/akcufo-5cwygqzcdfysm-uytzdi.png\" alt=\"demo4\"><\/p>\n<p>  <\/p>\n<p>\u0418\u0433\u0440\u043e\u043a \u043e\u0442\u043c\u0435\u043d\u0438\u043b \u0441\u0432\u043e\u0435 \u0443\u0447\u0430\u0441\u0442\u0438\u0435 \u0432 \u0438\u0433\u0440\u0435.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/pg\/ye\/c7\/pgyec7wg7kgltgjk7ton5wc7w64.png\" alt=\"demo6\"><\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f <a href=\"https:\/\/www.viber.com\">Viber<\/a>, \u0442\u043e \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u043c\u0435\u0441\u0435\u043d\u0436\u0435\u0440 \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0432 \u0441\u0438\u043b\u0443 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439 \u043d\u0435\u043f\u0440\u0438\u0433\u043e\u0434\u043d\u044b\u043c \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0431\u043e\u0442\u0430. \u0421 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u043d\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u043e \u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 <a href=\"https:\/\/www.viber.com\">Viber<\/a> \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f webhooks \u0432\u043c\u0435\u0441\u0442\u043e long-polling. \u041d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0442\u043e, \u0447\u0442\u043e \u0431\u043e\u0442 \u0432 Viber \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u043a\u0430\u043a \u043f\u043e\u043b\u043d\u043e\u043f\u0440\u0430\u0432\u043d\u044b\u0439 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u043e\u0439 \u0431\u0435\u0441\u0435\u0434\u044b. \u041f\u0440\u043e \u044d\u0442\u043e \u0432 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/github.com\/Viber\/sample-bot-isitup\/issues\/2\">\u0437\u0434\u0435\u0441\u044c<\/a>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0438\u0442\u043e\u0433\u0435 \u043e\u0442 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 \u044d\u0442\u0438\u043c \u043c\u0435\u0441\u0441\u0435\u043d\u0436\u0435\u0440\u043e\u043c \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c (\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e?) \u043e\u0442\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f.<\/p>\n<p>  <\/p>\n<p>\u0412 Telegram \u0433\u043e\u0442\u043e\u0432\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a \u0441\u0435\u0431\u0435 \u0432 \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u043e\u0439 \u0447\u0430\u0442 \u043f\u043e\u0434 \u0438\u043c\u0435\u043d\u0435\u043c <code>@Tormozz48bot<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u043c\u043e\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043b\u0435\u0436\u0430\u0442 <a href=\"https:\/\/github.com\/tormozz48\/football-chat-bot-2\">\u043d\u0430 Github<\/a>. \u041f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442\u0441\u044f \u043b\u044e\u0431\u044b\u0435 \u043f\u0440\u0430\u0432\u043a\u0438, \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u044f \u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>P.S. \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0432\u0441\u0435\u043c \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435.<\/p>\n<\/div>\n<p>               <script class=\"js-mediator-script\">!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement(\"script\"),r.type=\"text\/javascript\",r.async=!0,r.defer=!0,r.src=t,r.charset=\"UTF-8\";var d=function(){var e=a.getElementsByTagName(\"script\")[0];e.parentNode.insertBefore(r,e)};\"[object Opera]\"==e.opera?a.addEventListener?a.addEventListener(\"DOMContentLoaded\",d,!1):e.attachEvent(\"onload\",d):d() } } }t(\"\/\/mediator.mail.ru\/script\/2820404\/\",\"_mediator\")}(window);<\/script>      <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\/post\/483194\/\"> https:\/\/habr.com\/ru\/post\/483194\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/483194\/\">\n<h2 id=\"vvedenie\">\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<p>  <\/p>\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043e\u043f\u0438\u0448\u0443 \u0441\u0432\u043e\u0435\u0433\u043e \u0447\u0430\u0442-\u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 telegram \u0438 \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 VK \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c NodeJS.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u0443 \u043c\u043d\u043e\u0433\u0438\u0445 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0434\u043e\u043b\u0436\u043d\u043e \u0432\u044b\u0440\u0432\u0430\u0442\u044c\u0441\u044f \u0447\u0442\u043e-\u0442\u043e \u0432\u0440\u043e\u0434\u0435: &#171;\u0414\u043e\u043a\u043e\u043b\u0435!&#187; \u0438\u043b\u0438 &#171;\u0427\u0442\u043e, \u043e\u043f\u044f\u0442\u044c ?!&#187;.<br \/>  \u0414\u0430, \u043f\u043e\u0445\u043e\u0436\u0438\u0435 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0443\u0436\u0435 \u0431\u044b\u043b\u0438 \u0438 \u043d\u0430 \u0445\u0430\u0431\u0440\u0435 \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435. \u041d\u043e, \u0442\u0435\u043c \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u044f \u0441\u0447\u0438\u0442\u0430\u044e, \u0447\u0442\u043e \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u0430. \u041a\u0440\u0430\u0442\u043a\u043e \u043e \u0442\u043e\u043c \u0447\u0442\u043e \u0441 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0431\u043e\u0442\u0430:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u0430\u0440\u043a\u0430\u0441\u0430 \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043d\u0430\u0431\u0438\u0440\u0430\u044e\u0449\u0438\u0439 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u043e\u0441\u0442\u044c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a <a href=\"https:\/\/nestjs.com\">NestJS<\/a>.<\/li>\n<li>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/www.npmjs.com\/package\/telegraf\">telegraf<\/a> \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API Telegram.<\/li>\n<li>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/www.npmjs.com\/package\/node-vk-bot-api\">node-vk-bot-api<\/a> \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 API VK.<\/li>\n<li>\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/www.npmjs.com\/package\/typeorm\">typeorm<\/a> \u0434\u043b\u044f \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043b\u043e\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445.<\/li>\n<li>\u0422\u0435\u0441\u0442\u044b \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c <a href=\"https:\/\/www.npmjs.com\/package\/mocha\">mocha<\/a> \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0430\u0441\u0441\u0435\u0440\u0442\u043e\u0432 <a href=\"https:\/\/www.npmjs.com\/package\/chai\">chai<\/a> .<\/li>\n<li>CI \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Travis CI \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 GitHub Actions \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f \u0434\u043e\u043a\u0435\u0440-\u043e\u0431\u0440\u0430\u0437\u043e\u0432.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u0431\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u043e\u0434\u0440\u0443\u0436\u0438\u0442\u044c \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u043e\u0442\u0430 \u0441 Viber \u0434\u0435\u043b\u0430\u044f \u0435\u0433\u043e \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u043c \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445 \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438. <\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043c \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442 \u0443\u0437\u043d\u0430\u0442\u044c \u0447\u0442\u043e \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\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-296874","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/296874","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=296874"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/296874\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=296874"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=296874"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=296874"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}