From 24dd7f0af4e52088141dc82a430b2d7540363a3f Mon Sep 17 00:00:00 2001 From: pilot <657434@03b.ru> Date: Mon, 13 Apr 2026 08:07:28 +0800 Subject: [PATCH] start --- requirements.txt | 5 + restoran_max_bot/manage.py | 22 + restoran_max_bot/max_bot/__init__.py | 0 restoran_max_bot/max_bot/admin.py | 3 + restoran_max_bot/max_bot/apps.py | 6 + restoran_max_bot/max_bot/max_api.py | 21 + .../max_bot/migrations/__init__.py | 0 restoran_max_bot/max_bot/models.py | 556 ++++++++++++++++++ restoran_max_bot/max_bot/tests.py | 3 + restoran_max_bot/max_bot/urls.py | 8 + restoran_max_bot/max_bot/views.py | 65 ++ restoran_max_bot/restoran_max_bot/__init__.py | 0 restoran_max_bot/restoran_max_bot/asgi.py | 16 + restoran_max_bot/restoran_max_bot/settings.py | 122 ++++ restoran_max_bot/restoran_max_bot/urls.py | 7 + restoran_max_bot/restoran_max_bot/utils.py | 91 +++ restoran_max_bot/restoran_max_bot/wsgi.py | 16 + 17 files changed, 941 insertions(+) create mode 100644 requirements.txt create mode 100644 restoran_max_bot/manage.py create mode 100644 restoran_max_bot/max_bot/__init__.py create mode 100644 restoran_max_bot/max_bot/admin.py create mode 100644 restoran_max_bot/max_bot/apps.py create mode 100644 restoran_max_bot/max_bot/max_api.py create mode 100644 restoran_max_bot/max_bot/migrations/__init__.py create mode 100644 restoran_max_bot/max_bot/models.py create mode 100644 restoran_max_bot/max_bot/tests.py create mode 100644 restoran_max_bot/max_bot/urls.py create mode 100644 restoran_max_bot/max_bot/views.py create mode 100644 restoran_max_bot/restoran_max_bot/__init__.py create mode 100644 restoran_max_bot/restoran_max_bot/asgi.py create mode 100644 restoran_max_bot/restoran_max_bot/settings.py create mode 100644 restoran_max_bot/restoran_max_bot/urls.py create mode 100644 restoran_max_bot/restoran_max_bot/utils.py create mode 100644 restoran_max_bot/restoran_max_bot/wsgi.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3ef7a8b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django~=5.2.13 +requests +mysqlclient +qrcode +django-environ diff --git a/restoran_max_bot/manage.py b/restoran_max_bot/manage.py new file mode 100644 index 0000000..6545c1b --- /dev/null +++ b/restoran_max_bot/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'restoran_max_bot.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/restoran_max_bot/max_bot/__init__.py b/restoran_max_bot/max_bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/restoran_max_bot/max_bot/admin.py b/restoran_max_bot/max_bot/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/restoran_max_bot/max_bot/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/restoran_max_bot/max_bot/apps.py b/restoran_max_bot/max_bot/apps.py new file mode 100644 index 0000000..d44e070 --- /dev/null +++ b/restoran_max_bot/max_bot/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MaxBotConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'max_bot' diff --git a/restoran_max_bot/max_bot/max_api.py b/restoran_max_bot/max_bot/max_api.py new file mode 100644 index 0000000..995f29b --- /dev/null +++ b/restoran_max_bot/max_bot/max_api.py @@ -0,0 +1,21 @@ +import json + +import requests + + +def maxbot_send_text_message(chat_id: str, max_token: str, message: str): + url = f"https://platform-api.max.ru/messages?chat_id={chat_id}" + + payload = json.dumps({ + "text": message + + }) + headers = { + 'Authorization': f'{max_token}', + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + rt = {'success': True, 'error': '', 'data': response} + + return rt diff --git a/restoran_max_bot/max_bot/migrations/__init__.py b/restoran_max_bot/max_bot/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/restoran_max_bot/max_bot/models.py b/restoran_max_bot/max_bot/models.py new file mode 100644 index 0000000..665882a --- /dev/null +++ b/restoran_max_bot/max_bot/models.py @@ -0,0 +1,556 @@ +from django.db import models + + +# Create your models here. +class Client(models.Model): + objects = None + title = models.CharField(max_length=300, blank=True) + title_full = models.CharField(max_length=300, blank=True) + token = models.CharField(max_length=255, blank=True) + status = models.BooleanField(default=True) + params = models.TextField(null=True) + type = models.IntegerField(null=True) + manager_id = models.IntegerField(null=True) + partner = models.BooleanField(null=True) + code = models.CharField(max_length=50, verbose_name='Код клиента в таблице cdr') + brand = models.CharField(max_length=300, verbose_name='Код клиента в таблице cdr') + logo = models.CharField(max_length=300) + email = models.CharField(max_length=50, blank=True) + server = models.CharField(max_length=200, verbose_name='сервер на котором работает клиент') + phone = models.CharField(max_length=12, blank=True) + dt = models.DateTimeField(auto_now_add=True) + managment = models.CharField(max_length=300) + tz = models.CharField(max_length=50, blank=True) + balancecontrol = models.FloatField(null=True) + promised_payment = models.DateTimeField(auto_now_add=True) + details = models.TextField(null=True) + + class Meta: + db_table = 'lk_client' + + +class Billing(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + submit_fd = models.DateTimeField(auto_now_add=True, verbose_name='Время начала действия записи') + submit_td = models.DateTimeField(verbose_name='Время завершения действия записи') + balance = models.FloatField(verbose_name='Баланс пользователя', default=10) + payment = models.FloatField(default=10) + + class Meta: + db_table = 'lk_billing' + + +class BillingOrder(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + paysys = models.CharField(max_length=100) + descr = models.CharField(max_length=300) + amount = models.FloatField() + status = models.SmallIntegerField() + error = models.CharField(max_length=100) + payment_id = models.CharField(max_length=100) + payment = models.TextField() + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время начала действия записи') + updated_at = models.DateTimeField(verbose_name='Время завершения действия записи') + control_status = models.SmallIntegerField(blank=False) + + class Meta: + db_table = 'lk_billing_order' + + +class AuthAccess(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время начала действия записи') + ip_address = models.CharField(max_length=30, verbose_name='ip адрес сети') + ip_netmask = models.IntegerField(verbose_name='Маска подсети', default=32) + status = models.BooleanField(default=True, verbose_name='включен или нет') + + class Meta: + db_table = 'au_access' + + +class AuthPhone(models.Model): + phone = models.CharField(max_length=11, verbose_name='Номер телефона для аутификации') + data_last = models.DateTimeField(auto_now_add=True, verbose_name='Время последнего использования', null=True) + code_auth = models.CharField(max_length=6, verbose_name='Код аутификации', null=True) + status = models.BooleanField(default=True, verbose_name='Включение номера') + operator = models.CharField(max_length=6, verbose_name='Оператор', null=True) + server = models.CharField(max_length=6, verbose_name='ссылка на сервер', null=True) + phone_type = models.CharField(max_length=20, verbose_name='Используется для определенного типа авторизации', + null=True) + counter = models.IntegerField(verbose_name='счетчик звонков', default=0) + device_info = models.CharField(max_length=255, verbose_name='ссылка на сервер', null=True) + device_prefix = models.CharField(max_length=255, verbose_name='ссылка на сервер', null=True) + call_error = models.IntegerField(verbose_name='счетчик ошибок', default=0) + call_limit = models.IntegerField(verbose_name='лимит запросов в день', default=0) + data_last_auth = models.DateTimeField(verbose_name='Время последнего использования обратной авторизации', null=True) + user_blocking = models.BooleanField(default=True, verbose_name='Включение номера') + creation_date = models.DateTimeField(auto_now_add=True) + closing_date = models.DateTimeField(auto_now_add=False) + + class Meta: + db_table = 'au_phone' + + +class AuthLog(models.Model): + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время создание записи') + auth_phone = models.ForeignKey(AuthPhone, verbose_name='Данные аутификации', on_delete=models.CASCADE, null=True) + code_auth = models.CharField(max_length=6, verbose_name='Код аутификации', null=True) + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + status = models.IntegerField( + verbose_name='Статус выполнения запроса 0 - не выполнен, 1 - отправлен на выполнение, 2 - подтвержден', + default=0) + status_info = models.CharField(max_length=200, null=True) + status_sms = models.CharField(max_length=200, null=True) + phone = models.CharField(max_length=11, null=True, verbose_name='Номер телефона которому отправлен код дозвона') + auth_billing = models.ForeignKey(Billing, verbose_name='Сылка на билинг', on_delete=models.CASCADE, null=True) + price = models.FloatField(verbose_name='Стоимость услуги', default=0) + uid = models.CharField(max_length=100, null=True, verbose_name='Индификатор отправки сообщения') + auth_type = models.CharField(max_length=11, null=True, verbose_name='Тип авторизации') + web_hook = models.TextField(null=True) + web_hook_status = models.TextField(null=True) + sms_send = models.BooleanField(verbose_name='Отправка смс если не прошел звонок') + operator = models.CharField(max_length=250, verbose_name='Имя оператора') + + class Meta: + db_table = 'au_log' + + +class AuthPayment(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время создание записи') + partner_id = models.IntegerField(verbose_name='Данные партнера') + payment = models.FloatField(verbose_name='Сумма платежа') + source = models.CharField(verbose_name='Источник платежа', max_length=10, default='api') + info = models.CharField(verbose_name='Дополнительная информация', max_length=500, null=True) + + class Meta: + db_table = 'lk_payment' + + +class Users(models.Model): + objects = None + name = models.CharField(max_length=50, null=True) + avatar = models.CharField(max_length=300, null=True) + surname = models.CharField(max_length=50, null=True) + phone = models.CharField(max_length=11, null=True, default='89000000000', verbose_name='Номер телефона') + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + status = models.BooleanField(default=True, verbose_name='Включение номера') + role = models.IntegerField(default=2) + uid = models.CharField(max_length=50) + email = models.CharField(max_length=50) + passwd = models.CharField(max_length=100) + dt = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'lk_usr' + + +# Список контактов чате +class Contact(models.Model): + up = models.IntegerField() + group = models.IntegerField() + client = models.ForeignKey(to='Client', on_delete=models.PROTECT, verbose_name=' id client') + surname = models.CharField(max_length=100, null=True) + name = models.CharField(max_length=100, null=True) + patronymic = models.CharField(max_length=100, null=True) + phone = models.CharField(max_length=40, null=True) + telegram = models.CharField(max_length=100, null=True) + maxx = models.CharField(max_length=100, null=True) + status = models.IntegerField(default=1) + dt = models.DateTimeField(auto_now_add=True) + qr_menu = models.BooleanField(default=False) + webast_bot = models.BooleanField(default=False) + descr = models.CharField(max_length=500, null=True) + + class Meta: + db_table = 'lk_contact' + + +class CdrRecord(models.Model): + objects = None + calldate = models.DateTimeField(null=True) + src = models.CharField(max_length=80, null=True) + dst = models.CharField(max_length=80, null=True) + billsec = models.IntegerField(default=0, null=True) + accountcode = models.CharField(max_length=20, null=True) + uniqueid = models.CharField(max_length=32) + disposition = models.CharField(max_length=45) + duration = models.IntegerField(null=True) + userfield = models.CharField(max_length=32) + dstchannel = models.CharField(max_length=80) + category_call = models.CharField(max_length=50) + send_sms = models.BooleanField() + filename = models.CharField(max_length=250) + channel = models.CharField(max_length=80) + lastdata = models.TextField() + lastapp = models.TextField() + + class Meta: + db_table = 'cdr' + + +class QueueTime(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + src = models.CharField(max_length=20, verbose_name='Номер абонента') + dst = models.CharField(max_length=20, verbose_name='Номер агента') + calldate = models.DateTimeField(auto_now_add=True, verbose_name='Время записи') + billsec = models.IntegerField(verbose_name='Время ожидания', default=0) + agent_code = models.IntegerField(verbose_name='Время ожидания') + sla = models.IntegerField( + verbose_name='параметр качества обслуживания 10 - до 10 секунд, 15 - до 15 сек, 20, 30, 100 более 30 секунд', + default=0) + + class Meta: + db_table = 'lk_queue_time' + + +class SmsConfig(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + user = models.CharField(max_length=40, verbose_name='Имя пользователя') + password = models.CharField(max_length=40, verbose_name='Пароль') + url = models.CharField(max_length=255, verbose_name='Ссылка') + type = models.CharField(max_length=10, verbose_name='тип рассылки') + params = models.CharField(max_length=255, verbose_name='Ссылка') + + class Meta: + db_table = 'lk_sms_config' + + +class EmailBase(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время создание записи') + email = models.CharField(max_length=11, verbose_name='email') + mac_address = models.CharField(max_length=20, blank=True, verbose_name='mac') + description = models.CharField(max_length=50, verbose_name='Дополнительная информация') + + class Meta: + db_table = 'lk_emails' + + +class Operator(models.Model): + prefix = models.CharField(max_length=11, verbose_name='Префикс оператора сети') + operator = models.CharField(max_length=11, verbose_name='Оператор') + + class Meta: + db_table = 'au_operator' + + +class DefCode(models.Model): + code_def = models.IntegerField() + code_from = models.IntegerField() + code_to = models.IntegerField() + container = models.IntegerField() + operator = models.CharField(max_length=255, verbose_name='Оператор') + inn = models.IntegerField() + region = models.CharField(max_length=255, verbose_name='Оператор') + + class Meta: + db_table = 'au_defcode' + + +class Term(models.Model): + objects = None + up = models.IntegerField() + term = models.CharField(max_length=200) + val = models.CharField(max_length=100) + vali = models.IntegerField() + html = models.TextField() + params = models.TextField() + status = models.BooleanField() + dt = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'lk_term' + + +class App(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + name = models.CharField(max_length=255) + param = models.TextField() + descr = models.CharField(max_length=255) + type = models.CharField(max_length=100) + status = models.SmallIntegerField() + dt = models.DateTimeField(auto_now_add=True, verbose_name='Время создание записи') + update_at = models.DateTimeField(verbose_name='Время обновления записи') + app = models.CharField(max_length=255, verbose_name='приложение обработки') + + class Meta: + db_table = 'lk_app' + + +class Task(models.Model): + objects = None + app = models.ForeignKey(App, verbose_name='Сылка на приложение', on_delete=models.CASCADE) + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + name = models.CharField(max_length=255) + mess = models.TextField() + status = models.IntegerField() + prev_status = models.IntegerField() + reason = models.CharField(max_length=255) + priority = models.IntegerField() + dt_start = models.DateTimeField(auto_now_add=True, verbose_name='Время запуска приложения') + dt = models.DateTimeField(auto_now_add=True, verbose_name='Время создание записи') + + class Meta: + db_table = 'lk_task' + + +class TaskDetail(models.Model): + objects = None + task = models.ForeignKey(Task, verbose_name='Сылка на приложение', on_delete=models.CASCADE) + contact = models.ForeignKey(Contact, verbose_name='Данные пользователя', on_delete=models.CASCADE) + subject = models.CharField(max_length=255) + mess = models.TextField() + cnt = models.IntegerField() + status = models.IntegerField() + reason = models.CharField(max_length=255) + dt = models.DateTimeField(verbose_name='Время создание записи') + update_at = models.DateTimeField(verbose_name='Время обновления записи') + external_id = models.CharField(max_length=255) + + class Meta: + db_table = 'lk_task_detail' + + +class Price(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + price = models.FloatField() + description = models.CharField(max_length=255) + service = models.CharField(max_length=255) + category = models.CharField(max_length=255) + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время начала действия записи') + + class Meta: + db_table = 'lk_price' + + +class Partner(models.Model): + objects = None + name = models.CharField(max_length=100) + title = models.CharField(max_length=100) + logo = models.CharField(max_length=100) + descr = models.CharField(max_length=300) + status = models.IntegerField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + slug = models.CharField(max_length=30) + setting = models.TextField() + token_partner = models.CharField(max_length=50) + token_api = models.CharField(max_length=50) + application_id = models.IntegerField() + + class Meta: + db_table = 'lk_integration_partner' + + +class Integration(models.Model): + objects = None + client = models.ForeignKey(Client, on_delete=models.CASCADE) + partner = models.ForeignKey(Partner, on_delete=models.CASCADE) + token = models.CharField(max_length=100) + setting = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + status = models.IntegerField() + updated_at = models.DateTimeField(auto_now=True) + partner_client = models.CharField(max_length=255) + + class Meta: + db_table = 'lk_integration' + + +class SipTrunk(models.Model): + client = models.ForeignKey(Client, on_delete=models.CASCADE) + provider = models.CharField(max_length=50) + phone = models.CharField(max_length=50) + passwd = models.CharField(max_length=50) + status = models.BooleanField() + dt = models.DateTimeField(auto_now=True) + + class Meta: + db_table = 'lk_sip_trunk' + + +class Balance(models.Model): + objects = None + client = models.ForeignKey(to='Client', on_delete=models.PROTECT) + type = models.CharField(max_length=50) + accountcode = models.CharField(max_length=100) + domain = models.CharField(max_length=100) + login = models.CharField(max_length=50) + secret = models.CharField(max_length=100) + balance = models.FloatField() + status = models.IntegerField() + params = models.CharField(max_length=500) + description = models.CharField(max_length=500) + error = models.CharField(max_length=300) + dt = models.DateTimeField(auto_now_add=True) + informing_blocking = models.DateTimeField() + informing_balance = models.DateTimeField() + balance_save = models.FloatField() + + class Meta: + db_table = 'lk_balance' + + +class SipPeer(models.Model): + objects = None + client = models.ForeignKey(to='Client', on_delete=models.PROTECT) + num = models.CharField(max_length=100) + secret = models.CharField(max_length=100) + ipaddress = models.CharField(max_length=100) + ipport = models.CharField(max_length=100) + accountcode = models.CharField(max_length=100) + description = models.CharField(max_length=100) + type = models.CharField(max_length=100) + status = models.BooleanField() + op_position = models.CharField(max_length=100) + op_fio = models.CharField(max_length=100) + op_email = models.CharField(max_length=100) + dt = models.DateTimeField(auto_now_add=True) + cfg_server = models.CharField(max_length=50) + cfg_port = models.CharField(max_length=10) + operator = models.CharField(max_length=100) + control_password = models.DateTimeField() + + class Meta: + db_table = 'lk_sip_peer' + + +class BillingLog(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + price = models.ForeignKey(Price, verbose_name='Данные пользователя', on_delete=models.CASCADE, null=True) + billing = models.ForeignKey(Billing, verbose_name='Данные пользователя', on_delete=models.CASCADE, null=True) + created_at = models.DateTimeField(verbose_name='Время начала действия записи') + accruals = models.FloatField() + function_init = models.CharField(max_length=255) + description = models.CharField(max_length=255) + + class Meta: + db_table = 'lk_billing_log' + + +class SmsLog(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время создание записи') + phone = models.CharField(max_length=20, verbose_name='Номер телефона') + text = models.TextField(blank=True, verbose_name='Текст') + status = models.IntegerField(default=False, verbose_name='Статус') + status_info = models.CharField(max_length=300, blank=True, verbose_name='Информация о статусе') + uid = models.CharField(max_length=50, verbose_name='Индификатор сообщения от оператора') + price = models.FloatField() + provider = models.CharField(max_length=50, verbose_name='Индификатор сообщения от оператора') + user_answer = models.CharField(max_length=255, null=True, verbose_name='ответ на сообщение') + task = models.ForeignKey(Task, null=True, verbose_name='Данные пользователя', on_delete=models.CASCADE) + + class Meta: + db_table = 'lk_sms_log' + + +class BillingService(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Время начала действия записи') + updated_at = models.DateTimeField(auto_now=True, verbose_name='Дата проведения билинга') + price = models.ForeignKey(Price, on_delete=models.CASCADE) + status = models.IntegerField(default=True, verbose_name='Статус') + periodicity = models.CharField(max_length=255, verbose_name='Частотан начисления date, month') + app = models.ForeignKey(App, null=True, on_delete=models.CASCADE) + is_blocked = models.IntegerField(default=False, verbose_name='Статус блокировки услуги') + blocked_at = models.DateTimeField(auto_now=True, verbose_name='Дата блокировки') + + class Meta: + db_table = 'lk_billing_service' + + +class SmsSession(models.Model): + sms_log = models.ForeignKey(SmsLog, verbose_name='Данные пользователя', on_delete=models.CASCADE) + submit_fd = models.DateTimeField(auto_now_add=True, verbose_name='Время создание записи') + submit_td = models.DateTimeField(verbose_name='Время создание записи') + phone = models.CharField(max_length=11, verbose_name='Номер телефона') + app = models.ForeignKey(App, on_delete=models.CASCADE) + + class Meta: + db_table = 'lk_sms_session' + + +class SmsAnswer(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + app = models.ForeignKey(App, on_delete=models.CASCADE) + request = models.CharField(max_length=255, verbose_name='Номер телефона') + answer = models.TextField() + constantly = models.BooleanField(default=False) + + class Meta: + db_table = 'lk_sms_answer' + + +class HotelBooking(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + uid = models.IntegerField() + arrival = models.DateTimeField() + departure = models.DateTimeField() + regdate = models.DateTimeField() + upddate = models.DateTimeField() + folio = models.BigIntegerField() + name = models.CharField(max_length=100) + phone = models.CharField(max_length=11) + email = models.CharField(max_length=50) + summa = models.FloatField() + uid_bitrix = models.IntegerField() + status_booking = models.IntegerField() + contact_id = models.BigIntegerField() + + class Meta: + db_table = 'lk_hotel_booking' + + +class WP(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + host = models.CharField(max_length=100) + port = models.IntegerField() + session = models.CharField(max_length=50) + status = models.IntegerField() + created_at = models.DateTimeField() + updated_at = models.DateTimeField() + phone = models.CharField(max_length=50) + + class Meta: + db_table = 'lk_whapsapp' + + +class DialplanConfig(models.Model): + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + webhook = models.CharField(max_length=255) + tg_send_contact = models.BooleanField + tg_send_missed = models.BooleanField + + class Meta: + db_table = 'lk_sip_dialplan_config' + + +class ServerConfig(models.Model): + name = models.CharField(max_length=50) + server_type = models.CharField(max_length=10) + + class Meta: + db_table = 'lk_sip_server' + + +class MessageTemplate(models.Model): + objects = None + client = models.ForeignKey(Client, verbose_name='Данные пользователя', on_delete=models.CASCADE) + title = models.CharField(max_length=200) + type = models.CharField(max_length=100) + html = models.TextField() + params = models.JSONField() + app_id = models.IntegerField() + dt = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'vi_mess_template' diff --git a/restoran_max_bot/max_bot/tests.py b/restoran_max_bot/max_bot/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/restoran_max_bot/max_bot/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/restoran_max_bot/max_bot/urls.py b/restoran_max_bot/max_bot/urls.py new file mode 100644 index 0000000..53b2327 --- /dev/null +++ b/restoran_max_bot/max_bot/urls.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from django.urls import path + +from max_bot.views import api_start_max_v1 + +urlpatterns = [ + path('api/v1/', api_start_max_v1), +] diff --git a/restoran_max_bot/max_bot/views.py b/restoran_max_bot/max_bot/views.py new file mode 100644 index 0000000..d9a3895 --- /dev/null +++ b/restoran_max_bot/max_bot/views.py @@ -0,0 +1,65 @@ +import json +from django.http import JsonResponse +from max_bot.max_api import maxbot_send_text_message +from max_bot.models import Partner, Integration, Client +from restoran_max_bot.utils import is_json + + +def api_decorator(func): + def wrapper_api_decorator(*args, **kwargs): + # получаем информацию по токену клиента и делаем проверку + client = Client.objects.filter(token=kwargs['token']).first() + if not client: + rt = {'success': False, 'error': 'Unauthorized token', 'data': {}} + return JsonResponse(rt, status=401) + + # проверяем заблокирован ли пользователь + if not client.status: + rt = {'success': False, 'error': 'client is blocked', 'data': {}} + return JsonResponse(rt, status=401) + + # проверка на интеграцию с максом + partner = Partner.objects.filter(slug='max_bot').first() + integration = Integration.objects.filter(client=client, partner=partner, status=True).first() + if not integration: + rt = {'success': False, 'error': 'not found max bot integration', 'data': ''} + return JsonResponse(rt, status=404) + + if not is_json(integration.setting): + rt = {'success': False, 'error': 'not found max bot settings', 'data': ''} + return JsonResponse(rt, status=404) + + settings_max = json.loads(integration.setting) + + kwargs['client'] = client + kwargs['integration'] = integration + kwargs['settings_max'] = settings_max + + return func(*args, **kwargs) + + return wrapper_api_decorator + + +@api_decorator +def api_start_max_v1(request, token, **kwargs): + data = json.loads(request.body.decode('utf-8')) + print(data) + + client = kwargs['client'] + settings_max = kwargs['settings_max'] + + # получение сообщения + if data['update_type'] == 'message_created': + print(data) + chat_id = data['message']['recipient']['chat_id'] + chat_type = data['message']['recipient']['chat_type'] + text = str(data['message']['body']['text']).lower() + maxbot_send_text_message(chat_id=chat_id, max_token=settings_max['token'], message=text) + + # добавление бота в группу + if data['update_type'] == 'bot_added': + chat_id = data['chat_id'] + maxbot_send_text_message(chat_id=chat_id, max_token=settings_max['token'], message='bot_added') + + rt = {'success': True, 'error': '', 'data': ''} + return JsonResponse(rt, status=200) diff --git a/restoran_max_bot/restoran_max_bot/__init__.py b/restoran_max_bot/restoran_max_bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/restoran_max_bot/restoran_max_bot/asgi.py b/restoran_max_bot/restoran_max_bot/asgi.py new file mode 100644 index 0000000..c7596e7 --- /dev/null +++ b/restoran_max_bot/restoran_max_bot/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for restoran_max_bot project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'restoran_max_bot.settings') + +application = get_asgi_application() diff --git a/restoran_max_bot/restoran_max_bot/settings.py b/restoran_max_bot/restoran_max_bot/settings.py new file mode 100644 index 0000000..e3b2271 --- /dev/null +++ b/restoran_max_bot/restoran_max_bot/settings.py @@ -0,0 +1,122 @@ +from pathlib import Path +from environ import environ + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +env = environ.Env() +env_file = f'{BASE_DIR}/.env' +env.read_env(env_file=env_file) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = env('SECRET_KEY') + +# SECURITY WARNING: don't run with debug turned on in production! +if str(env('DEBUG')).lower() == 'false': + DEBUG = False +else: + DEBUG = True + + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'max_bot', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'restoran_max_bot.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'restoran_max_bot.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': env('DATABASE_NAME'), + 'USER': env('DATABASE_USER'), + 'PASSWORD': env('DATABASE_PASSWORD'), + 'HOST': env('DATABASE_HOST'), + 'PORT': env('DATABASE_PORT'), + 'OPTIONS': { + 'charset': 'utf8mb4', + 'use_unicode': True, }, + } +} + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' +TIME_ZONE = 'UTC' +USE_I18N = True +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/restoran_max_bot/restoran_max_bot/urls.py b/restoran_max_bot/restoran_max_bot/urls.py new file mode 100644 index 0000000..44a4d08 --- /dev/null +++ b/restoran_max_bot/restoran_max_bot/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + # path('admin/', admin.site.urls), + path('max/', include('max_bot.urls')) +] diff --git a/restoran_max_bot/restoran_max_bot/utils.py b/restoran_max_bot/restoran_max_bot/utils.py new file mode 100644 index 0000000..bd93b7d --- /dev/null +++ b/restoran_max_bot/restoran_max_bot/utils.py @@ -0,0 +1,91 @@ +import json +from datetime import datetime + + +def normalize_mobile_phone(phone: str) -> str: + """ + Приводит номер телефона к формату 7XXXXXXXXXX (11 цифр, начинается с 7). + + Алгоритм: + 1. Удаляет все нецифровые символы. + 2. Если номер начинается с '8', заменяет на '7'. + 3. Если начинается с '+7', убирает '+', оставляет '7'. + 4. Если начинается с другой цифры (и не 8/7), добавляет '7' в начало. + 5. Возвращает строку из 11 цифр, начинающуюся с 7. + + Args: + phone (str): Исходный номер (может содержать пробелы, дефисы, скобки, + и т.п.). + + Returns: + str: Нормализованный номер в формате 7XXXXXXXXXX. + """ + # Удаляем всё, кроме цифр + digits = ''.join(c for c in phone.strip() if c.isdigit()) + + if not digits: + return '7' + '0' * 10 # если цифр нет — возвращаем 70000000000 + + # Если начинается с 8 — заменяем на 7 + if digits.startswith('8'): + digits = '7' + digits[1:] + # Если начинается с 7 — оставляем как есть + elif not digits.startswith('7'): + digits = '7' + digits # иначе добавляем 7 в начало + + # Доводим до 11 цифр: если меньше — дополняем нулями справа; если больше — берём первые 11 + if len(digits) < 11: + digits = digits.ljust(11, '0') + else: + digits = digits[:11] + + return digits + + +def is_valid_datetime(text): + """ + Проверяет, соответствует ли строка формату %Y-%m-%dT%H:%M:%S. + + :param text: строка для проверки + :return: bool — True, если формат верный; False иначе + """ + try: + datetime.strptime(text, "%Y-%m-%dT%H:%M:%S") + return True + except (ValueError, TypeError): + return False + + +def has_key(json_data, key): + """ + Проверяет, есть ли указанный ключ в JSON‑объекте (словаре). + + :param json_data: dict или str (JSON‑строка) + :param key: строка — искомый ключ + :return: bool + """ + # Если на вход дана строка, преобразуем в словарь + if isinstance(json_data, str): + try: + json_data = json.loads(json_data) + except json.JSONDecodeError: + return False + + return key in json_data + + +# проверяет json ли это +def is_json(myjson): + """ + Проверяет, является ли строка валидным JSON. + + Args: + myjson (str): проверяемая строка + + Returns: + bool: True, если строка — валидный JSON, иначе False + """ + try: + json.loads(myjson) + except ValueError as e: + return False + return True diff --git a/restoran_max_bot/restoran_max_bot/wsgi.py b/restoran_max_bot/restoran_max_bot/wsgi.py new file mode 100644 index 0000000..c863d83 --- /dev/null +++ b/restoran_max_bot/restoran_max_bot/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for restoran_max_bot project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'restoran_max_bot.settings') + +application = get_wsgi_application()