{"id":473829,"date":"2025-09-03T15:01:04","date_gmt":"2025-09-03T15:01:04","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=473829"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=473829","title":{"rendered":"<span>WhatsApp Web \u0438 Telegram \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0434\u043b\u044f Bitrix24: \u043d\u0430\u0448 \u043e\u043f\u044b\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f. \u0427\u0430\u0441\u0442\u044c 1 \u2014 \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f Bitrix24<\/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, \u043c\u0438\u0440! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041f\u0430\u0432\u0435\u043b, \u044f IT \u0438\u043d\u0436\u0435\u043d\u0435\u0440 \u0438 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c \u0441\u043b\u0443\u0436\u0431\u044b \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438.<\/p>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u044f \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 \u043a\u0440\u0443\u043f\u043d\u043e\u0433\u043e IT-\u0430\u0443\u0442\u0441\u043e\u0440\u0441\u0438\u043d\u0433\u0430, \u043c\u044b \u0432 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439: \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u0449\u0435\u0433\u043e WhatsApp\/Telegram Web, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u043e\u0433\u043e \u043d\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430\u0445 \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u043e\u0432 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438, \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u043c. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u043b \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0434\u0438\u0430\u043b\u043e\u0433\u043e\u0432, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u044f\u043b \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0439 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0442\u0438\u043a\u0435\u0442\u044b, \u0432\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u0435 \u0447\u0435\u0433\u043e \u0431\u044b\u043b\u0430 \u043d\u0430\u0447\u0430\u0442\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0430\u043b\u0443 <strong>Bitrix24<\/strong>.<\/p>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0431\u044b \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u0441\u043d\u043e\u0432\u0430\u043c\u0438 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043e\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430.  \u0414\u0435\u0442\u0430\u043b\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 &#8212; \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u0435\u0440\u0441\u0442\u043a\u0443 html \u0441\u0442\u0440\u0430\u043d\u0438\u0446, \u0442\u0435\u0441\u0442\u044b, \u043a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u0430\u043d\u0435\u043b\u0438 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 \u0438 \u0442\u0430\u043a \u0434\u0430\u043b\u0435\u0435 &#8212; \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u043d\u0430 \u0432\u0430\u0448\u0435 \u0443\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0438\u0435.<\/p>\n<h2>\u0422\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0442\u0435\u043a<\/h2>\n<p> \u041e\u0441\u043d\u043e\u0432\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 <strong>Python <\/strong>\u0438 <strong>Django<\/strong>, \u0447\u0442\u043e \u043e\u0431\u0443\u0441\u043b\u043e\u0432\u043b\u0435\u043d\u043e \u0438\u0445 \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u043e\u0439, \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c\u044e \u0438 \u0448\u0438\u0440\u043e\u043a\u0438\u043c \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439. \u0422\u0430\u043a\u043e\u0439 \u0432\u044b\u0431\u043e\u0440 \u0434\u0430\u043b \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0436\u0438\u0437\u043d\u0435\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 (MVP) \u0438 \u0437\u0430\u043b\u043e\u0436\u0438\u0442\u044c \u0444\u0443\u043d\u0434\u0430\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0433\u043e \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c redis, celery  \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u0445.<\/p>\n<h2>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b Bitrix<\/h2>\n<p>\u041e\u043f\u0443\u0441\u0442\u0438\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 Django \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0443 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u043e\u043c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c Django \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 bitrix. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0432 <strong>models.py<\/strong>:<\/p>\n<pre><code class=\"python\">import uuid from django.conf import settings from django.contrib.sites.models import Site from django.db import models<\/code><\/pre>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043e \u043f\u043e\u0440\u0442\u0430\u043b\u0435:<\/p>\n<pre><code class=\"python\">class Bitrix(models.Model):     PROTOCOL_CHOICES = [         ('http', 'HTTP'),         ('https', 'HTTPS'),     ]     protocol = models.CharField(max_length=5, choices=PROTOCOL_CHOICES, default='https')     domain = models.CharField(max_length=255)     owner = models.ForeignKey(         User, on_delete=models.SET_NULL, blank=True, null=True     )     user_id = models.CharField(max_length=255, blank=True, null=True)     member_id = models.CharField(max_length=255, unique=True, blank=True, null=True)     license_expired = models.BooleanField(default=False)      def __str__(self):         return self.domain<\/code><\/pre>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430:<\/p>\n<pre><code class=\"python\">class Connector(models.Model):     TYPE_CHOICES = [         ('telegram', 'Telegram Bot'),         ('waweb', 'WhatsApp Web'),     ]     code = models.CharField(max_length=255, default=uuid.uuid4(), unique=True)     service = models.CharField(max_length=255, choices=TYPE_CHOICES, blank=True, null=True)     name = models.CharField(max_length=255, default=\"itsource.kg\", unique=False)     icon = models.FileField(upload_to='connector_icons\/', blank=True, null=True,                             default='connector_icons\/cloud-rain-alt.svg') # \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u0432\u0430\u0448\u0443 svg \u0438\u043a\u043e\u043d\u043a\u0443      def __str__(self):         return self.name<\/code><\/pre>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0411\u0438\u0442\u0440\u0438\u043a\u0441:<\/p>\n<pre><code class=\"python\">class App(models.Model):     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)     owner = models.ForeignKey(         settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True     )     site = models.ForeignKey(         Site, on_delete=models.SET_NULL, related_name=\"apps\", blank=True, null=True     )     name = models.CharField(max_length=255, blank=True, unique=False)     page_url = models.CharField(max_length=255, blank=True, default=\"\/\")     connectors = models.ManyToManyField(Connector, blank=True, related_name='apps')     asterisk = models.BooleanField(default=False, help_text=\"Chek for Asterisk connector\")     client_id = models.CharField(max_length=255, blank=True, unique=False)     client_secret = models.CharField(max_length=255, blank=True)      def __str__(self):         return self.name<\/code><\/pre>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043b\u0438\u043d\u0438\u0439:<\/p>\n<pre><code class=\"python\">class AppInstance(models.Model):     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)     owner = models.ForeignKey(         settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True     )     app = models.ForeignKey(App, on_delete=models.SET_NULL, related_name=\"installations\", blank=True, null=True)     portal = models.ForeignKey(         Bitrix, on_delete=models.CASCADE, related_name=\"installations\", blank=True, null=True     )     auth_status = models.CharField(max_length=1)     application_token = models.CharField(max_length=255, blank=True)     storage_id = models.CharField(max_length=255, blank=True)     status = models.IntegerField(default=0, blank=True)     attempts = models.IntegerField(default=0, blank=True)     access_token = models.CharField(max_length=255, blank=True, null=True, editable=False)     refresh_token = models.CharField(max_length=255, blank=True, null=True, editable=False)      def __str__(self):         return f\"{self.app.name} on {self.portal.domain}\"   class Line(models.Model):     line_id = models.CharField(max_length=50)     owner = models.ForeignKey(         settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True     )     app_instance = models.ForeignKey(AppInstance, on_delete=models.CASCADE, related_name=\"lines\", null=True)     connector = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name=\"lines\", null=True)     portal = models.ForeignKey(Bitrix, on_delete=models.CASCADE, related_name=\"lines\", blank=True, null=True)      def __str__(self):         return f\"Line {self.line_id}\"<\/code><\/pre>\n<p>\u042f \u043f\u0440\u0438\u0432\u0435\u0440\u0436\u0435\u043d\u0435\u0446 \u043c\u043e\u0434\u0443\u043b\u044c\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c api \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f bitrix \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 api \u0432\u0435\u0431\u0445\u0443\u043a\u043e\u0432<\/p>\n<p><strong>api\/serializers.py<\/strong><\/p>\n<pre><code class=\"python\">from rest_framework import serializers  from bitrix.models import Bitrix   class PortalSerializer(serializers.ModelSerializer):     class Meta:         model = Bitrix         fields = [             \"owner\",             \"user_id\",             \"domain\",         ]      def create(self, validated_data):         return Bitrix.objects.create(**validated_data) <\/code><\/pre>\n<p><strong>api\/views.py<\/strong><\/p>\n<pre><code class=\"python\">from rest_framework.mixins import CreateModelMixin from rest_framework.renderers import JSONRenderer from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response  from bitrix.models import Bitrix  from bitrix.utils import event_processor from .serializers import PortalSerializer   class PortalViewSet(CreateModelMixin, GenericViewSet):     queryset = Bitrix.objects.all()     serializer_class = PortalSerializer      def create(self, request, *args, **kwargs):         print(\"create func\")         return event_processor(request)      def head(self, request, *args, **kwargs):         print(\"head func\")         return Response(headers={'Allow': 'POST, HEAD'}) <\/code><\/pre>\n<p>\u0412 <strong>settings.py <\/strong>\u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0441 \u044f\u0434\u0440\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u043a\u043b\u044e\u0447\u0438\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e \u0442\u043e\u043a\u0435\u043d\u0430\u043c<\/p>\n<pre><code class=\"python\">REST_FRAMEWORK = {     \"DEFAULT_AUTHENTICATION_CLASSES\": (         \"rest_framework.authentication.SessionAuthentication\",         \"rest_framework.authentication.TokenAuthentication\",         \"core.qpta.QueryParamTokenAuthentication\",     ),     \"DEFAULT_PERMISSION_CLASSES\": (\"rest_framework.permissions.IsAuthenticated\",),     'DEFAULT_RENDERER_CLASSES': ('rest_framework.renderers.JSONRenderer',) }<\/code><\/pre>\n<p>\u0422\u0430\u043c \u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <strong>qpta.py<\/strong>:<\/p>\n<pre><code class=\"python\">from rest_framework.authentication import TokenAuthentication   class QueryParamTokenAuthentication(TokenAuthentication):     def authenticate(self, request):         # Try to get the token from the URL query parameter         token = request.query_params.get(\"api-key\")          if not token:             # Fall back to default token authentication             return super().authenticate(request)          # Authenticate the token manually         user, token = self.authenticate_credentials(token)         return (user, token) <\/code><\/pre>\n<p>\u0418<strong> api_router.py<\/strong>:<\/p>\n<pre><code class=\"python\">from django.conf import settings from rest_framework.routers import DefaultRouter from rest_framework.routers import SimpleRouter  from bitrix.api.views import PortalViewSet from users.api.views import UserViewSet from waweb.api.views import EventsHandler from telegram.api.views import TelegramEventsHandler  router = DefaultRouter() if settings.DEBUG else SimpleRouter()  router.register(\"users\", UserViewSet) router.register(\"bitrix\", PortalViewSet) router.register(\"waweb\", EventsHandler, basename=\"waevents\") router.register(\"telegram\", TelegramEventsHandler, basename=\"tgevents\")   app_name = \"api\" urlpatterns = router.urls<\/code><\/pre>\n<p>\u0412 <strong>urls.py<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c:<\/p>\n<pre><code class=\"python\">urlpatterns += [     path(\"api\/\", include(\"core.api_router\")), # core \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0433\u0434\u0435 \u043b\u0435\u0436\u0438\u0442 \u044f\u0434\u0440\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430   ]<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b<strong> utils.py<\/strong> \u0441 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"python\">import base64 import json import logging import re import redis  import requests from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.contrib import messages from django.conf import settings from django.shortcuts import redirect, get_object_or_404  from rest_framework import status from rest_framework.authtoken.models import Token from rest_framework.response import Response from django.http import HttpResponse  from waweb.models import Session import waweb.utils as waweb import waweb.tasks as waweb_tasks import telegram.tasks as telegram_tasks  from .crest import call_method from .models import App, AppInstance, Bitrix, Line, Connector import bitrix.tasks as bitrix_tasks  from telegram.models import TelegramBot  redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=6379, db=0)   logger = logging.getLogger(\"django\")  GENERAL_EVENTS = [     \"ONAPPUNINSTALL\", ]  CONNECTOR_EVENTS = [     \"ONIMCONNECTORMESSAGEADD\",     \"ONIMCONNECTORLINEDELETE\",     \"ONIMCONNECTORSTATUSDELETE\", ] <\/code><\/pre>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043b\u0438\u043d\u0438\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 2 \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0431\u043b\u043e\u043a\u0430: \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u043b\u0438\u043d\u0438\u0438. \u0415\u0441\u043b\u0438 id \u043b\u0438\u043d\u0438\u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 create, \u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u043b\u0438\u043d\u0438\u044e \u0438 AppInstance. \u0414\u0430\u043b\u0435\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043b\u0438\u043d\u0438\u0438 (<strong>imopenlines.config.add<\/strong>), \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c AppInstance \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u0439 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043b\u0438\u043d\u0438\u0438 \u0438 \u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u043c \u0435\u0435, \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0432\u0430\u044f \u0438\u043c\u0435\u0435\u0442\u0441\u044f. \u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u043c \u0448\u0430\u0433\u043e\u043c \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0448\u0443 \u043e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u043b\u0438\u043d\u0438\u044e \u0432 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0435 \u043c\u0435\u0442\u043e\u0434\u043e\u043c <strong>imconnector.activate <\/strong>\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"python\">def connect_line(request, line_id, entity, connector, redirect_to):     line_id = str(line_id)     if line_id.startswith(\"create__\"):         instance_id = line_id.split(\"__\")[1]         app_instance = get_object_or_404(AppInstance, id=instance_id, owner=request.user)         if not app_instance.portal:             messages.error(request, \"\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043b\u0438\u043d\u0438\u044e: \u043f\u043e\u0440\u0442\u0430\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\")             return redirect(redirect_to)         if entity.line:             call_method(app_instance, \"imconnector.activate\", {                 \"CONNECTOR\": connector.code,                 \"LINE\": entity.line.line_id,                 \"ACTIVE\": 0,             })         if connector.service == \"telegram\":             line_name = entity.bot_username         else:             line_name = entity.phone         create_payload = {\"PARAMS\": {\"LINE_NAME\": line_name}}         result = call_method(app_instance, \"imopenlines.config.add\", create_payload)         if result and result.get(\"result\"):             new_line_id = result[\"result\"]             line = Line.objects.create(                 line_id=new_line_id,                 portal=app_instance.portal,                 connector=connector,                 app_instance=app_instance,                 owner=request.user             )             entity.line = line             entity.app_instance = app_instance             entity.save()              activate_payload = {                 \"CONNECTOR\": connector.code,                 \"LINE\": new_line_id,                 \"ACTIVE\": 1,             }             call_method(app_instance, \"imconnector.activate\", activate_payload)             messages.success(request, f\"\u0421\u043e\u0437\u0434\u0430\u043d\u0430 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043b\u0438\u043d\u0438\u044f {new_line_id}\")         else:             messages.error(request, f\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043b\u0438\u043d\u0438\u0438: {result}\")         return redirect(redirect_to)     else:         line = get_object_or_404(Line, id=line_id)         if not line:             messages.error(request, f\"\u041b\u0438\u043d\u0438\u044f {line_id} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430\")             return redirect(redirect_to)         entity_model = type(entity)         usage_count = entity_model.objects.filter(line=line).exclude(pk=entity.pk).count()         if usage_count &gt; 0:             messages.error(request, \"\u042d\u0442\u0430 \u043b\u0438\u043d\u0438\u044f \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.\")             return redirect(redirect_to)          app_instance = line.app_instance          if entity.line == line:             messages.success(request, \"\u0412\u044b\u0431\u0440\u0430\u043d\u0430 \u0442\u0430 \u0436\u0435 \u043b\u0438\u043d\u0438\u044f\")             return redirect(redirect_to)         if entity.line:             call_method(app_instance, \"imconnector.activate\", {                 \"CONNECTOR\": connector.code,                 \"LINE\": entity.line.line_id,                 \"ACTIVE\": 0,             })         response = call_method(app_instance, \"imconnector.activate\", {             \"CONNECTOR\": connector.code,             \"LINE\": line.line_id,             \"ACTIVE\": 1,         })         if response.get(\"result\"):             entity.line = line             entity.app_instance = app_instance             entity.save()             messages.success(request, \"\u041b\u0438\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0430\")      messages.success(request, \"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b\")     return redirect(redirect_to)<\/code><\/pre>\n<p>\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0437 \u0441\u0435\u0431\u044f \u0432\u0441\u0435\u0433\u043e 1 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 <strong>event.bind<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c \u0430\u0434\u0440\u0435\u0441 \u043d\u0430\u0448\u0435\u0433\u043e \u0432\u0435\u0431\u0445\u0443\u043a\u0430, \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0449\u0435\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f:<\/p>\n<pre><code class=\"python\">def events_bind(events: dict, appinstance: AppInstance, api_key: str):     url = appinstance.app.site     for event in events:         payload = {             \"event\": event,             \"HANDLER\": f\"https:\/\/{url}\/api\/bitrix\/?api-key={api_key}\",         }          try:             return call_method(appinstance, \"event.bind\", payload)         except (ObjectDoesNotExist, Exception) as exc:             print(exc, \" in events_bind\")             return None<\/code><\/pre>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f 2 \u0444\u0443\u043d\u043a\u0446\u0438\u0438: \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 (<strong>register_connector<\/strong>) \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0435\u0433\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 (<strong>process_placement<\/strong>). <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 \u043c\u0435\u043d\u044e \u0431\u0438\u0442\u0440\u0438\u043a\u0441\u0430 CRM &#8212; \u041a\u043e\u043d\u0442\u0430\u043a\u0442-\u0426\u0435\u043d\u0442\u0440. \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0448\u0443 svg \u0438\u043a\u043e\u043d\u043a\u0443 \u0432 base64 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 <strong>imconnector.register<\/strong> \u0441 \u0442\u0430\u043a\u0438\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438, \u043a\u0430\u043a uuid \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430, \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u0438 \u0435\u0433\u043e \u0438\u043a\u043e\u043d\u043a\u0430 \u0432 base64. \u041f\u043e\u0441\u043b\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 \u044d\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0432 \u041a\u043e\u043d\u0442\u0430\u043a\u0442-\u0426\u0435\u043d\u0442\u0440\u0435.<\/p>\n<p>\u0427\u0442\u043e \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438, \u0435\u0433\u043e \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0432\u043e\u0441\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0432\u0435\u0431\u0445\u0443\u043a \u043e\u0442 \u0431\u0438\u0442\u0440\u0438\u043a\u0441\u0430. \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043b\u0438\u043d\u0438\u0438, uuid \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0430\u0434\u0440\u0435\u0441 \u043f\u043e\u0440\u0442\u0430\u043b\u0430 \u0411\u0438\u0442\u0440\u0438\u043a\u0441. \u0415\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b, \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u041e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u041b\u0438\u043d\u0438\u044e \u043a \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c HANDLER \u0434\u043b\u044f \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"python\">def register_connector(appinstance: AppInstance, api_key: str, connector):     url = appinstance.app.site      if not connector.icon:         return None      try:         with open(connector.icon.path, \"rb\") as file:             image_data = file.read()             encoded_image = base64.b64encode(image_data).decode(\"utf-8\")             connector_logo = f\"data:image\/svg+xml;base64,{encoded_image}\"          payload = {             \"ID\": connector.code,             \"NAME\": connector.name,             \"ICON\": {                 \"DATA_IMAGE\": connector_logo,             },             \"PLACEMENT_HANDLER\": f\"https:\/\/{url}\/app-settings\/?inst={appinstance.id}\",         }          try:             return call_method(appinstance, \"imconnector.register\", payload)         except (ObjectDoesNotExist, Exception) as exc:             print(exc, \" in register_connector\")             return None          events_bind(CONNECTOR_EVENTS, appinstance, api_key)      except FileNotFoundError:         return None     except Exception as e:         return None   def process_placement(request):     try:         data = request.POST         placement_options = data.get(\"PLACEMENT_OPTIONS\")         instance_id = request.GET.get(\"inst\")         domain = request.GET.get(\"DOMAIN\")         print(\"process placement GET \", request.GET)         print(\"FULL URL:\", request.build_absolute_uri())          placement_options = json.loads(placement_options)         line_id = placement_options.get(\"LINE\")         connector_code = placement_options.get(\"CONNECTOR\")          app_instance = AppInstance.objects.filter(id=instance_id).first()         if not app_instance:             return HttpResponse(\"app not found\")         portal = Bitrix.objects.filter(domain=domain).first()         if not portal:             return HttpResponse(\"bitrix not found\")         connector = Connector.objects.filter(code=connector_code).first()         if not connector:             return HttpResponse(\"connector not found\")         line, created = Line.objects.get_or_create(             line_id=line_id,             portal=portal,             connector=connector,             app_instance=app_instance,             owner=app_instance.owner         )         activate_payload = {             \"CONNECTOR\": connector.code,             \"LINE\": line_id,             \"ACTIVE\": 1,         }         print(\"activate_payload= \", activate_payload)         activate_result = call_method(app_instance, \"imconnector.activate\", activate_payload)          if not activate_result or not activate_result.get(\"result\"):             return HttpResponse(f\"Failed to activate connector: {activate_result}\")          # api_key = request.GET.get(\"api-key\")         api_key = Token.objects.get(user=app_instance.owner).key         print(\"request data= \", request.GET)         print(\"api_key=\", api_key)         print(\"app_instance=\", app_instance)         events = [\"ONIMCONNECTORMESSAGEADD\", \"ONIMCONNECTORSTATUSDELETE\", \"ONIMCONNECTORLINEDELETE\"]         for event in events:             event_payload = {                 \"event\": event,                 \"HANDLER\": f\"https:\/\/{app_instance.app.site}\/api\/bitrix\/?api-key={api_key}\",             }             call_method(app_instance, \"event.bind\", event_payload)          return HttpResponse(             f\"\u041b\u0438\u043d\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0430 \u0438 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u0430. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0434\u043e\u043f. \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432 Bitrix: https:\/\/{app_instance.app.site}\/portals\/\"         )     except Exception as e:         logger.error(f\"Unexpected error: {e}\")         return HttpResponse({\"An unexpected error occurred\"})<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432  \u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439. <strong>extract_files <\/strong>\u0438 <strong>upload_file <\/strong>\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u0441 \u0444\u0430\u0439\u043b\u0430\u043c\u0438. \u041d\u0438\u0447\u0435\u0433\u043e \u0441\u0432\u0435\u0440\u0445\u044a\u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043d\u0435 \u0437\u0430\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u044d\u0442\u043e\u043c \u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a <strong>event_processor <\/strong>&#8212; \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0435\u0439 \u0432\u0441\u0435 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043e\u0442 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0430.<\/p>\n<pre><code class=\"python\">def extract_files(data):     files = []     i = 0     while True:         name_key = f\"data[MESSAGES][0][message][files][{i}][name]\"         link_key = f\"data[MESSAGES][0][message][files][{i}][link]\"         type_key = f\"data[MESSAGES][0][message][files][{i}][type]\"          if name_key in data and link_key in data:             files.append(                 {                     \"name\": data.get(name_key),                     \"link\": data.get(link_key),                     \"type\": data.get(type_key),                 },             )             i += 1         else:             break      return files   def upload_file(appinstance, storage_id, fileContent, filename):     payload = {         \"id\": storage_id,         \"fileContent\": fileContent,         \"data\": {\"NAME\": filename},         \"generateUniqueName\": True,     }     upload_to_bitrix = call_method(appinstance, \"disk.folder.uploadfile\", payload)     if \"result\" in upload_to_bitrix:         return upload_to_bitrix[\"result\"]     else:         return None<\/code><\/pre>\n<p><strong>event_processor<\/strong> &#8212; \u044d\u0442\u043e \u0435\u0434\u0438\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432\u0441\u0435\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u043e\u0442 Bitrix24 \u043f\u043e REST-\u0445\u0443\u043a\u0430\u043c. \u041e\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0438\u0437 \u043d\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0435 (\u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u0442\u043e\u043a\u0435\u043d\u044b, \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e\u0440\u0442\u0430\u043b\u0430) \u0438 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u0438\u043f\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0443:<\/p>\n<ul>\n<li>\n<p>\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u0440\u0442\u0430\u043b\u0430 \u043f\u0440\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435,<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u043e\u0432\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439,<\/p>\n<\/li>\n<li>\n<p>\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043b\u0438\u043d\u0438\u0438,<\/p>\n<\/li>\n<li>\n<p>\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0431\u043e\u043b\u0435\u0435 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e.<\/p>\n<h3>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (ONAPPINSTALL)<\/h3>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0432\u043f\u0435\u0440\u0432\u044b\u0435 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0441\u0432\u043e\u0439 \u043f\u043e\u0440\u0442\u0430\u043b Bitrix24, \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 <strong>ONAPPINSTALL<\/strong>.<\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044f:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043b\u0438 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (AppInstance).<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043d\u0435\u0442 \u2014 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043d\u043e\u0432\u044b\u0439 \u043f\u043e\u0440\u0442\u0430\u043b (Bitrix) \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 \u043a \u043d\u0435\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0432\u044b\u0434\u0430\u043d\u043d\u044b\u0435 Bitrix24 \u0442\u043e\u043a\u0435\u043d\u044b (access_token, refresh_token, application_token).<\/p>\n<\/li>\n<li>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u044f (<strong>event.bind<\/strong>) \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u044b (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, WhatsApp \u0438\u043b\u0438 Telegram).<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0432 Bitrix \u0414\u0438\u0441\u043a\u0435 (<strong>disk.storage.getforapp<\/strong>).<\/p>\n<\/li>\n<li>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0443\u0436\u0435 \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0433\u043e\u0442\u043e\u0432\u043e \u043a \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u0435.<\/p>\n<h3>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 (ONIMCONNECTORMESSAGEADD)<\/h3>\n<p>\u041e\u0434\u043d\u043e \u0438\u0437 \u0441\u0430\u043c\u044b\u0445 \u0447\u0430\u0441\u0442\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u2014 \u044d\u0442\u043e \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430, \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0435\u0435 \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u043b\u0438\u043d\u0438\u044e.<\/p>\n<p>\u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0440\u0430\u0431\u043e\u0442\u044b:<\/p>\n<ol>\n<li>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f, \u043a\u0430\u043a\u043e\u0439 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b (WhatsApp Web \u0438\u043b\u0438 Telegram).<\/p>\n<\/li>\n<li>\n<p>\u0418\u0437\u0432\u043b\u0435\u043a\u0430\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u043b\u0438\u043d\u0438\u0438 (LINE), \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0438 (MESSAGE_ID, CHAT_ID) \u0438 \u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c.<\/p>\n<\/li>\n<li>\n<p>Bitrix \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043e \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0435 \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0442\u043e\u0434 <strong>imconnector.send.status.delivery<\/strong>.<\/p>\n<\/li>\n<li>\n<p>\u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043d\u0435 \u0431\u044b\u043b\u043e \u043b\u0438 \u044d\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u0441\u0430\u043c\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0435\u0439 (\u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 &#171;\u043f\u0435\u0442\u043b\u0438&#187; \u0447\u0435\u0440\u0435\u0437 Redis).<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2014 \u043e\u043d\u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u044e\u0442\u0441\u044f.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 WhatsApp \u0438\u043b\u0438 Telegram:<\/p>\n<ul>\n<li>\n<p>\u0434\u043b\u044f WhatsApp \u2014 \u0447\u0435\u0440\u0435\u0437 waweb-\u0441\u0435\u0441\u0441\u0438\u044e,<\/p>\n<\/li>\n<li>\n<p>\u0434\u043b\u044f Telegram \u2014 \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u0431\u043e\u0442\u0430.<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u0430 \u2014 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u043c \u00ab\u043c\u043e\u0441\u0442\u043e\u043c\u00bb \u043c\u0435\u0436\u0434\u0443 Bitrix24 \u0438 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u043e\u043c.<\/p>\n<h3>\u041e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043b\u0438\u043d\u0438\u0438 (ONIMCONNECTORSTATUSDELETE)<\/h3>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0432 Bitrix24 \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043b\u0438\u043d\u0438\u044f, \u0441\u043e\u0431\u044b\u0442\u0438\u0435 <strong>ONIMCONNECTORSTATUSDELETE <\/strong>\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u0435\u043d.<\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442:<\/p>\n<ul>\n<li>\n<p>\u043f\u043e\u0438\u0441\u043a \u043b\u0438\u043d\u0438\u0438 \u043f\u043e ID,<\/p>\n<\/li>\n<li>\n<p>\u043e\u0442\u0432\u044f\u0437\u043a\u0430 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 WhatsApp),<\/p>\n<\/li>\n<li>\n<p>\u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043b\u0438\u043d\u0438\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0430.<\/p>\n<\/li>\n<\/ul>\n<h3>\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043b\u0438\u043d\u0438\u0438 (ONIMCONNECTORLINEDELETE)<\/h3>\n<p>\u0415\u0441\u043b\u0438 \u043b\u0438\u043d\u0438\u044f \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Bitrix24, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f.<\/p>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a:<\/p>\n<ul>\n<li>\n<p>\u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u043b\u0438\u043d\u0438\u044e \u0432 \u0431\u0430\u0437\u0435,<\/p>\n<\/li>\n<li>\n<p>\u0443\u0434\u0430\u043b\u044f\u0435\u0442 \u0435\u0451,<\/p>\n<\/li>\n<li>\n<p>\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<\/ul>\n<h3>\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (ONAPPUNINSTALL)<\/h3>\n<p>\u041f\u0440\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0437 Bitrix24 (<strong>ONAPPUNINSTALL<\/strong>):<\/p>\n<ol>\n<li>\n<p>\u0423\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f AppInstance (\u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u0440\u0442\u0430\u043b\u0430).<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u043f\u043e\u0440\u0442\u0430\u043b\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u0442 \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u2014 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f \u0438 \u0441\u0430\u043c \u043f\u043e\u0440\u0442\u0430\u043b.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">def event_processor(request):     try:         data = request.data         event = data.get(\"event\")         domain = data.get(\"auth[domain]\")         user_id = data.get(\"auth[user_id]\")         auth_status = data.get(\"auth[status]\")         access_token = data.get(\"auth[access_token]\")         refresh_token = data.get(\"auth[refresh_token]\")         application_token = data.get(\"auth[application_token]\")         member_id = data.get(\"auth[member_id]\")         api_key = request.query_params.get(\"api-key\")         app_id = request.query_params.get(\"app-id\")          print(data)          try:             if not api_key:                 raise ValueError(\"API key is required\")              appinstance = AppInstance.objects.get(application_token=application_token)             appinstance.access_token = access_token             appinstance.refresh_token = refresh_token             appinstance.save()              if not appinstance.portal.member_id:                 appinstance.portal.member_id = member_id                 appinstance.portal.save()          except AppInstance.DoesNotExist:             if event == \"ONAPPINSTALL\":                 try:                     app = App.objects.get(id=app_id)                 except App.DoesNotExist:                     return Response({\"message\": \"App not found.\"})                  portal, created = Bitrix.objects.get_or_create(                     member_id=member_id,                     defaults={                         \"domain\": domain,                         \"user_id\": user_id,                         \"owner\": request.user if auth_status == \"L\" else None,                     }                 )                  appinstance_owner = (                     portal.owner                     if portal.owner                     else (request.user if auth_status == \"L\" else None)                 )                  appinstance, created = AppInstance.objects.update_or_create(                     app=app,                     portal=portal,                     owner=appinstance_owner,                     defaults={                         \"auth_status\": auth_status,                         \"access_token\": access_token,                         \"refresh_token\": refresh_token,                         \"application_token\": application_token,                     }                 )                  storage_data = call_method(appinstance, \"disk.storage.getforapp\", {})                 if \"result\" in storage_data:                     storage_id = storage_data[\"result\"][\"ID\"]                     appinstance.storage_id = storage_id                     appinstance.save()                  # \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430\/ \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f                 def register_events_and_connectors():                     events_bind(GENERAL_EVENTS, appinstance, api_key)                     if app.connectors.exists():                         for connector in app.connectors.all():                             register_connector(appinstance, api_key, connector)                  transaction.on_commit(register_events_and_connectors)                  if VENDOR_BITRIX_INSTANCE:                     bitrix_tasks.create_deal(appinstance.id, VENDOR_BITRIX_INSTANCE, app.name)                  if portal.owner:                     return Response('App successfully created and linked')                  return Response(                     {\"message\": \"App and portal successfully created and linked.\"},                     status=status.HTTP_201_CREATED,                 )             else:                 return Response({\"message\": \"App not found and not an install event.\"})          # \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f ONIMCONNECTORMESSAGEADD         if event == \"ONIMCONNECTORMESSAGEADD\":             connector_code = data.get(\"data[CONNECTOR]\")             connector = get_object_or_404(Connector, code=connector_code)             if not connector:                 return Response({'Connector not found'})             line_id = data.get(\"data[LINE]\")             message_id = data.get(\"data[MESSAGES][0][im][message_id]\")             chat_id = data.get(\"data[MESSAGES][0][im][chat_id]\")             chat = data.get(\"data[MESSAGES][0][chat][id]\")             status_data = {                 \"CONNECTOR\": connector_code,                 \"LINE\": line_id,                 \"MESSAGES\": [                     {                         \"im\": {                             \"chat_id\": chat_id,                             \"message_id\": message_id,                         },                     },                 ],             }              call_method(appinstance, \"imconnector.send.status.delivery\", status_data)              # \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432 \u0440\u0435\u0434\u0438\u0441 (\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 )             if redis_client.exists(f'bitrix:{domain}:{message_id}'):                 return Response({'message': 'loop message'})              file_type = data.get(\"data[MESSAGES][0][message][files][0][type]\", None)             text = data.get(\"data[MESSAGES][0][message][text]\", None)             if text:                 text = re.sub(r\"\\[(?!(br|\\n))[^\\]]+\\]\", \"\", text)                 text = text.replace(\"[br]\", \"\\n\")              files = []             print(file_type)             if file_type:                 files = extract_files(data)              if connector.service == \"waweb\":                 try:                     line = Line.objects.get(line_id=line_id, app_instance=appinstance)                     wa = Session.objects.get(line=line)                     if files:                         for file in files:                             waweb_tasks.send_message_task(str(wa.session), [chat], file, 'media')                     resp = waweb.send_message(wa.session, chat, text)                     if resp.status_code == 201:                         waweb.store_msg(resp)                 except Exception as e:                     print(f'Failed to send waweb message: {str(e)}')                     return Response({'error': f'Failed to send message: {str(e)}'})              if connector.service == \"telegram\":                 try:                     # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043b\u0438\u043d\u0438\u044e                     line = Line.objects.get(line_id=line_id, app_instance=appinstance)                     # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0431\u043e\u0442\u0430                     bot = TelegramBot.objects.get(line=line)                      # \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043c\u0435\u0434\u0438\u0430\u0444\u0430\u0439\u043b\u044b, \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c                     if files:                         for file in files:                             telegram_tasks.send_telegram_message_task(                                 bot_token=bot.bot_token,                                 recipient=chat,                                 content=file,                                 cont_type=\"media\"                             )                      # \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435                     if text:                         telegram_tasks.send_telegram_message_task(                             bot_token=bot.bot_token,                             recipient=chat,                             content=text,                             cont_type=\"string\"                         )                  except Exception as e:                     print(f'Failed to send Telegram message: {str(e)}')                     return Response({'error': f'Failed to send message: {str(e)}'})              return Response(                 {\"status\": \"ONIMCONNECTORMESSAGEADD event processed\"},                 status=status.HTTP_200_OK,             )          elif event == \"ONIMCONNECTORSTATUSDELETE\":             line_id = data.get(\"data[line]\")             connector_code = data.get(\"data[connector]\")             connector = get_object_or_404(Connector, code=connector_code)             if not connector:                 return Response({'Connector not found'})             try:                 line = Line.objects.get(line_id=line_id, app_instance=appinstance)                  if connector.service == \"waweb\":                     phone = line.wawebs.first()                     if phone:                         phone.line = None                         phone.save()                  return Response(\"Line disconnected\")              except Line.DoesNotExist:                 return Response(                     {\"status\": \"Line not found\"},                     status=status.HTTP_200_OK,                 )           elif event == \"ONIMCONNECTORLINEDELETE\":             line_id = data.get(\"data\")             try:                 line = Line.objects.filter(line_id=line_id, app_instance=appinstance).first()                 if line:                     line.delete()                 return Response({\"status\": \"Line deleted\"}, status=status.HTTP_200_OK)             except Line.DoesNotExist:                 return Response(                     {\"status\": \"Line not found\"}, status=status.HTTP_200_OK                 )          elif event == \"ONAPPUNINSTALL\":             portal = appinstance.portal             appinstance.delete()             if not AppInstance.objects.filter(portal=portal).exists():                 # portal.delete()                 return Response(f\"{appinstance} and associated portal deleted\")             else:                 return Response(f\"{appinstance} deleted\")          else:             return Response('Unsupported event')      except Exception as e:         logger.error(f\"Error occurred: {e!s}\")         return Response(             {\"error\": \"Internal server error\"},             status=status.HTTP_500_INTERNAL_SERVER_ERROR,         ) <\/code><\/pre>\n<p>\u0414\u043b\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <strong>tasks.py<\/strong>:<\/p>\n<pre><code class=\"python\">import redis from celery import shared_task import logging from django.core.exceptions import ObjectDoesNotExist from django.conf import settings  from .crest import call_method from .models import AppInstance   logger = logging.getLogger(\"django\")  redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=6379, db=0)  FROM_MARKET_FIELD = settings.FROM_MARKET_FIELD  @shared_task(bind=True, max_retries=5, default_retry_delay=5) def call_api(self, id, method, payload):     try:         appinstance = AppInstance.objects.get(id=id)         return call_method(appinstance, method, payload)     except (ObjectDoesNotExist, Exception) as exc:         raise self.retry(exc=exc)   @shared_task def get_app_info():     app_instances = AppInstance.objects.all()     for app_instance in app_instances:         if app_instance.attempts &lt; settings.BITRIX_CHECK_APP_ATTEMTS:             call_api(app_instance.id, \"app.info\", {})   @shared_task(bind=True, max_retries=5, default_retry_delay=5) def send_messages(self, app_instance_id, user_phone, text, connector,                   line, sms=False, pushName=None,                   message_id=None, attachments=None, profilepic_url=None,                   chat_id=None, chat_url=None, user_id=None):     init_message = \"System: initiation message.\"     try:         if not app_instance_id:             raise ValueError(\"app_instance_id is required and cannot be None or empty\")         app_instance = AppInstance.objects.get(id=app_instance_id)         print(pushName)         bitrix_msg = {             \"CONNECTOR\": connector,             \"LINE\": line,             \"MESSAGES\": [                 {                     \"user\": {                         \"phone\": user_phone,                         \"name\": pushName or user_phone,                         \"id\": user_id or user_phone,                         \"skip_phone_validate\": 'Y',                         # \"picture\": {                         #     \"url\": profilepic_url                         # }                     },                     \"chat\": {                         \"id\": chat_id or user_phone,                         \"url\": chat_url                     },                     \"message\": {                         \"text\": text,                         \"id\": message_id,                         \"files\": attachments or []                     }                 }             ],         }          resp = call_method(app_instance, \"imconnector.send.messages\", bitrix_msg)          result = resp.get(\"result\", {})         results = result.get(\"DATA\", {}).get(\"RESULT\", [])         for result_item in results:             chat_session = result_item.get(\"session\", {})             if chat_session:                 domain = app_instance.portal.domain                 chat_id = chat_session.get(\"CHAT_ID\")                 identity = user_id or user_phone                 redis_client.set(f\"bitrix_chat:{domain}:{line}:{identity}\", chat_id)         return resp      except Exception as e:         raise e   @shared_task(bind=True, max_retries=5, default_retry_delay=5) def message_add(self, app_instance_id, line_id, user_phone, text, connector):     try:         app_instance = AppInstance.objects.get(id=app_instance_id)     except AppInstance.DoesNotExist:         logger.error(f\"AppInstance {app_instance_id} does not exist\")         raise      domain = app_instance.portal.domain     chat_key = f'bitrix_chat:{domain}:{line_id}:{user_phone}'      if redis_client.exists(chat_key):         chat_id = redis_client.get(chat_key).decode('utf-8')         payload = {             \"DIALOG_ID\": f\"chat{chat_id}\",             \"MESSAGE\": text,         }          max_send_attempts = 3          for attempt in range(max_send_attempts):             try:                 resp = call_method(app_instance, \"im.message.add\", payload)                 message_id = resp.get(\"result\")                 print(message_id)                 redis_client.setex(f'bitrix:{domain}:{message_id}', 600, message_id)                 payload_status = {                     \"CONNECTOR\": connector,                     \"LINE\": line_id,                     \"MESSAGES\": [{                         \"im\": {                             \"chat_id\": str(chat_id),                             \"message_id\": str(message_id)                         }                     }]                 }                 delivery_status_resp = call_method(app_instance, \"imconnector.send.status.delivery\", payload_status)                 print(delivery_status_resp)                 return resp             except Exception as e:                 if attempt &gt;= max_send_attempts - 1:                     logger.error(f\"Exception occurred while sending message: {e}\")                     raise                 else:                     self.retry(exc=e)      send_messages(app_instance_id, user_phone, text, connector, line_id, True)   @shared_task def create_deal(app_instance_id, vendor_inst_id, app_name):     app_instance = AppInstance.objects.get(id=app_instance_id)     try:         user_current = call_method(app_instance, \"user.current\", {})         user_data = user_current.get(\"result\", {})         user_email = user_data.get(\"EMAIL\")     except Exception as e:         return     if not user_email:         return     user_id = None     venrot_instance = AppInstance.objects.get(id=vendor_inst_id)     # \u041f\u043e\u0438\u0441\u043a \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0430 \u0432 \u0431\u0438\u0442\u0440\u0438\u043a\u0441      payload = {         \"FILTER\": {             \"EMAIL\": user_email         },         \"select\": [FROM_MARKET_FIELD]     }     client_data = call_method(venrot_instance, \"crm.contact.list\", payload)     if \"result\" in client_data:         client_data = client_data.get(\"result\", [])         if client_data:             from_market = client_data[0].get(FROM_MARKET_FIELD)             if from_market == \"1\":                 return             user_id = client_data[0].get(\"ID\")     if not user_id:                 contact_data = {             \"fields\": {                 \"NAME\": user_data.get(\"NAME\"),                 \"LAST_NAME\": user_data.get(\"LAST_NAME\"),                 FROM_MARKET_FIELD: \"1\",                 \"EMAIL\": [                     {                         \"VALUE\": user_email,                         \"VALUE_TYPE\": \"WORK\"                     }                 ],                 \"PHONE\": [                     {                         \"VALUE\": user_data.get(\"WORK_PHONE\"),                         \"VALUE_TYPE\": \"WORK\"                     },                     {                         \"VALUE\": user_data.get(\"PERSONAL_MOBILE\"),                         \"VALUE_TYPE\": \"MOBILE\"                     }                 ]             }         }          create_contact = call_method(venrot_instance, \"crm.contact.add\", contact_data)         if \"result\" in create_contact:             user_id = create_contact.get(\"result\")     if user_id:         deal_data = {             \"fields\": {                 \"TITLE\": f\"\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f: {app_name}\",                 \"CONTACT_IDS\": [user_id],                 \"OPENED\": \"N\",             }         }         call_method(venrot_instance, \"crm.deal.add\", deal_data)<\/code><\/pre>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0431\u043e\u043b\u0435\u0435 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e:<\/p>\n<p><strong>call_api <\/strong>\u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f\u00a0\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 API Bitrix24. \u041d\u0430\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u0448 AppInstance \u043f\u043e\u00a0id \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432\u00a0call_method.<\/p>\n<p><strong>get_app_info<\/strong>  &#8212; \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Celery Beat \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0437\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e \u0442\u043e\u043a\u0435\u043d\u043e\u0432.<\/p>\n<p><strong>send_messages  <\/strong>&#8212; \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u043b\u0438\u043d\u0438\u0438. \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u043f\u0440\u0438\u0448\u0435\u0434\u0448\u0438\u0445 \u0438\u0437 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432.<\/p>\n<p><strong>message_add<\/strong>   &#8212; \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u0447\u0430\u0442\u0435 \u0411\u0438\u0442\u0440\u0438\u043a\u0441. \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 payload \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434  <strong>im.message.add<\/strong> .<\/p>\n<p><strong> create_deal<\/strong>  &#8212; \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0430\u0434\u0438\u044f, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043b\u0438\u0434\u0430 \u0432 CRM \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0447\u0430\u0442\u0430.<\/p>\n<p>\u0412\u044b\u0437\u043e\u0432 api Bitrix \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 <strong>crest.py. <\/strong>\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u044d\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a \u0411\u0438\u0442\u0440\u0438\u043a\u0441, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0435\u0433\u043e, \u0430 \u043d\u0443\u0436\u043d\u043e \u043b\u0438\u0448\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u043c\u0435\u0442\u043e\u0434. \u041f\u0435\u0440\u0435\u0434\u0430\u044e \u043f\u0440\u0438\u0432\u0435\u0442 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c DRY \ud83d\ude42<\/p>\n<pre><code class=\"python\">from urllib.parse import urlparse import logging  import requests from django.db import transaction from django.conf import settings from waweb.tasks import send_message_task from users.models import Message  from .models import AppInstance  logger = logging.getLogger(\"django\")   def call_method(appinstance: AppInstance, b24_method: str, data: dict, attempted_refresh=False, verify=True):     portal = appinstance.portal     endpoint = f\"{portal.protocol}:\/\/{portal.domain}\/rest\/\"     access_token = appinstance.access_token     print(b24_method)      payload = {\"auth\": access_token, **data}     print(payload)     try:         response = requests.post(f\"{endpoint}{b24_method}\", json=payload,                                 allow_redirects=False, timeout=60, verify=verify)         print(response.json())         appinstance.status = response.status_code     except requests.exceptions.SSLError:         if verify:             return call_method(appinstance, b24_method, data, attempted_refresh, verify=False)         else:             raise      if response.status_code == 302 and not attempted_refresh:         new_url = response.headers['Location']         parsed_url = urlparse(new_url)         portal = appinstance.portal         domain = parsed_url.netloc         if portal.domain != domain:             portal.domain = domain             portal.save()         appinstance.attempts = 0         appinstance.save()         return call_method(appinstance, b24_method, data, attempted_refresh=True)      elif response.status_code == 200:         appinstance.attempts = 0         appinstance.save()         return response.json()      else:         appinstance.attempts += 1         appinstance.save()         if response.status_code == 401:             resp = response.json()             error = resp.get(\"error\", \"\")             error_description = resp.get(\"error_description\", \"\")             if \"REST is available only on commercial plans\" in error_description and not appinstance.portal.license_expired:                 appinstance.portal.license_expired = True                 appinstance.portal.save()                 waweb_id = settings.WAWEB_SYTEM_ID                 if waweb_id and appinstance.owner.phone_number:                     try:                         notification = Message.objects.get(code=\"b24_expired\")                         send_message_task(waweb_id, [str(appinstance.owner.phone_number)], notification.message)                     except Message.DoesNotExist:                         pass                 raise Exception(\"b24 license expired\")                          if error == \"expired_token\" or error == \"NO_AUTH_FOUND\" and not attempted_refresh:                 refreshed = refresh_token(appinstance)                 if isinstance(refreshed, AppInstance):                     return call_method(appinstance, b24_method, data, attempted_refresh=True)                 else:                     raise Exception(f\"Token refresh failed for portal {appinstance.portal.domain}\")             else:                 raise Exception(f\"Unauthorized error: {response.json()}\")          raise Exception(f\"Failed to call bitrix: {appinstance.portal.domain} \"                         f\"status {response.status_code}, response: {response.json()}\")   def refresh_token(appinstance: AppInstance):     payload = {         \"grant_type\": \"refresh_token\",         \"client_id\": appinstance.app.client_id,         \"client_secret\": appinstance.app.client_secret,         \"refresh_token\": appinstance.refresh_token,     }     response = requests.post(\"https:\/\/oauth.bitrix.info\/oauth\/token\/\", data=payload, timeout=60)     print(appinstance.refresh_token)     print(appinstance.access_token)     try:         response_data = response.json()     except Exception:         raise Exception(f\"Invalid response while refreshing token for portal {appinstance.portal.domain}\")      if response.status_code != 200:         raise Exception(f\"Failed to refresh token: {appinstance.portal.domain} {response_data}\")      appinstance.access_token = response_data[\"access_token\"]     appinstance.refresh_token = response_data[\"refresh_token\"]     with transaction.atomic():         appinstance.save(update_fields=[\"access_token\", \"refresh_token\"])     return appinstance <\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432\u0435\u0431\u0445\u0443\u043a\u0438 \u0432 <strong>views.py<\/strong> (\u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 install_finish.html)<\/p>\n<pre><code class=\"python\">import uuid import requests from datetime import timedelta  from django.conf import settings from django.contrib import messages from django.shortcuts import render, redirect from django.utils import timezone from django.http import HttpResponse, HttpResponseForbidden from django.views.decorators.csrf import csrf_exempt from rest_framework.authtoken.models import Token  from .crest import call_method from .utils import process_placement from .models import AppInstance, Bitrix, Line, App  from django.contrib.auth import get_user_model, login, logout User = get_user_model()  @csrf_exempt def app_install(request):     if request.method == \"HEAD\":         return HttpResponse(\"ok\")      app_id = request.GET.get(\"app-id\")     protocol = request.GET.get(\"PROTOCOL\")     domain = request.GET.get(\"DOMAIN\")     data = request.POST      member_id = data.get(\"member_id\")     auth_id = data.get(\"AUTH_ID\")      if not app_id or not member_id or not domain or not auth_id:         return redirect(\"portals\")      try:         app = App.objects.get(id=app_id)     except App.DoesNotExist:         return redirect(\"portals\")      proto = \"https\" if protocol == \"1\" else \"http\"     owner = get_owner(request)     api_key, _ = Token.objects.get_or_create(user=app.owner)      payload = {         \"event\": \"ONAPPINSTALL\",         \"HANDLER\": f\"https:\/\/{app.site}\/api\/bitrix\/?api-key={api_key.key}&amp;app-id={app_id}\",         \"auth\": auth_id,     }      try:         response = requests.post(f\"{proto}:\/\/{domain}\/rest\/event.bind\", json=payload, timeout=60)         response.raise_for_status()     except requests.RequestException as e:         resp = response.json()         error_description = resp.get(\"error_description\")         if \"Handler already binded\" in error_description:             return render(request, \"install_finish.html\")         else:             return HttpResponse(f\"Bitrix event.bind failed {response.status_code, resp}\")      return render(request, \"install_finish.html\")   @csrf_exempt def app_settings(request):     if request.method == \"POST\":         try:             app_id = request.GET.get(\"app-id\")             data = request.POST             domain = request.GET.get(\"DOMAIN\")             member_id = data.get(\"member_id\")             portal = Bitrix.objects.get(domain=domain, member_id=member_id)         except Exception as e:             return redirect(\"portals\")          placement = data.get(\"PLACEMENT\")         if placement == \"SETTING_CONNECTOR\":             return process_placement(request)          elif placement == \"DEFAULT\":             try:                 app = App.objects.get(id=app_id)             except Exception:                 return redirect(\"portals\")             app_url = app.page_url             owner = get_owner(request)              if owner is None:                 logout(request)                 return redirect(app_url)              should_login = not request.user.is_authenticated or request.user != owner             if should_login:                 if request.user.is_authenticated:                     logout(request)                 try:                     login(request, owner, backend='django.contrib.auth.backends.ModelBackend')                 except Exception:                     return redirect(app_url)              AppInstance.objects.filter(portal=portal, owner__isnull=True).update(owner=owner)             Line.objects.filter(portal=portal, owner__isnull=True).update(owner=owner)             return redirect(f\"{app_url}?domain={domain}\")         else:             return redirect(\"portals\")     elif request.method == \"HEAD\":         return HttpResponse(\"ok\")     elif request.method == \"GET\":         return redirect(\"portals\")<\/code><\/pre>\n<p>\u0410 \u0442\u0430\u043a\u0436\u0435 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b \u0432 urls.py (\u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432 urls \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430):<\/p>\n<pre><code class=\"python\">from django.urls import path  from .views import app_settings, app_install  urlpatterns = [     path(\"app-settings\/\", app_settings, name=\"app_settings\"),     path(\"app-install\/\", app_install, name=\"app_install\"), ]<\/code><\/pre>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043c\u043e\u0434\u0443\u043b\u044f \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d. \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b WhatsApp \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0447\u0430\u0441\u0442\u0438.<\/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\/943596\/\"> https:\/\/habr.com\/ru\/articles\/943596\/<\/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, \u043c\u0438\u0440! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041f\u0430\u0432\u0435\u043b, \u044f IT \u0438\u043d\u0436\u0435\u043d\u0435\u0440 \u0438 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c \u0441\u043b\u0443\u0436\u0431\u044b \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438.<\/p>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u044f \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 \u043a\u0440\u0443\u043f\u043d\u043e\u0433\u043e IT-\u0430\u0443\u0442\u0441\u043e\u0440\u0441\u0438\u043d\u0433\u0430, \u043c\u044b \u0432 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439: \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u0449\u0435\u0433\u043e WhatsApp\/Telegram Web, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u043e\u0433\u043e \u043d\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430\u0445 \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u043e\u0432 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438, \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u043c. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u043b \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0434\u0438\u0430\u043b\u043e\u0433\u043e\u0432, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u044f\u043b \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0439 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0432 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0442\u0438\u043a\u0435\u0442\u044b, \u0432\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u0435 \u0447\u0435\u0433\u043e \u0431\u044b\u043b\u0430 \u043d\u0430\u0447\u0430\u0442\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430 \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0430\u043b\u0443 <strong>Bitrix24<\/strong>.<\/p>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0431\u044b \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u0441\u043d\u043e\u0432\u0430\u043c\u0438 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043e\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430.  \u0414\u0435\u0442\u0430\u043b\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 &#8212; \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u0435\u0440\u0441\u0442\u043a\u0443 html \u0441\u0442\u0440\u0430\u043d\u0438\u0446, \u0442\u0435\u0441\u0442\u044b, \u043a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u0430\u043d\u0435\u043b\u0438 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 \u0438 \u0442\u0430\u043a \u0434\u0430\u043b\u0435\u0435 &#8212; \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e \u043d\u0430 \u0432\u0430\u0448\u0435 \u0443\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0438\u0435.<\/p>\n<h2>\u0422\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0442\u0435\u043a<\/h2>\n<p> \u041e\u0441\u043d\u043e\u0432\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 <strong>Python <\/strong>\u0438 <strong>Django<\/strong>, \u0447\u0442\u043e \u043e\u0431\u0443\u0441\u043b\u043e\u0432\u043b\u0435\u043d\u043e \u0438\u0445 \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u043e\u0439, \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c\u044e \u0438 \u0448\u0438\u0440\u043e\u043a\u0438\u043c \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439. \u0422\u0430\u043a\u043e\u0439 \u0432\u044b\u0431\u043e\u0440 \u0434\u0430\u043b \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0436\u0438\u0437\u043d\u0435\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u044b\u0439 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 (MVP) \u0438 \u0437\u0430\u043b\u043e\u0436\u0438\u0442\u044c \u0444\u0443\u043d\u0434\u0430\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0433\u043e \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c redis, celery  \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u0445.<\/p>\n<h2>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b Bitrix<\/h2>\n<p>\u041e\u043f\u0443\u0441\u0442\u0438\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 Django \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0443 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u043e\u043c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c Django \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 bitrix. \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0432 <strong>models.py<\/strong>:<\/p>\n<pre><code class=\"python\">import uuid from django.conf import settings from django.contrib.sites.models import Site from django.db import models<\/code><\/pre>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043e \u043f\u043e\u0440\u0442\u0430\u043b\u0435:<\/p>\n<pre><code class=\"python\">class Bitrix(models.Model):     PROTOCOL_CHOICES = [         ('http', 'HTTP'),         ('https', 'HTTPS'),     ]     protocol = models.CharField(max_length=5, choices=PROTOCOL_CHOICES, default='https')     domain = models.CharField(max_length=255)     owner = models.ForeignKey(         User, on_delete=models.SET_NULL, blank=True, null=True     )     user_id = models.CharField(max_length=255, blank=True, null=True)     member_id = models.CharField(max_length=255, unique=True, blank=True, null=True)     license_expired = models.BooleanField(default=False)      def __str__(self):         return self.domain<\/code><\/pre>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440\u0430:<\/p>\n<pre><code class=\"python\">class Connector(models.Model):     TYPE_CHOICES = [         ('telegram', 'Telegram Bot'),         ('waweb', 'WhatsApp Web'),     ]     code = models.CharField(max_length=255, default=uuid.uuid4(), unique=True)     service = models.CharField(max_length=255, choices=TYPE_CHOICES, blank=True, null=True)     name = models.CharField(max_length=255, default=\"itsource.kg\", unique=False)     icon = models.FileField(upload_to='connector_icons\/', blank=True, null=True,                             default='connector_icons\/cloud-rain-alt.svg') # \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u0432\u0430\u0448\u0443 svg \u0438\u043a\u043e\u043d\u043a\u0443      def __str__(self):         return self.name<\/code><\/pre>\n<p>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0411\u0438\u0442\u0440\u0438\u043a\u0441:<\/p>\n<pre><code class=\"python\">class App(models.Model):     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)     owner = models.ForeignKey(         settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True     )     site = models.ForeignKey(         Site, on_delete=models.SET_NULL, related_name=\"apps\", blank=True, null=True     )     name = models.CharField(max_length=255, blank=True, unique=False)     page_url = models.CharField(max_length=255, blank=True, default=\"\/\")     connectors = models.ManyToManyField(Connector, blank=True, related_name='apps')     asterisk = models.BooleanField(default=False, help_text=\"Chek for Asterisk connector\")     client_id = models.CharField(max_length=255, blank=True, unique=False)     client_secret = models.CharField(max_length=255, blank=True)      def __str__(self):         return self.name<\/code><\/pre>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043b\u0438\u043d\u0438\u0439:<\/p>\n<pre><code class=\"python\">class AppInstance(models.Model):     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)     owner = models.ForeignKey(         settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True     )     app = models.ForeignKey(App, on_delete=models.SET_NULL, related_name=\"installations\", blank=True, null=True)     portal = models.ForeignKey(         Bitrix, on_delete=models.CASCADE, related_name=\"installations\", blank=True, null=True     )     auth_status = models.CharField(max_length=1)     application_token = models.CharField(max_length=255, blank=True)     storage_id = models.CharField(max_length=255, blank=True)     status = models.IntegerField(default=0, blank=True)     attempts = models.IntegerField(default=0, blank=True)     access_token = models.CharField(max_length=255, blank=True, null=True, editable=False)     refresh_token = models.CharField(max_length=255, blank=True, null=True, editable=False)      def __str__(self):         return f\"{self.app.name} on {self.portal.domain}\"   class Line(models.Model):     line_id = models.CharField(max_length=50)     owner = models.ForeignKey(         settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True     )     app_instance = models.ForeignKey(AppInstance, on_delete=models.CASCADE, related_name=\"lines\", null=True)     connector = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name=\"lines\", null=True)     portal = models.ForeignKey(Bitrix, on_delete=models.CASCADE, related_name=\"lines\", blank=True, null=True)      def __str__(self):         return f\"Line {self.line_id}\"<\/code><\/pre>\n<p>\u042f \u043f\u0440\u0438\u0432\u0435\u0440\u0436\u0435\u043d\u0435\u0446 \u043c\u043e\u0434\u0443\u043b\u044c\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c api \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f bitrix \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 api \u0432\u0435\u0431\u0445\u0443\u043a\u043e\u0432<\/p>\n<p><strong>api\/serializers.py<\/strong><\/p>\n<pre><code class=\"python\">from rest_framework import serializers  from bitrix.models import Bitrix   class PortalSerializer(serializers.ModelSerializer):     class Meta:         model = Bitrix         fields = [             \"owner\",             \"user_id\",             \"domain\",         ]      def create(self, validated_data):         return Bitrix.objects.create(**validated_data) <\/code><\/pre>\n<p><strong>api\/views.py<\/strong><\/p>\n<pre><code class=\"python\">from rest_framework.mixins import CreateModelMixin from rest_framework.renderers import JSONRenderer from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response  from bitrix.models import Bitrix  from bitrix.utils import event_processor from .serializers import PortalSerializer   class PortalViewSet(CreateModelMixin, GenericViewSet):     queryset = Bitrix.objects.all()     serializer_class = PortalSerializer      def create(self, request, *args, **kwargs):         print(\"create func\")         return event_processor(request)      def head(self, request, *args, **kwargs):         print(\"head func\")         return Response(headers={'Allow': 'POST, HEAD'}) <\/code><\/pre>\n<p>\u0412 <strong>settings.py <\/strong>\u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0441 \u044f\u0434\u0440\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u043a\u043b\u044e\u0447\u0438\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e \u0442\u043e\u043a\u0435\u043d\u0430\u043c<\/p>\n<pre><code class=\"python\">REST_FRAMEWORK = {     \"DEFAULT_AUTHENTICATION_CLASSES\": (         \"rest_framework.authentication.SessionAuthentication\",         \"rest_framework.authentication.TokenAuthentication\",         \"core.qpta.QueryParamTokenAuthentication\",     ),     \"DEFAULT_PERMISSION_CLASSES\": (\"rest_framework.permissions.IsAuthenticated\",),     'DEFAULT_RENDERER_CLASSES': ('rest_framework.renderers.JSONRenderer',) }<\/code><\/pre>\n<p>\u0422\u0430\u043c \u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <strong>qpta.py<\/strong>:<\/p>\n<pre><code class=\"python\">from rest_framework.authentication import TokenAuthentication   class QueryParamTokenAuthentication(TokenAuthentication):     def authenticate(self, request):         # Try to get the token from the URL query parameter         token = request.query_params.get(\"api-key\")          if not token:             # Fall back to default token authentication             return super().authenticate(request)          # Authenticate the token manually         user, token = self.authenticate_credentials(token)         return (user, token) <\/code><\/pre>\n<p>\u0418<strong> api_router.py<\/strong>:<\/p>\n<pre><code class=\"python\">from django.conf import settings from rest_framework.routers import DefaultRouter from rest_framework.routers import SimpleRouter  from bitrix.api.views import PortalViewSet from users.api.views import UserViewSet from waweb.api.views import EventsHandler from telegram.api.views import TelegramEventsHandler  router = DefaultRouter() if settings.DEBUG else SimpleRouter()  router.register(\"users\", UserViewSet) router.register(\"bitrix\", PortalViewSet) router.register(\"waweb\", EventsHandler, basename=\"waevents\") router.register(\"telegram\", TelegramEventsHandler, basename=\"tgevents\")   app_name = \"api\" urlpatterns = router.urls<\/code><\/pre>\n<p>\u0412 <strong>urls.py<\/strong> \u0434\u043e\u0431\u0430\u0432\u0438\u043c:<\/p>\n<pre><code class=\"python\">urlpatterns += [     path(\"api\/\", include(\"core.api_router\")), # core \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0433\u0434\u0435 \u043b\u0435\u0436\u0438\u0442 \u044f\u0434\u0440\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430   ]<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b<strong> utils.py<\/strong> \u0441 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"python\">import base64 import json import logging import re import redis  import requests from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.contrib import messages from django.conf import settings from django.shortcuts import redirect, get_object_or_404  from rest_framework import status from rest_framework.authtoken.models import Token from rest_framework.response import Response from django.http import HttpResponse  from waweb.models import Session import waweb.utils as waweb import waweb.tasks as waweb_tasks import telegram.tasks as telegram_tasks  from .crest import call_method from .models import App, AppInstance, Bitrix, Line, Connector import bitrix.tasks as bitrix_tasks  from telegram.models import TelegramBot  redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=6379, db=0)   logger = logging.getLogger(\"django\")  GENERAL_EVENTS = [     \"ONAPPUNINSTALL\", ]  CONNECTOR_EVENTS = [     \"ONIMCONNECTORMESSAGEADD\",     \"ONIMCONNECTORLINEDELETE\",     \"ONIMCONNECTORSTATUSDELETE\", ] <\/code><\/pre>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043b\u0438\u043d\u0438\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 2 \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0431\u043b\u043e\u043a\u0430: \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 \u043b\u0438\u043d\u0438\u0438. \u0415\u0441\u043b\u0438 id \u043b\u0438\u043d\u0438\u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 create, \u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u043b\u0438\u043d\u0438\u044e \u0438 AppInstance. \u0414\u0430\u043b\u0435\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0411\u0438\u0442\u0440\u0438\u043a\u0441 \u043a\u043e\u043d\u0444\u0438\u0433\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043b\u0438\u043d\u0438\u0438 (<strong>imopenlines.config.add<\/strong>), \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c AppInstance \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u0439 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043b\u0438\u043d\u0438\u0438 \u0438 \u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u043c \u0435\u0435, \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0432\u0430\u044f \u0438\u043c\u0435\u0435\u0442\u0441\u044f. \u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u043c \u0448\u0430\u0433\u043e\u043c \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0448\u0443 \u043e\u0442\u043a\u0440\u044b\u0442\u0443\u044e \u043b\u0438\u043d\u0438\u044e \u0432 \u0411\u0438\u0442\u0440\u0438\u043a\u0441\u0435 \u043c\u0435\u0442\u043e\u0434\u043e\u043c <strong>imconnector.activate <\/strong>\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"python\">def connect_line(request, line_id, entity,<\/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-473829","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/473829","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=473829"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/473829\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=473829"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=473829"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=473829"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}