{"id":369287,"date":"2024-05-21T04:00:06","date_gmt":"2024-05-21T04:00:06","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=369287"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-21T04:00:06","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=369287","title":{"rendered":"<span>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 Chat-Ops \u0431\u043e\u0442\u0430 \u0432 Mattermost \u043d\u0430 python<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440!<\/p>\n<p>\u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0410\u043b\u044c\u0444\u0430\u0421\u0442\u0440\u0430\u0445\u043e\u0432\u0430\u043d\u0438\u0435, \u043a\u0430\u043a \u0438 \u043c\u043d\u043e\u0433\u0438\u0435 \u0434\u0440\u0443\u0433\u0438\u0435, \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0430\u0441\u044c \u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c\u044e \u0437\u0430\u043c\u0435\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0432 \u0441\u0432\u044f\u0437\u0438 \u0441 \u0441\u0430\u043d\u043a\u0446\u0438\u044f\u043c\u0438. \u0417\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0433\u043e\u0434 \u043c\u044b \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438\u0441\u044c \u043e\u0442 Slack \u0432 \u043f\u043e\u043b\u044c\u0437\u0443 open-source \u0430\u043d\u0430\u043b\u043e\u0433\u0430 Mattermost, \u0430 Jira \u043d\u0435 \u0431\u0435\u0437 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0431\u044b\u043b\u0430 \u0437\u0430\u043c\u0435\u043d\u0435\u043d\u0430 \u043d\u0430 Kaiten.<\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u0435, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u043e\u0431\u043e\u0440\u043e\u0442\u0430 \u0432 \u0447\u0430\u0441\u0442\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432, \u0447\u0430\u0441\u0442\u043e \u043d\u0430 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u044b\u0445 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0445 \u0438 \u043f\u0440\u0438 \u043e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u0438 \u0432 \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0435 Mattermost \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u043b\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u043d\u0430\u043a\u0438\u0434\u0430\u0442\u044c \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a\u0438 \u0437\u0430\u0434\u0430\u0447 \u0432 \u0442\u0430\u0441\u043a-\u0442\u0440\u0435\u043a\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0442\u043e\u043c \u0438\u0445 \u0434\u043e\u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u044c. \u042d\u0442\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043b\u043e\u0436\u0438\u043b\u0430\u0441\u044c \u043d\u0430 \u043c\u043e\u0435 \u0436\u0435\u043b\u0430\u043d\u0438\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043d\u0430 python, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0435 \u0441 Chat-Ops.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u0440\u044f\u0434\u043e\u043c \u0441\u043b\u0430\u0431\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 \u0430\u0441\u043f\u0435\u043a\u0442\u043e\u0432, \u043e \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438 \u0445\u043e\u0442\u0435\u043b \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<h2>\u041e\u0431\u0449\u0435\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435<\/h2>\n<p>\u041a\u043e\u043b\u043b\u0435\u0433\u0438 \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u0430\u043b\u0438, \u0447\u0442\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Mattermost \u043c\u043e\u0436\u043d\u043e \u0447\u0435\u0440\u0435\u0437 websocket. \u042f\u0437\u044b\u043a python \u0431\u044b\u043b \u0432\u044b\u0431\u0440\u0430\u043d, \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u044f\u0445 \u043d\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0442\u0430\u043a\u0436\u0435 \u0441\u044b\u0433\u0440\u0430\u043b\u043e \u0440\u043e\u043b\u044c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f websocket-\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432.<\/p>\n<p>\u0411\u043e\u0442\u0430 \u0441\u0434\u0435\u043b\u0430\u043b \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u043c, \u0431\u0435\u0437 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043a\u043d\u043e\u043f\u043e\u0447\u0435\u043a, \u043a \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u0431\u044b Web API \u2013 \u043f\u043e \u0441\u0443\u0442\u0438, \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0433\u0438\u0431\u043a\u0438\u0439 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u044d\u0442\u043e \u0432\u0441\u0451 \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0431\u0435\u0437\u0431\u043e\u043b\u0435\u0437\u043d\u0435\u043d\u043d\u043e \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c. \u0421\u0445\u0435\u043c\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u0442\u0430\u043a\u0430\u044f:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/9fb\/751\/14d\/9fb75114d2cb65c1478cc90459804469.jpg\" width=\"1600\" height=\"854\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9fb\/751\/14d\/9fb75114d2cb65c1478cc90459804469.jpg\" data-blurred=\"true\"\/><\/figure>\n<h2>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 websocket &#8212; \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430, \u044f \u043d\u0430\u0443\u0447\u0438\u043b\u0441\u044f \u0447\u0438\u0442\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 websocket \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 websocket-client (<a href=\"https:\/\/pypi.org\/project\/websocket-client\/\">https:\/\/pypi.org\/project\/websocket-client\/<\/a>). <\/p>\n<p>\u0418\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0432\u044b\u0448\u0435, \u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 Mattermost \u043a\u0430\u0441\u0430\u0442\u0435\u043b\u044c\u043d\u043e websocket, \u044f \u0441\u043b\u0435\u043f\u0438\u043b \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a\u043e\u0435:<\/p>\n<pre><code class=\"python\">import logging import logging.config import websocket  class WebSocketMattermostApp:     \"\"\"     \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0441\u043b\u0443\u0448\u0430\u044e\u0449\u0435\u0435 Mattermost \u0447\u0435\u0440\u0435\u0437 WebSocket     \"\"\"      mm_ws_headers = dict()     \"\"\"     \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0434\u043b\u044f WebSocket-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a API Mattermost     \"\"\"      connection: websocket.WebSocketApp     \"\"\"     \u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 WebSocket     \"\"\"      def connect():         \"\"\"         \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a WebSocket         \"\"\"          # \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Mattermost \u0447\u0435\u0440\u0435\u0437 WebSocket         WebSocketMattermostApp.mm_ws_headers[\"Authorization\"] = \"Bearer ****************\"          WebSocketMattermostApp.connection = websocket.WebSocketApp(\"wss:\/\/*************\/api\/v4\/websocket\",                                     header=WebSocketMattermostApp.mm_ws_headers,                                     on_open=WebSocketMattermostApp.ws_on_open,                                     on_message=WebSocketMattermostApp.ws_on_message,                                     on_error=WebSocketMattermostApp.ws_on_error,                                     on_close=WebSocketMattermostApp.ws_on_close)         WebSocketMattermostApp.connection.run_forever(reconnect=5)      def disconnect():         \"\"\"         \u041e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 WebSocket         \"\"\"         WebSocketMattermostApp.connection.close()      def ws_on_message(ws, message):         \"\"\"         \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f         \"\"\"         print(message)     def ws_on_error(ws, error):         \"\"\"         \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435         \"\"\"         logging.error(f\"Error: {error}\")      def ws_on_close(ws, close_status_code, close_msg):         \"\"\"         \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f         \"\"\"         logging.info(             f\"Connection closed {close_status_code} | {close_msg}\")      def ws_on_open(ws):         \"\"\"         \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f         \"\"\"         logging.info(\"Connection opened\")  # \u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0444\u0430\u0439\u043b\u0430 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438  if __name__ == \"__main__\":     WebSocketMattermostApp.connect()<\/code><\/pre>\n<p>\u0412 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043a\u0435\u043d \u0431\u043e\u0442-\u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 Mattermost, \u0430 \u0432 URI \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a websocket &#8212; \u0445\u043e\u0441\u0442 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430 Mattermost.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u0430\u0439\u043b\u0430 \u0432\u044b\u0448\u0435, \u0435\u0441\u043b\u0438 \u0432\u0441\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f \u0438 \u043d\u0430\u0447\u043d\u0435\u0442 \u043f\u0438\u0441\u0430\u0442\u044c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0432\u0441\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438\u0437 \u0432\u0441\u0435\u0445 \u043a\u0430\u043d\u0430\u043b\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0448\u0430 \u0443\u0447\u0435\u0442\u043a\u0430 \u0431\u043e\u0442\u0430.<\/p>\n<p>\u0421\u0443\u0449\u043d\u043e\u0441\u0442\u0438, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 websocket, \u0431\u044b\u0432\u0430\u044e\u0442 \u0440\u0430\u0437\u043d\u044b\u0435. \u041d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0442\u0438\u043f posted (\u0442.\u0435. \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435), \u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043d\u0438\u0436\u0435:<\/p>\n<pre><code class=\"json\">{     \"event\": \"posted\",     \"data\": {         \"channel_display_name\": \"my_mm_channel\",         \"channel_name\": \"my_mm_channel\",         \"channel_type\": \"P\",         \"post\":\"{\\\"id\\\":\\\"1qaz2wsx3edc4rfv\\\",\\\"create_at\\\":123456789,\\\"update_at\\\":123456789,\\\"edit_at\\\":0,\\\"delete_at\\\":0,\\\"is_pinned\\\":false,\\\"user_id\\\":\\\"1qaz2wsx3edc\\\",\\\"channel_id\\\":\\\"1qaz2wsx3edc\\\",\\\"root_id\\\":\\\"\\\",\\\"original_id\\\":\\\"\\\",\\\"message\\\":\\\"Test message\\\",\\\"type\\\":\\\"slack_attachment\\\",\\\"props\\\":{\\\"attachments\\\":[{\\\"id\\\":0,\\\"fallback\\\":\\\"\\\",\\\"color\\\":\\\"#CF0A2C\\\",\\\"pretext\\\":\\\"\\\",\\\"author_name\\\":\\\"\\\",\\\"author_link\\\":\\\"\\\",\\\"author_icon\\\":\\\"\\\",\\\"title\\\":\\\"Message attachment title\\\",\\\"title_link\\\":\\\"\\\",\\\"text\\\":\\\"\\\",\\\"fields\\\":null,\\\"image_url\\\":\\\"\\\",\\\"thumb_url\\\":\\\"\\\",\\\"footer\\\":\\\"message footer\\\",\\\"footer_icon\\\":\\\"\\\",\\\"ts\\\":null}],\\\"from_bot\\\":\\\"true\\\",\\\"from_webhook\\\":\\\"true\\\",\\\"override_icon_emoji\\\":\\\":love_letter:\\\",\\\"override_icon_url\\\":\\\"\/static\/emoji\/1f48c.png\\\",\\\"override_username\\\":\\\"my_mm_bot\\\",\\\"webhook_display_name\\\":\\\"my_mm_bot\\\"},\\\"hashtags\\\":\\\"\\\",\\\"pending_post_id\\\":\\\"\\\",\\\"reply_count\\\":0,\\\"last_reply_at\\\":0,\\\"participants\\\":null,\\\"metadata\\\":{\\\"embeds\\\":[{\\\"type\\\":\\\"message_attachment\\\"}]}}\",         \"sender_name\": \"my_mm_bot\",         \"set_online\": false,         \"team_id\": \"1qaz2wsx3edc4rfv\"     },     \"broadcast\": {         \"omit_users\": null,         \"user_id\": \"\",         \"channel_id\": \"1qaz2wsx3edc4rfv\",         \"team_id\": \"\",         \"connection_id\": \"\",         \"omit_connection_id\": \"\"     },     \"seq\": 6 }<\/code><\/pre>\n<p>\u041a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c, \u0432 \u043f\u043e\u043b\u0435 data.post \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0435\u0433\u043e JSON \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u0442\u043e\u0440\u043e\u0439, \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0439 JSON; \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u043d\u0451\u043c \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0449\u0430\u044f \u043d\u0430\u0441 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f: \u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a \u043d\u0435\u043c\u0443, \u0418\u0414 \u0430\u0432\u0442\u043e\u0440\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u0442.\u0434.:<\/p>\n<pre><code class=\"json\">{     \"id\": \"1qaz2wsx3edc4rfv\",     \"create_at\": 123456789,     \"update_at\": 123456789,     \"edit_at\": 0,     \"delete_at\": 0,     \"is_pinned\": false,     \"user_id\": \"1qaz2wsx3edc\",     \"channel_id\": \"1qaz2wsx3edc\",     \"root_id\": \"\",     \"original_id\": \"\",     \"message\": \"Test message\",     \"type\": \"slack_attachment\",     \"props\": {         \"attachments\": [             {                 \"id\": 0,                 \"fallback\": \"\",                 \"color\": \"#CF0A2C\",                 \"pretext\": \"\",                 \"author_name\": \"\",                 \"author_link\": \"\",                 \"author_icon\": \"\",                 \"title\": \"Message attachment title\",                 \"title_link\": \"\",                 \"text\": \"\",                 \"fields\": null,                 \"image_url\": \"\",                 \"thumb_url\": \"\",                 \"footer\": \"message footer\",                 \"footer_icon\": \"\",                 \"ts\": null             }         ],         \"from_bot\": \"true\",         \"from_webhook\": \"true\",         \"override_icon_emoji\": \":love_letter:\",         \"override_icon_url\": \"\/static\/emoji\/1f48c.png\",         \"override_username\": \"my_mm_bot\",         \"webhook_display_name\": \"my_mm_bot\"     },     \"hashtags\": \"\",     \"pending_post_id\": \"\",     \"reply_count\": 0,     \"last_reply_at\": 0,     \"participants\": null,     \"metadata\": {         \"embeds\": [             {                 \"type\": \"message_attachment\"             }         ]     } }<\/code><\/pre>\n<h3>\u0420\u0430\u0437\u043d\u0435\u0441\u0435\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438<\/h3>\n<p>\u0420\u0430\u0437\u043d\u0435\u0441\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u041a\u0430\u0439\u0442\u0435\u043d, Mattermost (\u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430) \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u0440\u0430\u0437\u043d\u044b\u043c \u0444\u0430\u0439\u043b\u0430\u043c.<\/p>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u041a\u0430\u0439\u0442\u0435\u043d \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430 \u2013 \u043f\u0430\u0440\u0441\u0438\u043c \u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u043c \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0442\u0435\u043a\u0441\u0442, \u0432\u0437\u044f\u0442\u044b\u0439 \u0432 \u043a\u0430\u0432\u044b\u0447\u043a\u0438 \u2013 \u044d\u0442\u043e \u0438 \u0431\u0443\u0434\u0435\u0442 \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0430 \u0434\u043b\u044f \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0439 \u0432 \u041a\u0430\u0439\u0442\u0435\u043d \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0434\u043e\u0441\u043a\u0443, \u0434\u043e\u0440\u043e\u0436\u043a\u0443 \u0438 \u0442\u0438\u043f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438. \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0439 \u0418\u0414 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0432 \u041a\u0430\u0439\u0442\u0435\u043d. \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u0430 headers \u0438 client \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u0430. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">import http.client import json import re  class KaitenHelper:      \"\"\"     \u041f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a API Kaiten     \"\"\"      headers = dict()     \"\"\"     \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a API Kaiten     \"\"\"      client: http.client.HTTPSConnection = None     \"\"\"     \u041a\u043b\u0438\u0435\u043d\u0442 \u043a API Kaiten     \"\"\"      def create_kaiten_card(message, creator_id):         \"\"\"         \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0443 \u0432 Kaiten         \"\"\"          # \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e         board_id = 1  # \u0418\u0414 \u0414\u043e\u0441\u043a\u0438         lane_id = 2  # \u0418\u0414 \u0414\u043e\u0440\u043e\u0436\u043a\u0438 \u043d\u0430 \u0414\u043e\u0441\u043a\u0435         type_id = 26  # \u0418\u0414 \u0442\u0438\u043f\u0430 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438         properties = {}          # \u0422\u0435\u043a\u0441\u0442 \u0437\u0430\u0434\u0430\u0447\u0438         double_quotes_title_regex = re.compile(             r'\"(.+)\"',             flags=re.I | re.M)         single_quotes_title_regex = re.compile(             r'\\'(.+)\\'',             flags=re.I | re.M)         task_title_regex = re.compile(             r'(\u0437\u0430\u0434\u0430\u0447|\u0442\u0430\u0441\u043a|kaiten|\u043a\u0430\u0439\u0442\u0435\u043d).*? (.+)',             flags=re.I | re.M)         title_search = double_quotes_title_regex.search(message)         if title_search == None:             title_search = single_quotes_title_regex.search(message)         if title_search == None:             title_search = task_title_regex.search(message)         if title_search == None or len(title_search.groups()) == 0:             return None         title = title_search.groups()[-1]          # \u041c\u0435\u043d\u044f\u0435\u043c \u0447\u0430\u0441\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0442\u0435\u0445\u0434\u043e\u043b\u0433\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447         tech_debt_regex = re.compile(             r'(\u0432 \u0442\u0435\u0445.*\u0434\u043e\u043b\u0433|\u0442\u0435\u0445.*\u0434\u043e\u043b\u0433.+(\u0437\u0430\u0434\u0430\u0447|\u0442\u0430\u0441\u043a))',             flags=re.I | re.M)         if tech_debt_regex.search(message):             # \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u0447 \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u043e\u043b\u0433\u0430             board_id = 4  # \u0414\u043e\u0441\u043a\u0430: \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u043e\u043b\u0433             lane_id = 2  # \u0414\u043e\u0440\u043e\u0436\u043a\u0430: \u0412\u0430\u0436\u043d\u044b\u0435             type_id = 7  # \u0422\u0438\u043f \u0434\u043b\u044f \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u043e\u043b\u0433\u0430          body = json.dumps({             \"title\": title,             \"board_id\": board_id,             \"lane_id\": lane_id,             \"owner_id\": creator_id,             \"type_id\": type_id,             \"properties\": {}         })          KaitenHelper.client.request(\"POST\", \"\/api\/latest\/cards\",                               body, KaitenHelper.headers)         response = KaitenHelper.client.getresponse()         response_obj = json.loads(response.read().decode())         KaitenHelper.client.close()         return response_obj[\"id\"]<\/code><\/pre>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0442\u043e\u0440\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0432\u0435\u0442\u0430 \u043d\u0430\u0448\u0435\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0447\u0435\u0440\u0435\u0437 API Mattermost:<\/p>\n<pre><code class=\"python\">import http.client import json  class MmHelper:     \"\"\"     \u041f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a API Mattermost     \"\"\"      headers = dict()     \"\"\"     \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a API Mattermost     \"\"\"      client: http.client.HTTPSConnection = None     \"\"\"     \u041a\u043b\u0438\u0435\u043d\u0442 \u043a API Mattermost     \"\"\"      def post_to_mm(message, channel_id, root_id=\"\", props={}):         \"\"\"         \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0432 Mattermost \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435         \"\"\"          body = json.dumps({             \"channel_id\": channel_id,             \"root_id\": root_id,             \"message\": message,             \"props\": props         })         MmHelper.client.request(\"POST\", \"\/api\/v4\/posts\", body, MmHelper.headers)         MmHelper.client.close() <\/code><\/pre>\n<p>\u0421\u0432\u044f\u0436\u0435\u0442 \u044d\u0442\u0438 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0432\u043e\u0435\u0434\u0438\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"python\">import re  from utils.kaiten_helper import KaitenHelper from utils.mm_helper import MmHelper  bot_mm_tag = \"@bot\" \"\"\" \u0422\u044d\u0433 \u0431\u043e\u0442\u0430 \u0432 Mattermost \"\"\"  create_kaiten_card_regex = re.compile(     r'(\u0441\u043e\u0437\u0434\u0430|\u0437\u0430\u0432\u0435\u0434|\u043d\u043e\u0432[\u0430\u0443]|\u043f\u043e\u043b\u043e\u0436).+(\u0437\u0430\u0434\u0430\u0447|\u0442\u0430\u0441\u043a|kaiten|\u043a\u0430\u0439\u0442\u0435\u043d)', flags=re.I | re.M) \"\"\" \u0420\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0432 Kaiten \"\"\"  class IncomingPostHandler:     \"\"\"     \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439     \"\"\"      users_of_bot = {}     \"\"\"     \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0431\u043e\u0442\u0430 Mattermost     \"\"\"      def handle(post_obj):         \"\"\"         \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435         \"\"\"         reply = \"\"         if create_kaiten_card_regex.search(post_obj['message']):             reply = IncomingPostHandler.handle_create_kaiten_card(post_obj)         else:             reply = IncomingPostHandler.handle_help()          mm_root_id = post_obj[\"id\"] if post_obj[\"root_id\"] == \"\" else post_obj[\"root_id\"]         MmHelper.post_to_mm(reply, post_obj[\"channel_id\"], mm_root_id)      def handle_create_kaiten_card(post_obj):         \"\"\"         \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0432 Kaiten         \"\"\"         kaiten_creator_id: int = next(             (user[\"kaiten_id\"]              for user in IncomingPostHandler.users_of_bot if user[\"id\"] == post_obj[\"user_id\"]),             None)         created_id = KaitenHelper.create_kaiten_card(             post_obj['message'], kaiten_creator_id)         if created_id == None:             return \":( \u041d\u0435 \u0441\u043c\u043e\u0433 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u0437\u0430\u0434\u0430\u0447\u0438\"         else:             return f\":white_check_mark: \u0421\u043e\u0437\u0434\u0430\u043b \u0437\u0430\u0434\u0430\u0447\u0443 {created_id}\\n:earth_africa: [\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0432 Kaiten](https:\/\/kaiten.mycompany.com\/space\/36\/card\/{created_id})\"      def handle_help():         \"\"\"         \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f \/ \u043f\u0440\u043e\u0441\u044c\u0431\u044b \u043e \u043f\u043e\u043c\u043e\u0449\u0438         \"\"\"         return \"\u041f\u0440\u0438\u0432\u0435\u0442! \u041d\u0435 \u0441\u043c\u043e\u0433 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0442\u044c \u0432\u0430\u0448\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u0443\"<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a (\u043e\u043f\u044f\u0442\u044c \u0436\u0435, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0445 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439) \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0445\u043e\u0447\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u041a\u0430\u0439\u0442\u0435\u043d:<\/p>\n<pre><code class=\"python\">create_kaiten_card_regex = re.compile(     r'(\u0441\u043e\u0437\u0434\u0430|\u0437\u0430\u0432\u0435\u0434|\u043d\u043e\u0432[\u0430\u0443]|\u043f\u043e\u043b\u043e\u0436).+(\u0437\u0430\u0434\u0430\u0447|\u0442\u0430\u0441\u043a|kaiten|\u043a\u0430\u0439\u0442\u0435\u043d)', flags=re.I | re.M)<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0430, \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 mm_root_id \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u043e\u0441\u0442, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0442\u0440\u0435\u0434\u0435. \u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0443\u0436\u0435 \u043f\u0440\u0438\u0448\u0435\u043b \u0438\u0437 \u0442\u0440\u0435\u0434\u0430, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0433\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u043e\u0441\u0442 \u044d\u0442\u043e\u0433\u043e \u0442\u0440\u0435\u0434\u0430:<\/p>\n<pre><code class=\"python\">        mm_root_id = post_obj[\"id\"] if post_obj[\"root_id\"] == \"\" else post_obj[\"root_id\"]         MmHelper.post_to_mm(reply, post_obj[\"channel_id\"], mm_root_id)<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0438 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043a\u043b\u0430\u0441\u0441 WebSocketMattermostApp, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 \u043d\u0435\u0433\u043e \u043c\u0435\u0442\u043e\u0434 configure():<\/p>\n<pre><code class=\"python\">    def configure():         \"\"\"         \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 config.json         \"\"\"          # \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u043a \u043a\u043e\u043d\u0444\u0438\u0433\u0430\u043c \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u0444\u0430\u0439\u043b\u0443         os.chdir(os.path.dirname(os.path.abspath(__file__)))          # \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0444\u0430\u0439\u043b\u0430         with open('logging_config.json') as file:             logging.config.dictConfig(json.load(file))          with open('config.json') as file:             global config             config = json.load(file)         logging.info(\"Configuration files loaded\")          # \u041a\u043b\u0438\u0435\u043d\u0442 \u043a API Mattermost         MmHelper.client = http.client.HTTPSConnection(config[\"mattermost\"][\"host\"])         mm_auth = f\"Bearer {config['mattermost']['token']}\"         MmHelper.headers[\"Authorization\"] = mm_auth         MmHelper.headers[\"Content-Type\"] = \"application\/json\"          # \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Mattermost \u0447\u0435\u0440\u0435\u0437 WebSocket         WebSocketMattermostApp.mm_ws_headers[\"Authorization\"] = mm_auth          # \u0410\u0434\u0440\u0435\u0441 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Mattermost \u0447\u0435\u0440\u0435\u0437 WebSocket         global mm_ws_url         mm_ws_url = f\"wss:\/\/{config['mattermost']['host']}\/api\/v4\/websocket\"          # \u041a\u043b\u0438\u0435\u043d\u0442 \u043a API Kaiten         KaitenHelper.client = http.client.HTTPSConnection(config[\"kaiten\"][\"host\"])          KaitenHelper.headers[\"Authorization\"] = f\"Bearer {config['kaiten']['token']}\"         KaitenHelper.headers[\"Content-Type\"] = \"application\/json\"          IncomingPostHandler.users_of_bot = config['mattermost_allowed_users']          logging.info(\"Configuration completed\")<\/code><\/pre>\n<p>\u0424\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">{     \"mattermost_allowed_users\": [         {             \"id\": \"1f84d516a1494c1b9057f89fb2eab2d0\",             \"name\": \"ivanovii\",             \"kaiten_id\": 123         },         {             \"id\": \"287422ffa5984bfd8fc720c0e1960fef\",             \"name\": \"petrovapp\",             \"kaiten_id\": 456         },     ],     \"mattermost\": {         \"host\": \"mattermost.mycompany.com\",         \"token\": \"4256f55502e64dd59471e424653b1d4d\"     },     \"kaiten\": {         \"host\": \"kaiten.mycompany.com\",         \"token\": \"e90c03b3-cddb-4053-a7ba-201b0e581e08\"     } }<\/code><\/pre>\n<p>\u0421\u0440\u0430\u0437\u0443 \u0441\u043a\u0430\u0436\u0443, \u0447\u0442\u043e \u044f \u043f\u043e\u043a\u0430 \u043d\u0435 \u0437\u0430\u043c\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043b\u0441\u044f \u0441 \u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438 \u0442\u0435\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u044e \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u0442\u0430\u043a \u043a\u0430\u043a \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u2013 \u043b\u0438\u0448\u044c \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f.<\/p>\n<p>\u041f\u043e\u043b\u0435 mattermost_allowed_users \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0432\u044b\u0448\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0445 \u0441\u043e\u0431\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Mattermost, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0430\u043f\u043b\u0435\u043d \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 Kaiten.<\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0432\u043e\u0442 \u043a\u0443\u0441\u043e\u043a, \u0433\u0434\u0435 \u043c\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u043c \u043a\u0430\u043a MmHelper, \u0442\u0430\u043a \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Websocket:<\/p>\n<pre><code class=\"python\">        # \u041a\u043b\u0438\u0435\u043d\u0442 \u043a API Mattermost         MmHelper.client = http.client.HTTPSConnection(config[\"mattermost\"][\"host\"])         mm_auth = f\"Bearer {config['mattermost']['token']}\"         MmHelper.headers[\"Authorization\"] = mm_auth         MmHelper.headers[\"Content-Type\"] = \"application\/json\"          # \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Mattermost \u0447\u0435\u0440\u0435\u0437 WebSocket         WebSocketMattermostApp.mm_ws_headers[\"Authorization\"] = mm_auth<\/code><\/pre>\n<p>\u0422\u043e\u0433\u0434\u0430 \u043c\u0435\u0442\u043e\u0434 connect() \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u0438\u043b\u0435\u0442\u0435\u043b\u043e \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0430:<\/p>\n<pre><code class=\"python\">    def connect():         \"\"\"         \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a WebSocket         \"\"\"          WebSocketMattermostApp.configure()         WebSocketMattermostApp.connection = websocket.WebSocketApp(mm_ws_url,                                     header=WebSocketMattermostApp.mm_ws_headers,                                     on_open=WebSocketMattermostApp.ws_on_open,                                     on_message=WebSocketMattermostApp.ws_on_message,                                     on_error=WebSocketMattermostApp.ws_on_error,                                     on_close=WebSocketMattermostApp.ws_on_close)         WebSocketMattermostApp.connection.run_forever(reconnect=5)<\/code><\/pre>\n<p>KaitenHelper \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u043c \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e:<\/p>\n<pre><code class=\"python\">        # \u041a\u043b\u0438\u0435\u043d\u0442 \u043a API Kaiten         KaitenHelper.client = http.client.HTTPSConnection(config[\"kaiten\"][\"host\"])          KaitenHelper.headers[\"Authorization\"] = f\"Bearer {config['kaiten']['token']}\"         KaitenHelper.headers[\"Content-Type\"] = \"application\/json\"<\/code><\/pre>\n<p>\u0418, \u043d\u0430\u043a\u043e\u043d\u0435\u0446, \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0432 IncomingPostHandler, \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043f\u0440\u0438\u0441\u043b\u0430\u0432\u0448\u0435\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u0435\u043b\u044f \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0432 Kaiten:<\/p>\n<pre><code class=\"python\">        IncomingPostHandler.users_of_bot = config['mattermost_allowed_users']<\/code><\/pre>\n<p>\u0412\u0435\u0440\u043d\u0435\u043c\u0441\u044f \u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c \u0432 WebSocketMattermostApp. \u041f\u043e\u043c\u0435\u043d\u044f\u0435\u043c \u043c\u0435\u0442\u043e\u0434 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f:  <\/p>\n<pre><code class=\"python\">    def ws_on_message(ws, message):         \"\"\"         \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f         \"\"\"         msg_obj = json.loads(message)                  # \u041e\u0442\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u0435\u043c my_tag         if msg_obj[\"event\"] == \"posted\" and bot_mm_tag in msg_obj[\"data\"][\"post\"]:             post_obj = json.loads(msg_obj[\"data\"][\"post\"])                          # \u0418\u0449\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043d\u044b\u0445             found_user = next(                 (mm_user for mm_user in config[\"mattermost_allowed_users\"] if mm_user[\"id\"] == post_obj[\"user_id\"]),                 None)             if found_user != None:                 IncomingPostHandler.handle(post_obj)<\/code><\/pre>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u044d\u0442\u043e\u0442 \u043a\u0443\u0441\u043e\u043a \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435. \u0417\u0434\u0435\u0441\u044c \u0442\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438:<\/p>\n<ol>\n<li>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0442\u0438\u043f\u0430 posted (\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435)<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">\u00a0 \u00a0 \u00a0 \u00a0 if msg_obj[\"event\"] == \"posted\" <\/code><\/pre>\n<ol start=\"2\">\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u044f\u0432\u043d\u043e \u0442\u0435\u0433\u043d\u0443\u043b\u0438 \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u043e\u0442\u0430 \u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0438<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">\u00a0 \u00a0 \u00a0 \u00a0and bot_mm_tag in msg_obj[\"data\"][\"post\"]:<\/code><\/pre>\n<ol start=\"3\">\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043d\u043d\u044b\u0445 (mattermost_allowed_users \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435).<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if found_user != None:<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0441\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u043e\u0448\u043b\u0438, \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 IncomingPostHandler:<\/p>\n<pre><code class=\"python\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 IncomingPostHandler.handle(post_obj)<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u043c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0431\u043b\u043e\u043a \u043a\u043e\u0434\u0430 \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 WebSocketMattermostApp:<\/p>\n<pre><code># \u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0444\u0430\u0439\u043b\u0430 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438  if __name__ == \"__main__\": \u00a0 \u00a0 WebSocketMattermostApp.connect()<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043c\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u044d\u0442\u043e\u0442 py-\u0444\u0430\u0439\u043b, \u043e\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u0434\u0440\u0443\u0433\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0438 \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435. \u0422\u0430\u043a\u0438\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0443\u0434\u043e\u0431\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u043e\u0442\u043b\u0430\u0434\u043a\u0443.<\/p>\n<h2>\u0420\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>\u0414\u043b\u044f \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u0431\u043e\u0442\u0430 \u0431\u044b\u043b \u0432\u044b\u0431\u0440\u0430\u043d \u043e\u0434\u0438\u043d \u0438\u0437 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 Kubernetes \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u0432, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0433\u043b\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0440\u0435\u043f\u043e\u0440\u0442\u0438\u0442\u044c \u043e \u0441\u0432\u043e\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438, \u0435\u0433\u043e \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u043e Flask. \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0440\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u044b, \u0432\u044b\u043d\u0435\u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b, \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e WebSocketMattermopstApp \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435:<\/p>\n<pre><code class=\"python\">import json import logging import os from threading import Thread from flask import Flask from ws_mm_app import WebSocketMattermostApp  app = Flask('mattermost-chat-ops-bot')  @app.route('\/liveness') @app.route('\/readyness') def getProbe():     \"\"\"     \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0440\u043e\u0431\u044b \u0434\u043b\u044f k8s     \"\"\"     return \"Healthy\"  with app.app_context():     # \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0440\u0430\u0431\u043e\u0447\u0443\u044e \u043f\u0430\u043f\u043a\u0443     os.chdir(os.path.dirname(os.path.abspath(__file__)))      # \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0444\u0430\u0439\u043b\u0430     with open('logging_config.json') as file:         logging.config.dictConfig(json.load(file))     logging.info(\"Logging configuration completed\")      # \u0421\u0442\u0430\u0440\u0442\u0443\u0435\u043c WebSocket-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435     ws_thread = Thread(target=WebSocketMattermostApp.connect)     ws_thread.start()  # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c Flask-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u043f\u0440\u044f\u043c\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435 \u0444\u0430\u0439\u043b\u0430 if __name__ == '__main__':     app.run(host='0.0.0.0', port=5000)<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u0444\u0430\u0439\u043b\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u043b\u0438\u0441\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 <\/p>\n<pre><code class=\"python\">\u00a0 \u00a0 \u00a0 \u00a0 os.chdir(os.path.dirname(os.path.abspath(__file__)))<\/code><\/pre>\n<p>\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0440\u0430\u0431\u043e\u0447\u0430\u044f \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f \u2013 \u043d\u0430 \u0442\u0443, \u0433\u0434\u0435 \u043b\u0435\u0436\u0438\u0442 \u0441\u0430\u043c \u0444\u0430\u0439\u043b.<\/p>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u043d\u0435\u0441\u0435\u043d\u0430 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 json-\u0444\u0430\u0439\u043b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u043c\u0435\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443:<\/p>\n<pre><code class=\"json\">{     \"version\": 1,     \"formatters\": {         \"default\": {             \"format\": \"%(asctime)s|%(levelname)s|%(module)s|%(message)s\"         }     },     \"handlers\": {         \"console\": {             \"class\": \"logging.StreamHandler\",             \"stream\": \"ext:\/\/sys.stdout\",             \"formatter\": \"default\"         }     },     \"root\": {         \"level\": \"INFO\",         \"handlers\": [             \"console\"         ]     } }<\/code><\/pre>\n<p>\u0412 \u043a\u043e\u0434\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b, \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 logging:<\/p>\n<pre><code class=\"python\">        with open('logging_config.json') as file:             logging.config.dictConfig(json.load(file))<\/code><\/pre>\n<h2>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b<\/h2>\n<p>\u0412\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0431\u043e\u0442\u043e\u043c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/ddc\/76b\/a9a\/ddc76ba9ad42894bfbe4b67aec97c7ba.jpg\" width=\"494\" height=\"311\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ddc\/76b\/a9a\/ddc76ba9ad42894bfbe4b67aec97c7ba.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0411\u043e\u0442 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u043a\u0438\u0434\u044b\u0432\u0430\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0431\u044d\u043a\u043b\u043e\u0433. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u043f\u043e \u043c\u0435\u0440\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u044b\u0445 \u0438\u0434\u0435\u0439 \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u0435\u0439, \u0432 \u0431\u043e\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c.<\/p>\n<p>\u041c\u043d\u0435 \u0436\u0435 \u0432 \u0438\u0442\u043e\u0433\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 \u0441\u0442\u0435\u043a \u0438 \u0443\u0437\u043d\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043d\u043e\u0432\u043e\u0435.<\/p>\n<p>\u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u041a\u0430\u0439\u0442\u0435\u043d\u0430, \u0442\u044d\u0433 \u0431\u043e\u0442\u0430 \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u043a\u043e\u0434\u0430.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435, \u0435\u0441\u0442\u044c \u043f\u043b\u0430\u043d\u044b \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u043e\u0434, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 Mattermost \u0431\u043e\u0442 \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u043a\u043e\u0435-\u0442\u043e \u0432\u0440\u0435\u043c\u044f \u043c\u043e\u0433 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442\u0443 \u044f \u043e\u0441\u043e\u0437\u043d\u0430\u043d\u043d\u043e \u043d\u0435 \u0441\u0442\u0430\u043b \u043e\u0442\u0440\u0430\u0436\u0430\u0442\u044c \u0432 \u043f\u0440\u043e\u0431\u0430\u0445, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443, \u0435\u0441\u043b\u0438 Mattermost \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043a\u043b\u0430\u0441\u0442\u0435\u0440 kubernetes \u044f\u0432\u043d\u043e \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0440\u0435\u0448\u0438\u0442\u044c \u044d\u0442\u0443 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443, \u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u0441\u043c\u043e\u0433\u0443\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u0442\u0443. \u0412 \u043e\u0431\u0449\u0435\u043c, \u0432 \u043f\u043b\u0430\u043d\u0435 \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u043e\u0441\u0442\u0438 \u0435\u0441\u0442\u044c \u0435\u0449\u0435 \u043a\u0443\u0434\u0430 \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f.<\/p>\n<h2>\u0421\u0441\u044b\u043b\u043a\u0438<\/h2>\n<p><a href=\"https:\/\/github.com\/AlfaInsurance\/mattermost_chatops\">https:\/\/github.com\/AlfaInsurance\/mattermost_chatops<\/a> &#8212; \u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u043a\u043e\u0434\u043e\u043c \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438<\/p>\n<p><a href=\"https:\/\/developers.kaiten.ru\/\">https:\/\/developers.kaiten.ru\/<\/a> &#8212; API Kaiten<\/p>\n<p><a href=\"https:\/\/api.mattermost.com\/\">https:\/\/api.mattermost.com\/<\/a> &#8212; API Mattermost (\u0432 \u0442. \u0447. \u043f\u0440\u043e WebSocket)<\/p>\n<p><a href=\"https:\/\/pypi.org\/project\/websocket-client\/\">https:\/\/pypi.org\/project\/websocket-client\/<\/a> &#8212; \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 websocket-client<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/796379\/\"> https:\/\/habr.com\/ru\/articles\/796379\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440!<\/p>\n<p>\u041a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0410\u043b\u044c\u0444\u0430\u0421\u0442\u0440\u0430\u0445\u043e\u0432\u0430\u043d\u0438\u0435, \u043a\u0430\u043a \u0438 \u043c\u043d\u043e\u0433\u0438\u0435 \u0434\u0440\u0443\u0433\u0438\u0435, \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0430\u0441\u044c \u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c\u044e \u0437\u0430\u043c\u0435\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0432 \u0441\u0432\u044f\u0437\u0438 \u0441 \u0441\u0430\u043d\u043a\u0446\u0438\u044f\u043c\u0438. \u0417\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0433\u043e\u0434 \u043c\u044b \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438\u0441\u044c \u043e\u0442 Slack \u0432 \u043f\u043e\u043b\u044c\u0437\u0443 open-source \u0430\u043d\u0430\u043b\u043e\u0433\u0430 Mattermost, \u0430 Jira \u043d\u0435 \u0431\u0435\u0437 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0431\u044b\u043b\u0430 \u0437\u0430\u043c\u0435\u043d\u0435\u043d\u0430 \u043d\u0430 Kaiten.<\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u0435, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u043e\u0431\u043e\u0440\u043e\u0442\u0430 \u0432 \u0447\u0430\u0441\u0442\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432, \u0447\u0430\u0441\u0442\u043e \u043d\u0430 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u044b\u0445 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0445 \u0438 \u043f\u0440\u0438 \u043e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u0438 \u0432 \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0435 Mattermost \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u043b\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u043d\u0430\u043a\u0438\u0434\u0430\u0442\u044c \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a\u0438 \u0437\u0430\u0434\u0430\u0447 \u0432 \u0442\u0430\u0441\u043a-\u0442\u0440\u0435\u043a\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0442\u043e\u043c \u0438\u0445 \u0434\u043e\u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u044c. \u042d\u0442\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043b\u043e\u0436\u0438\u043b\u0430\u0441\u044c \u043d\u0430 \u043c\u043e\u0435 \u0436\u0435\u043b\u0430\u043d\u0438\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043d\u0430 python, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0435 \u0441 Chat-Ops.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u0431\u043e\u0442\u0430 \u044f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u0440\u044f\u0434\u043e\u043c \u0441\u043b\u0430\u0431\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 \u0430\u0441\u043f\u0435\u043a\u0442\u043e\u0432, \u043e \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438 \u0445\u043e\u0442\u0435\u043b \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<h2>\u041e\u0431\u0449\u0435\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435<\/h2>\n<p>\u041a\u043e\u043b\u043b\u0435\u0433\u0438 \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u0430\u043b\u0438, \u0447\u0442\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 Mattermost \u043c\u043e\u0436\u043d\u043e \u0447\u0435\u0440\u0435\u0437 websocket. \u042f\u0437\u044b\u043a python \u0431\u044b\u043b \u0432\u044b\u0431\u0440\u0430\u043d, \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u044f\u0445 \u043d\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0442\u0430\u043a\u0436\u0435 \u0441\u044b\u0433\u0440\u0430\u043b\u043e \u0440\u043e\u043b\u044c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f websocket-\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432.<\/p>\n<p>\u0411\u043e\u0442\u0430 \u0441\u0434\u0435\u043b\u0430\u043b \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u043c, \u0431\u0435\u0437 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043a\u043d\u043e\u043f\u043e\u0447\u0435\u043a, \u043a \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u0431\u044b Web API \u2013 \u043f\u043e \u0441\u0443\u0442\u0438, \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0433\u0438\u0431\u043a\u0438\u0439 \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u044d\u0442\u043e \u0432\u0441\u0451 \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0431\u0435\u0437\u0431\u043e\u043b\u0435\u0437\u043d\u0435\u043d\u043d\u043e \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c. \u0421\u0445\u0435\u043c\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u0442\u0430\u043a\u0430\u044f:<\/p>\n<figure class=\"full-width\"><\/figure>\n<h2>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 websocket &#8212; \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430, \u044f \u043d\u0430\u0443\u0447\u0438\u043b\u0441\u044f \u0447\u0438\u0442\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 websocket \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 websocket-client (<a href=\"https:\/\/pypi.org\/project\/websocket-client\/\">https:\/\/pypi.org\/project\/websocket-client\/<\/a>). <\/p>\n<p>\u0418\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0432\u044b\u0448\u0435, \u0438 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 Mattermost \u043a\u0430\u0441\u0430\u0442\u0435\u043b\u044c\u043d\u043e websocket, \u044f \u0441\u043b\u0435\u043f\u0438\u043b \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a\u043e\u0435:<\/p>\n<pre><code class=\"python\">import logging import logging.config import websocket  class WebSocketMattermostApp:     \"\"\"     \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0441\u043b\u0443\u0448\u0430\u044e\u0449\u0435\u0435 Mattermost \u0447\u0435\u0440\u0435\u0437 WebSocket     \"\"\"      mm_ws_headers = dict()     \"\"\"     \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0434\u043b\u044f WebSocket-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a API Mattermost     \"\"\"      connection: websocket.WebSocketApp     \"\"\"     \u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 WebSocket     \"\"\"      def connect():         \"\"\"         \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a WebSocket         \"\"\"          # \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a Mattermost \u0447\u0435\u0440\u0435\u0437 WebSocket         WebSocketMattermostApp.mm_ws_headers[\"Authorization\"] = \"Bearer ****************\"          WebSocketMattermostApp.connection = websocket.WebSocketApp(\"wss:\/\/*************\/api\/v4\/websocket\",                                     header=WebSocketMattermostApp.mm_ws_headers,                                     on_open=WebSocketMattermostApp.ws_on_open,                                     on_message=WebSocketMattermostApp.ws_on_message,                                     on_error=WebSocketMattermostApp.ws_on_error,                                     on_close=WebSocketMattermostApp.ws_on_close)         WebSocketMattermostApp.connection.run_forever(reconnect=5)      def disconnect():         \"\"\"         \u041e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 WebSocket         \"\"\"         WebSocketMattermostApp.connection.close()      def ws_on_message(ws, message):         \"\"\"         \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f         \"\"\"         print(message)     def ws_on_error(ws, error):         \"\"\"         \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435         \"\"\"         logging.error(f\"Error: {error}\")      def ws_on_close(ws, close_status_code, close_msg):         \"\"\"         \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f         \"\"\"         logging.info(             f\"Connection closed {close_status_code} | {close_msg}\")      def ws_on_open(ws):         \"\"\"         \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f         \"\"\"         logging.info(\"Connection opened\")  # \u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0444\u0430\u0439\u043b\u0430 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438  if __name__ == \"__main__\":     WebSocketMattermostApp.connect()<\/code><\/pre>\n<p>\u0412 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043a\u0435\u043d \u0431\u043e\u0442-\u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 Mattermost, \u0430 \u0432 URI \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a websocket &#8212; \u0445\u043e\u0441\u0442 \u0438\u043d\u0441\u0442\u0430\u043d\u0441\u0430 Mattermost.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u0430\u0439\u043b\u0430 \u0432\u044b\u0448\u0435, \u0435\u0441\u043b\u0438 \u0432\u0441\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f \u0438 \u043d\u0430\u0447\u043d\u0435\u0442 \u043f\u0438\u0441\u0430\u0442\u044c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0432\u0441\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438\u0437 \u0432\u0441\u0435\u0445 \u043a\u0430\u043d\u0430\u043b\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0430\u0448\u0430 \u0443\u0447\u0435\u0442\u043a\u0430 \u0431\u043e\u0442\u0430.<\/p>\n<p>\u0421\u0443\u0449\u043d\u043e\u0441\u0442\u0438, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 websocket, \u0431\u044b\u0432\u0430\u044e\u0442 \u0440\u0430\u0437\u043d\u044b\u0435. \u041d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0442\u0438\u043f posted (\u0442.\u0435. \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435), \u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043d\u0438\u0436\u0435:<\/p>\n<pre><code class=\"json\">{     \"event\": \"posted\",     \"data\": {         \"channel_display_name\": \"my_mm_channel\",         \"channel_name\": \"my_mm_channel\",         \"channel_type\": \"P\",         \"post\":\"{\\\"id\\\":\\\"1qaz2wsx3edc4rfv\\\",\\\"create_at\\\":123456789,\\\"update_at\\\":123456789,\\\"edit_at\\\":0,\\\"delete_at\\\":0,\\\"is_pinned\\\":false,\\\"user_id\\\":\\\"1qaz2wsx3edc\\\",\\\"channel_id\\\":\\\"1qaz2wsx3edc\\\",\\\"root_id\\\":\\\"\\\",\\\"original_id\\\":\\\"\\\",\\\"message\\\":\\\"Test message\\\",\\\"type\\\":\\\"slack_attachment\\\",\\\"props\\\":{\\\"attachments\\\":[{\\\"id\\\":0,\\\"fallback\\\":\\\"\\\",\\\"color\\\":\\\"#CF0A2C\\\",\\\"pretext\\\":\\\"\\\",\\\"author_name\\\":\\\"\\\",\\\"author_link\\\":\\\"\\\",\\\"author_icon\\\":\\\"\\\",\\\"title\\\":\\\"Message attachment title\\\",\\\"title_link\\\":\\\"\\\",\\\"text\\\":\\\"\\\",\\\"fields\\\":null,\\\"image_url\\\":\\\"\\\",\\\"thumb_url\\\":\\\"\\\",\\\"footer\\\":\\\"message footer\\\",\\\"footer_icon\\\":\\\"\\\",\\\"ts\\\":null}],\\\"from_bot\\\":\\\"true\\\",\\\"from_webhook\\\":\\\"true\\\",\\\"override_icon_emoji\\\":\\\":love_letter:\\\",\\\"override_icon_url\\\":\\\"\/static\/emoji\/1f48c.png\\\",\\\"override_username\\\":\\\"my_mm_bot\\\",\\\"webhook_display_name\\\":\\\"my_mm_bot\\\"},\\\"hashtags\\\":\\\"\\\",\\\"pending_post_id\\\":\\\"\\\",\\\"reply_count\\\":0,\\\"last_reply_at\\\":0,\\\"participants\\\":null,\\\"metadata\\\":{\\\"embeds\\\":[{\\\"type\\\":\\\"message_attachment\\\"}]}}\",         \"sender_name\": \"my_mm_bot\",         \"set_online\": false,         \"team_id\": \"1qaz2wsx3edc4rfv\"     },     \"broadcast\": {         \"omit_users\": null,         \"user_id\": \"\",         \"channel_id\": \"1qaz2wsx3edc4rfv\",         \"team_id\": \"\",         \"connection_id\": \"\",         \"omit_connection_id\": \"\"     },     \"seq\": 6 }<\/code><\/pre>\n<p>\u041a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c, \u0432 \u043f\u043e\u043b\u0435 data.post \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0435\u0433\u043e JSON \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u0442\u043e\u0440\u043e\u0439, \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0439 JSON; \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u043d\u0451\u043c \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0449\u0430\u044f \u043d\u0430\u0441 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f: \u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a \u043d\u0435\u043c\u0443, \u0418\u0414 \u0430\u0432\u0442\u043e\u0440\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u0442.\u0434.:<\/p>\n<pre><code class=\"json\">{     \"id\": \"1qaz2wsx3edc4rfv\",     \"create_at\": 123456789,     \"update_at\": 123456789,     \"edit_at\": 0,     \"delete_at\": 0,     \"is_pinned\": false,     \"user_id\": \"1qaz2wsx3edc\",     \"channel_id\": \"1qaz2wsx3edc\",     \"root_id\": \"\",     \"original_id\": \"\",     \"message\": \"Test message\",     \"type\": \"slack_attachment\",     \"props\": {         \"attachments\": [             {                 \"id\": 0,                 \"fallback\": \"\",                 \"color\": \"#CF0A2C\",                 \"pretext\": \"\",                 \"author_name\": \"\",                 \"author_link\": \"\",                 \"author_icon\": \"\",                 \"title\": \"Message attachment title\",                 \"title_link\": \"\",                 \"text\": \"\",                 \"fields\": null,                 \"image_url\": \"\",                 \"thumb_url\": \"\",                 \"footer\": \"message footer\",                 \"footer_icon\": \"\",                 \"ts\": null             }         ],         \"from_bot\": \"true\",         \"from_webhook\": \"true\",         \"override_icon_emoji\": \":love_letter:\",         \"override_icon_url\": \"\/static\/emoji\/1f48c.png\",         \"override_username\": \"my_mm_bot\",         \"webhook_display_name\": \"my_mm_bot\"     },     \"hashtags\": \"\",     \"pending_post_id\": \"\",     \"reply_count\": 0,     \"last_reply_at\": 0,     \"participants\": null,     \"metadata\": {         \"embeds\": [             {                 \"type\": \"message_attachment\"             }         ]     } }<\/code><\/pre>\n<h3>\u0420\u0430\u0437\u043d\u0435\u0441\u0435\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438<\/h3>\n<p>\u0420\u0430\u0437\u043d\u0435\u0441\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u041a\u0430\u0439\u0442\u0435\u043d, Mattermost (\u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0432\u0435\u0442\u0430) \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u0440\u0430\u0437\u043d\u044b\u043c \u0444\u0430\u0439\u043b\u0430\u043c.<\/p>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u041a\u0430\u0439\u0442\u0435\u043d \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430 \u2013 \u043f\u0430\u0440\u0441\u0438\u043c \u0442\u0435\u043a\u0441\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u043c \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0442\u0435\u043a\u0441\u0442, \u0432\u0437\u044f\u0442\u044b\u0439 \u0432 \u043a\u0430\u0432\u044b\u0447\u043a\u0438 \u2013 \u044d\u0442\u043e \u0438 \u0431\u0443\u0434\u0435\u0442 \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0430 \u0434\u043b\u044f \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0439 \u0432 \u041a\u0430\u0439\u0442\u0435\u043d \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0434\u043e\u0441\u043a\u0443, \u0434\u043e\u0440\u043e\u0436\u043a\u0443 \u0438 \u0442\u0438\u043f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438. \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0439 \u0418\u0414 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u0432 \u041a\u0430\u0439\u0442\u0435\u043d. \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u0430 headers \u0438 client \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u0430. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">import http.client import json import re  class KaitenHelper:      \"\"\"     \u041f\u043e\u043c\u043e\u0449\u043d\u0438\u043a \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a API Kaiten     \"\"\"      headers = dict()     \"\"\"     \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a API Kaiten     \"\"\"      client: http.client.HTTPSConnection = None     \"\"\"     \u041a\u043b\u0438\u0435\u043d\u0442 \u043a API Kaiten     \"\"\"      def create_kaiten_card(message, creator_id):         \"\"\"         \u0421\u043e\u0437\u0434\u0430\u0435\u0442 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0443 \u0432 Kaiten         \"\"\"          # \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e         board_id = 1  # \u0418\u0414 \u0414\u043e\u0441\u043a\u0438         lane_id = 2  # \u0418\u0414 \u0414\u043e\u0440\u043e\u0436\u043a\u0438 \u043d\u0430 \u0414\u043e\u0441\u043a\u0435         type_id = 26  # \u0418\u0414 \u0442\u0438\u043f\u0430 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438         properties = {}          # \u0422\u0435\u043a\u0441\u0442 \u0437\u0430\u0434\u0430\u0447\u0438         double_quotes_title_regex = re.compile(             r'\"(.+)\"',             flags=re.I | re.M)         single_quotes_title_regex = re.compile(             r'\\'(.+)\\'',             flags=re.I | re.M)         task_title_regex = re.compile(             r'(\u0437\u0430\u0434\u0430\u0447|\u0442\u0430\u0441\u043a|kaiten|\u043a\u0430\u0439\u0442\u0435\u043d).*? (.+)',             flags=re.I | re.M)         title_search = double_quotes_title_regex.search(message)         if title_search == None:             title_search = single_quotes_title_regex.search(message)         if title_search == None:             title_search = task_title_regex.search(message)         if title_search == None or len(title_search.groups()) == 0:             return None         title = title_search.groups()[-1]          # \u041c\u0435\u043d\u044f\u0435\u043c \u0447\u0430\u0441\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0442\u0435\u0445\u0434\u043e\u043b\u0433\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447         tech_debt_regex = re.compile(             r'(\u0432 \u0442\u0435\u0445.*\u0434\u043e\u043b\u0433|\u0442\u0435\u0445.*\u0434\u043e\u043b\u0433.+(\u0437\u0430\u0434\u0430\u0447|\u0442\u0430\u0441\u043a))',             flags=re.I | re.M)         if tech_debt_regex.search(message):             # \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u0447 \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u043e\u043b\u0433\u0430             board_id = 4  # \u0414\u043e\u0441\u043a\u0430: \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0434\u043e\u043b\u0433             lane_id = 2  # \u0414\u043e\u0440\u043e\u0436\u043a\u0430: \u0412\u0430\u0436\u043d\u044b\u0435             type_id = 7  # \u0422\u0438\u043f \u0434\u043b\u044f \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u043e\u043b\u0433\u0430          body = json.dumps({             \"title\": title,             \"board_id\": board_id,<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-369287","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/369287","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=369287"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/369287\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=369287"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=369287"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=369287"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}