Compare commits

..

2 Commits

267 changed files with 2307 additions and 300 deletions

2
lottery/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from .lottery.celery import app as celery_app
__all__ = ("celery_app",)

View File

@@ -1,37 +1,101 @@
# import requests
# import json
# from dotenv import load_dotenv
# import os
# load_dotenv()
# API_URL='http://106.245.250.243:8000/api/clients'
# API_KEY='g1XqG9lir4RneLuX01VAob+F0MpVCZTpG2is8UBWLZ0='
# def fetch_clients(api_url, api_key):
# """
# Подключаемся к API с использованием заданного X-API-Key
# """
# headers = {
# "X-API-Key": api_key
# }
# try:
# response = requests.get(api_url, headers=headers)
# response.raise_for_status()
# return response.json()
# except requests.exceptions.RequestException as e:
# print(f"Ошибка при запросе к API: {e}")
# return None
# if __name__ == '__main__':
# api_endpoint = f"{os.getenv('API_URL')}"
# api_key = os.getenv("API_KEY")
# print(api_endpoint, api_key)
# if not api_endpoint or not api_key:
# print("Необходимо задать API_URL и API_KEY в .env файле.")
# exit(1)
# clients = fetch_clients(api_endpoint, api_key)
# if clients is not None:
# print(json.dumps(clients, indent=4, ensure_ascii=False))
# else:
# print("Не удалось получить данные с API.")
import requests
import json
from dotenv import load_dotenv
import os
from dotenv import load_dotenv
load_dotenv('.env')
load_dotenv()
def fetch_clients(api_url, api_key):
"""
Подключаемся к API с использованием заданного X-API-Key
"""
headers = {
"X-API-Key": api_key
}
try:
response = requests.get(api_url, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Ошибка при запросе к API: {e}")
return None
class ApiClient:
def __init__(self, base_url=None, api_key=None):
self.base_url = base_url or os.getenv('API_URL', 'http://106.245.250.243:8000/api')
self.api_key = api_key or os.getenv('API_KEY')
if not self.api_key:
raise ValueError("API_KEY не задан ни в .env, ни через параметры конструктора")
self.headers = {
"X-API-Key": self.api_key
}
def fetch_clients(self):
"""Получение списка клиентов"""
try:
url = f"{self.base_url}/clients"
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Ошибка при получении клиентов: {e}")
return None
def fetch_invoices(self, filters=None):
"""Получение списка счетов с возможными фильтрами"""
try:
url = f"{self.base_url}/invoices"
response = requests.get(url, headers=self.headers, params=filters)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Ошибка при получении счетов: {e}")
return None
if __name__ == '__main__':
api_endpoint = os.getenv("API_URL")
api_key = os.getenv("API_KEY")
client = ApiClient()
print(api_endpoint, api_key)
if not api_endpoint or not api_key:
print("Необходимо задать API_URL и API_KEY в .env файле.")
exit(1)
clients = fetch_clients(api_endpoint, api_key)
if clients is not None:
print("=== Клиенты ===")
clients = client.fetch_clients()
if clients:
print(json.dumps(clients, indent=4, ensure_ascii=False))
else:
print("Не удалось получить данные с API.")
print("Не удалось получить клиентов")
print("\n=== Счета ===")
filters = {
"page": 1,
# "created_at[after]": "2024-12-01T00:00:00",
# "created_at[before]": "2025-01-01T00:00:00"
}
invoices = client.fetch_invoices(filters)
if invoices:
print(json.dumps(invoices, indent=4, ensure_ascii=False))
else:
print("Не удалось получить счета")

View File

@@ -1,11 +1,36 @@
from django.contrib import admin
from django.urls import path
from django.shortcuts import redirect
from django.contrib import messages
from .models import BotConfig
from .models import BotConfig, WelcomeMessage
from .tasks import restart_bot_container
@admin.register(BotConfig)
class BotConfigAdmin(admin.ModelAdmin):
list_display = ("bot_name", "channel_id", "bot_token")
search_fields = ("bot_name", "channel_id", "bot_token")
change_form_template = "admin/botconfig_change_form.html"
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"<int:botconfig_id>/restart/",
self.admin_site.admin_view(self.restart_bot),
name="botconfig-restart"
),
]
return custom_urls + urls
def restart_bot(self, request, botconfig_id):
try:
BotConfig.objects.get(id=botconfig_id)
except BotConfig.DoesNotExist:
self.message_user(request, "Настройка бота с таким ID не найдена.", level=messages.ERROR)
return redirect("..")
restart_bot_container.delay()
self.message_user(request, "Бот будет перезапущен в фоновом режиме.", messages.SUCCESS)
return redirect("..")
@admin.register(WelcomeMessage)
class WWelcomeMessageAdmin(admin.ModelAdmin):

View File

@@ -1,11 +1,12 @@
# bot/handlers.py
import re
from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import ContextTypes
from asgiref.sync import sync_to_async
from webapp.models import BindingRequest, Client, Invoice
from draw.models import Lottery, DrawResult, LotteryParticipant
from draw.models import DrawResult
from bot.state import BINDING_PENDING
from bot.models import BotConfig
@sync_to_async
def get_client_by_telegram_id(telegram_id: int):
@@ -32,9 +33,11 @@ def create_binding_request(telegram_chat_id, client_card):
client_card=client_card
)
async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not update.message:
return
chat_id = update.message.chat_id
client = await get_client_by_telegram_id(chat_id)
if not client:
@@ -55,8 +58,7 @@ async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
show_all = len(invoices) > 5
displayed_invoices = invoices[:5] if show_all else invoices
for inv in displayed_invoices:
invoice_str = str(inv).split('/')[0].strip()
message_text += f"{invoice_str} (*{inv.sum}*)\n"
message_text += f" • ID {inv.id} (*{inv.sum}*)\n"
else:
message_text += " _Нет счетов_\n"
@@ -64,12 +66,8 @@ async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
if draws:
for draw in draws:
lottery_name = draw.lottery.name if draw.lottery else "неизвестно"
invoice_info = (draw.participant.invoice
if draw.participant and hasattr(draw.participant, 'invoice')
else "неизвестно")
prize_info = (draw.prize.reward
if draw.prize and hasattr(draw.prize, 'reward')
else "неизвестно")
invoice_info = getattr(draw.participant.invoice, 'id', 'неизвестно') if draw.participant else 'неизвестно'
prize_info = getattr(draw.prize, 'reward', 'неизвестно')
message_text += f" • Лотерея: *{lottery_name}*\n Счет: _{invoice_info}_\n Выигрыш: *{prize_info}*\n\n"
else:
message_text += " _Нет результатов розыгрышей_\n"
@@ -82,43 +80,37 @@ async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(message_text, parse_mode="Markdown", reply_markup=reply_markup)
async def handle_client_card(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
Обработчик команды /bind.
После ввода команды бот запрашивает ввод номера КК и ожидает сообщение с кодом.
Здесь мы просто добавляем ID пользователя в pending.
"""
if not update.message:
return
telegram_chat_id = update.message.chat_id
# Если пользователь уже привязан, сообщаем об этом.
client = await get_client_by_telegram_id(telegram_chat_id)
if client:
await update.message.reply_text("✅ Вы уже привязаны!")
return
# Добавляем пользователя в список pending и просим ввести номер КК.
BINDING_PENDING.add(telegram_chat_id)
await update.message.reply_text("Введите номер вашей клубной карты (КК):")
async def process_binding_input(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
Обрабатывает ввод номера КК от пользователя, который находится в состоянии ожидания.
"""
if not update.message or not update.message.text:
return
client_card = update.message.text.strip()
telegram_chat_id = update.message.chat_id
if not re.fullmatch(r'\d{3,}', client_card):
await update.message.reply_text("❌ Неверный формат клиентской карты. Пожалуйста, введите корректный код (минимум 3 цифры).")
await update.message.reply_text("❌ Неверный формат клиентской карты. Пожалуйста, введите минимум 3 цифры.")
return
await create_binding_request(telegram_chat_id, client_card)
if telegram_chat_id in BINDING_PENDING:
BINDING_PENDING.remove(telegram_chat_id)
BINDING_PENDING.discard(telegram_chat_id)
await update.message.reply_text("✅ Заявка отправлена. После подтверждения вы сможете участвовать в чате.")
async def show_all_invoices_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer()
@@ -132,9 +124,18 @@ async def show_all_invoices_callback(update: Update, context: ContextTypes.DEFAU
message_text = "💳 *Все счета:*\n"
if invoices:
for inv in invoices:
invoice_str = str(inv).split('/')[0].strip()
message_text += f"{invoice_str} (*{inv.sum}*)\n"
message_text += f" • ID {inv.id} (*{inv.sum}*)\n"
else:
message_text += " _Нет счетов_\n"
await query.edit_message_text(message_text, parse_mode="Markdown")
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not update.message:
return
bot_config = await sync_to_async(BotConfig.objects.filter(is_active=True).first)()
if bot_config and bot_config.active_welcome:
await update.message.reply_text(bot_config.active_welcome.welcome_message)
else:
await update.message.reply_text("Привет! Бот работает.")

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.6 on 2025-07-20 09:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bot', '0005_alter_welcomemessage_welcome_image'),
]
operations = [
migrations.AlterField(
model_name='botconfig',
name='global_block_message',
field=models.TextField(blank=True, help_text='Сообщение, которое показывается пользователям, если чат отключён.', null=True, verbose_name='Сообщение о глобальном блоке'),
),
migrations.AlterField(
model_name='welcomemessage',
name='welcome_image',
field=models.ImageField(blank=True, help_text='Загрузите изображение для приветствия', null=True, upload_to='static/upload_image/', verbose_name='Приветственное изображение'),
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.1.6 on 2025-07-20 23:33
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bot', '0006_alter_botconfig_global_block_message_and_more'),
]
operations = [
migrations.AddField(
model_name='botconfig',
name='active_welcome',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='used_by', to='bot.welcomemessage', verbose_name='Активное приветствие'),
),
migrations.AddField(
model_name='botconfig',
name='is_active',
field=models.BooleanField(default=False, verbose_name='Включен'),
),
migrations.AddField(
model_name='botconfig',
name='welcome_messages',
field=models.ManyToManyField(blank=True, to='bot.welcomemessage', verbose_name='Варианты приветствия'),
),
]

View File

@@ -1,5 +1,6 @@
from django.db import models
class BotConfig(models.Model):
bot_token = models.CharField(max_length=255, help_text="Токен для подключения к Telegram API")
channel_id = models.CharField(max_length=100, help_text="ID канала/чата, куда бот будет отправлять сообщения")
@@ -15,8 +16,20 @@ class BotConfig(models.Model):
verbose_name="Сообщение о глобальном блоке",
help_text="Сообщение, которое показывается пользователям, если чат отключён."
)
welcome_messages = models.ManyToManyField('WelcomeMessage', blank=True, verbose_name="Варианты приветствия")
active_welcome = models.ForeignKey(
'WelcomeMessage',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="used_by",
verbose_name="Активное приветствие"
)
is_active = models.BooleanField(default=False, verbose_name="Включен")
def __str__(self):
return self.bot_name
class Meta:
verbose_name = "Настройка бота"
verbose_name_plural = "Настройки Ботов"

21
lottery/bot/tasks.py Normal file
View File

@@ -0,0 +1,21 @@
from celery import shared_task
import subprocess
@shared_task
def restart_bot_container(container_name="bot"):
try:
result = subprocess.run(
["docker", "restart", container_name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True
)
return f"Success: {result.stdout.strip()}"
except subprocess.CalledProcessError as e:
return f"Error restarting container: {e.stderr.strip()}"
@shared_task
def ping():
return "pong"

View File

@@ -0,0 +1,6 @@
[mariadb-client]
port=3306
socket=/run/mysqld/mysqld.sock
user=healthcheck
password=9hOHOe^WXq`lsXwRba/@]gB3"|[mOFe{

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,269 @@
59,5
59,4
59,3
59,2
59,1
59,0
56,4
56,3
56,2
56,1
56,0
55,4
55,3
55,2
55,1
55,0
53,4
53,3
53,2
53,1
53,0
52,5
52,4
52,3
52,2
52,1
52,0
51,5
51,4
51,3
51,2
51,1
51,0
50,6
50,5
50,4
50,3
50,2
50,1
50,0
37,3
37,2
37,1
37,0
36,4
36,3
36,2
36,1
36,0
35,4
35,3
35,2
35,1
35,0
34,3
34,2
34,1
34,0
32,4
32,3
32,2
32,1
32,0
28,4
28,3
28,2
28,1
28,0
27,4
27,3
27,2
27,1
27,0
26,5
26,4
26,3
26,2
26,1
26,0
25,5
25,4
25,3
25,2
25,1
25,0
22,5
22,4
22,3
22,2
22,1
22,0
20,5
20,4
20,3
20,2
20,1
20,0
18,5
18,4
18,3
18,2
18,1
18,0
11,4
11,3
11,2
11,1
11,0
8,3
8,2
8,1
8,0
7,3
5,5
5,4
5,3
5,2
5,0
4,3
3,2
2,2
1,2
0,9
0,2
1,45
3,44
2,44
1,44
3,43
2,43
1,43
3,42
2,42
1,42
3,41
2,41
1,41
3,40
2,40
1,40
3,39
2,39
1,39
3,38
2,38
1,38
3,37
2,37
1,37
3,36
2,36
1,36
3,35
2,35
1,35
3,34
2,34
1,34
3,33
2,33
1,33
3,32
2,32
1,32
3,31
2,31
1,31
3,30
2,30
1,30
3,29
2,29
1,29
3,28
2,28
1,28
3,27
2,27
1,27
3,26
2,26
1,26
3,25
2,25
1,25
3,24
2,24
1,24
3,23
2,23
1,23
3,22
2,22
1,22
3,21
2,21
1,21
3,20
2,20
1,20
3,19
2,19
1,19
3,18
2,18
1,18
3,17
2,17
1,17
3,16
2,16
1,16
3,15
2,15
1,15
3,14
2,14
1,14
3,13
2,13
1,13
3,12
2,12
1,12
3,11
2,11
1,11
3,10
2,10
1,10
3,9
2,9
1,9
3,8
2,8
1,8
3,7
2,7
1,7
3,6
2,6
1,6
3,5
2,5
1,5
3,4
2,4
1,4
3,3
3,0
2,3
2,0
1,3
1,0
0,6
0,0
0,47
0,46
0,49
0,48
0,45
0,12
0,10
0,8
0,11
0,5
0,7
0,4
0,3

BIN
lottery/db_data/ib_logfile0 Normal file

Binary file not shown.

BIN
lottery/db_data/ibdata1 Normal file

Binary file not shown.

BIN
lottery/db_data/ibtmp1 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
default-character-set=utf8mb4
default-collation=utf8mb4_uca1400_ai_ci

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
11.6.2-MariaDB

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
default-character-set=utf8mb4
default-collation=utf8mb4_uca1400_ai_ci

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More