mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-19 19:32:10 +00:00
@@ -214,7 +214,7 @@ TRIAL_ACTIVATION_PRICE=0
|
||||
# Сколько устройств доступно по дефолту при покупке платной подписки
|
||||
DEFAULT_DEVICE_LIMIT=3
|
||||
|
||||
# Максимум устройств достопных к покупке (0 = Нет лимита)
|
||||
# Максимум устройств доступных к покупке (0 = Нет лимита)
|
||||
MAX_DEVICES_LIMIT=15
|
||||
|
||||
# Дефолт параметры для подписок выданных через админку
|
||||
|
||||
6
.github/workflows/docker-hub.yml
vendored
6
.github/workflows/docker-hub.yml
vendored
@@ -36,15 +36,15 @@ jobs:
|
||||
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:latest,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}"
|
||||
echo "🏷️ Собираем релизную версию: $VERSION"
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
VERSION="v3.0.0-$(git rev-parse --short HEAD)"
|
||||
VERSION="v3.1.0-$(git rev-parse --short HEAD)"
|
||||
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:latest,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}"
|
||||
echo "🚀 Собираем версию из main: $VERSION"
|
||||
elif [[ $GITHUB_REF == refs/heads/dev ]]; then
|
||||
VERSION="v3.0.0-dev-$(git rev-parse --short HEAD)"
|
||||
VERSION="v3.1.0-dev-$(git rev-parse --short HEAD)"
|
||||
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:dev,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}"
|
||||
echo "🧪 Собираем dev версию: $VERSION"
|
||||
else
|
||||
VERSION="v3.0.0-pr-$(git rev-parse --short HEAD)"
|
||||
VERSION="v3.1.0-pr-$(git rev-parse --short HEAD)"
|
||||
TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:pr-$(git rev-parse --short HEAD)"
|
||||
echo "🔀 Собираем PR версию: $VERSION"
|
||||
fi
|
||||
|
||||
6
.github/workflows/docker-registry.yml
vendored
6
.github/workflows/docker-registry.yml
vendored
@@ -49,13 +49,13 @@ jobs:
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
echo "🏷️ Building release version: $VERSION"
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
VERSION="v3.0.0-$(git rev-parse --short HEAD)"
|
||||
VERSION="v3.1.0-$(git rev-parse --short HEAD)"
|
||||
echo "🚀 Building main version: $VERSION"
|
||||
elif [[ $GITHUB_REF == refs/heads/dev ]]; then
|
||||
VERSION="v3.0.0-dev-$(git rev-parse --short HEAD)"
|
||||
VERSION="v3.1.0-dev-$(git rev-parse --short HEAD)"
|
||||
echo "🧪 Building dev version: $VERSION"
|
||||
else
|
||||
VERSION="v3.0.0-pr-$(git rev-parse --short HEAD)"
|
||||
VERSION="v3.1.0-pr-$(git rev-parse --short HEAD)"
|
||||
echo "🔀 Building PR version: $VERSION"
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -14,7 +14,7 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
||||
|
||||
FROM python:3.13-slim
|
||||
|
||||
ARG VERSION="v3.0.0"
|
||||
ARG VERSION="v3.1.0"
|
||||
ARG BUILD_DATE
|
||||
ARG VCS_REF
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ class ContestInfo(BaseModel):
|
||||
slug: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
prize_days: int
|
||||
prize_type: str
|
||||
prize_value: str
|
||||
is_available: bool
|
||||
already_played: bool = False
|
||||
|
||||
@@ -65,7 +66,8 @@ class ContestResult(BaseModel):
|
||||
"""Result of contest attempt."""
|
||||
is_winner: bool
|
||||
message: str
|
||||
prize_days: Optional[int] = None
|
||||
prize_type: Optional[str] = None
|
||||
prize_value: Optional[str] = None
|
||||
|
||||
|
||||
# ============ Helpers ============
|
||||
@@ -80,19 +82,47 @@ def _user_allowed(subscription) -> bool:
|
||||
}
|
||||
|
||||
|
||||
async def _award_prize(db: AsyncSession, user_id: int, prize_days: int) -> str:
|
||||
async def _award_prize(db: AsyncSession, user_id: int, prize_type: str, prize_value: str) -> str:
|
||||
"""Award prize to winner."""
|
||||
subscription = await get_subscription_by_user_id(db, user_id)
|
||||
if not subscription:
|
||||
return "Error: subscription not found"
|
||||
if prize_type == "days":
|
||||
try:
|
||||
days = int(prize_value)
|
||||
except ValueError:
|
||||
return "Error: invalid prize value"
|
||||
|
||||
subscription.end_date = subscription.end_date + timedelta(days=prize_days)
|
||||
subscription.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
await db.refresh(subscription)
|
||||
subscription = await get_subscription_by_user_id(db, user_id)
|
||||
if not subscription:
|
||||
return "Error: subscription not found"
|
||||
|
||||
logger.info(f"🎁 Extended subscription for user {user_id} by {prize_days} days (contest prize)")
|
||||
return f"Subscription extended by {prize_days} days"
|
||||
subscription.end_date = subscription.end_date + timedelta(days=days)
|
||||
subscription.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
await db.refresh(subscription)
|
||||
|
||||
logger.info(f"🎁 Extended subscription for user {user_id} by {days} days (contest prize)")
|
||||
return f"Subscription extended by {days} days"
|
||||
|
||||
elif prize_type == "balance":
|
||||
from app.database.crud.user import get_user_by_id
|
||||
try:
|
||||
amount = float(prize_value)
|
||||
except ValueError:
|
||||
return "Error: invalid prize value"
|
||||
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
return "Error: user not found"
|
||||
|
||||
user.balance += amount
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
logger.info(f"🎁 Added {amount} to balance for user {user_id} (contest prize)")
|
||||
return f"Balance increased by {amount}"
|
||||
|
||||
else:
|
||||
logger.warning(f"Unknown prize type: {prize_type}")
|
||||
return f"Prize type '{prize_type}' not supported"
|
||||
|
||||
|
||||
# ============ Routes ============
|
||||
@@ -169,7 +199,8 @@ async def get_contests(
|
||||
slug=tpl_slug,
|
||||
name=rnd.template.name if rnd.template else tpl_slug,
|
||||
description=rnd.template.description if rnd.template else None,
|
||||
prize_days=rnd.template.prize_days if rnd.template else 0,
|
||||
prize_type=rnd.template.prize_type if rnd.template else "days",
|
||||
prize_value=rnd.template.prize_value if rnd.template else "1",
|
||||
is_available=True,
|
||||
already_played=attempt is not None,
|
||||
))
|
||||
@@ -368,11 +399,12 @@ async def submit_contest_answer(
|
||||
|
||||
if is_winner:
|
||||
await increment_winner_count(db, round_obj)
|
||||
prize_text = await _award_prize(db, user.id, tpl.prize_days)
|
||||
prize_text = await _award_prize(db, user.id, tpl.prize_type, tpl.prize_value)
|
||||
return ContestResult(
|
||||
is_winner=True,
|
||||
message=f"🎉 Congratulations! You won! {prize_text}",
|
||||
prize_days=tpl.prize_days,
|
||||
prize_type=tpl.prize_type,
|
||||
prize_value=tpl.prize_value,
|
||||
)
|
||||
else:
|
||||
lose_messages = {
|
||||
|
||||
@@ -194,8 +194,8 @@ async def manual_start_round(
|
||||
return
|
||||
|
||||
# Проверяем, есть ли уже активный раунд для этого шаблона
|
||||
from app.database.crud.contest import get_active_rounds
|
||||
exists = await get_active_rounds(db, tpl.id)
|
||||
from app.database.crud.contest import get_active_round_by_template
|
||||
exists = await get_active_round_by_template(db, tpl.id)
|
||||
if exists:
|
||||
await callback.answer(texts.t("ADMIN_ROUND_ALREADY_ACTIVE", "Раунд уже активен."), show_alert=True)
|
||||
await show_daily_contest(callback, db_user, db)
|
||||
|
||||
@@ -571,11 +571,14 @@ def get_daily_contest_manage_keyboard(
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_CONTEST_START_MANUAL", "🧪 Ручной старт"), callback_data=f"admin_daily_manual_{template_id}"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_PRIZE", "🏅 Приз (дни)"), callback_data=f"admin_daily_edit_{template_id}_prize_days"),
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_MAX_WINNERS", "👥 Победителей"), callback_data=f"admin_daily_edit_{template_id}_max_winners"),
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_PRIZE_TYPE", "🏅 Тип приза"), callback_data=f"admin_daily_edit_{template_id}_prize_type"),
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_PRIZE_VALUE", "💰 Значение приза"), callback_data=f"admin_daily_edit_{template_id}_prize_value"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_MAX_WINNERS", "👥 Победителей"), callback_data=f"admin_daily_edit_{template_id}_max_winners"),
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_ATTEMPTS", "🔁 Попытки"), callback_data=f"admin_daily_edit_{template_id}_attempts_per_user"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text=_t(texts, "ADMIN_EDIT_TIMES", "⏰ Раундов/день"), callback_data=f"admin_daily_edit_{template_id}_times_per_day"),
|
||||
],
|
||||
[
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
- `GET /contests/daily/templates?enabled_only=false` — список шаблонов игр.
|
||||
- `GET /contests/daily/templates/{id}` — получить шаблон.
|
||||
- `PATCH /contests/daily/templates/{id}` — обновить поля: `name`, `description`, `prize_days`, `max_winners`, `attempts_per_user`, `times_per_day`, `schedule_times`, `cooldown_hours`, `payload` (dict), `is_enabled`.
|
||||
- `PATCH /contests/daily/templates/{id}` — обновить поля: `name`, `description`, `prize_type`, `prize_value`, `max_winners`, `attempts_per_user`, `times_per_day`, `schedule_times`, `cooldown_hours`, `payload` (dict), `is_enabled`.
|
||||
- `POST /contests/daily/templates/{id}/start-round` — запустить раунд вручную. Тело:
|
||||
```json
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user