forked from zovos/bot_tg
Merge pull request 'Ставки на спорт' (#13) from Dan4ick/bot_tg:feature/FenyaBot into main
Reviewed-on: zovos/bot_tg#13
This commit is contained in:
commit
7289296169
5 changed files with 405 additions and 1 deletions
1
.env
1
.env
|
|
@ -1,2 +1,3 @@
|
||||||
BOT_TOKEN=8488665890:AAH7FY5I2xzHhfOdxlzb11ewl2I-MMTH2-o
|
BOT_TOKEN=8488665890:AAH7FY5I2xzHhfOdxlzb11ewl2I-MMTH2-o
|
||||||
TZ=Europe/Moscow
|
TZ=Europe/Moscow
|
||||||
|
ODDS_API_KEY=c29bc26dee8aa9d95a5ce8d14ea63923
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@
|
||||||
- авто-ответы в чате (10% шанс + триггер-слова);
|
- авто-ответы в чате (10% шанс + триггер-слова);
|
||||||
- миниигра `/penis` + `/top_penis`;
|
- миниигра `/penis` + `/top_penis`;
|
||||||
- казино на размер (`/penis_casino`);
|
- казино на размер (`/penis_casino`);
|
||||||
- гадание на фене с мемом (`/gadanie`).
|
- гадание на фене с мемом (`/gadanie`);
|
||||||
|
- ставки на матчи CS2, Dota 2, футбол с реальными коэффициентами (`/matches`, `/bet`).
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
|
|
@ -31,11 +32,13 @@ docker compose down
|
||||||
|
|
||||||
Переменные окружения: задайте `BOT_TOKEN` в `.env`.
|
Переменные окружения: задайте `BOT_TOKEN` в `.env`.
|
||||||
Для ИИ-фич нужен `LLAMA_API_URL`.
|
Для ИИ-фич нужен `LLAMA_API_URL`.
|
||||||
|
Для ставок нужен `ODDS_API_KEY` (бесплатно на https://the-odds-api.com).
|
||||||
|
|
||||||
Контейнер хранит состояние в `./db`:
|
Контейнер хранит состояние в `./db`:
|
||||||
- `penis_stats.sqlite3`
|
- `penis_stats.sqlite3`
|
||||||
- `chat_history.sqlite3`
|
- `chat_history.sqlite3`
|
||||||
- `polychaetsi_stats.json`
|
- `polychaetsi_stats.json`
|
||||||
|
- `bets.sqlite3`
|
||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
|
|
||||||
|
|
@ -52,6 +55,9 @@ docker compose down
|
||||||
- `/penis_casino <ставка>` — казино на размер (макс 1 см, казино всегда в выигрыше).
|
- `/penis_casino <ставка>` — казино на размер (макс 1 см, казино всегда в выигрыше).
|
||||||
- `/gadanie <тема>` — ИИ-гадание на фене + авто-мем.
|
- `/gadanie <тема>` — ИИ-гадание на фене + авто-мем.
|
||||||
- `/ebalnik` — вкл/выкл авто-ответы бота в чате.
|
- `/ebalnik` — вкл/выкл авто-ответы бота в чате.
|
||||||
|
- `/matches [cs|dota|football]` — ближайшие матчи с коэффициентами.
|
||||||
|
- `/bet <спорт> <номер> <команда> <ставка>` — поставить см на матч.
|
||||||
|
- `/mybets` — мои активные ставки.
|
||||||
- `/svodka` — сводка СВО.
|
- `/svodka` — сводка СВО.
|
||||||
|
|
||||||
## Макеты и шрифт
|
## Макеты и шрифт
|
||||||
|
|
@ -80,6 +86,7 @@ docker compose down
|
||||||
- `/talk` шлёт запрос к llama.cpp серверу с контекстом 100 сообщений (хранятся в SQLite).
|
- `/talk` шлёт запрос к llama.cpp серверу с контекстом 100 сообщений (хранятся в SQLite).
|
||||||
- `/penis_casino` — слоты с множителем x1.2–x3 (`games/casino.py`).
|
- `/penis_casino` — слоты с множителем x1.2–x3 (`games/casino.py`).
|
||||||
- `/gadanie` — ИИ-гадание + авто-мем через `generate_meme` (`games/fortune.py`).
|
- `/gadanie` — ИИ-гадание + авто-мем через `generate_meme` (`games/fortune.py`).
|
||||||
|
- `/matches` + `/bet` — ставки на реальные матчи, коэффициенты через The-Odds-API (`games/betting.py`).
|
||||||
|
|
||||||
## ИИ-модуль (`AI/talk_handler.py`)
|
## ИИ-модуль (`AI/talk_handler.py`)
|
||||||
|
|
||||||
|
|
|
||||||
15
config.py
15
config.py
|
|
@ -27,6 +27,18 @@ POLYCHAETSI_IMAGE_PATH = BASE_DIR / "maket" / "cvetok.jpg"
|
||||||
# --- Казино ---
|
# --- Казино ---
|
||||||
CASINO_MAX_BET = 1.0
|
CASINO_MAX_BET = 1.0
|
||||||
|
|
||||||
|
# --- Ставки на матчи ---
|
||||||
|
ODDS_API_KEY = os.getenv("ODDS_API_KEY", "")
|
||||||
|
ODDS_API_BASE = "https://api.the-odds-api.com/v4"
|
||||||
|
ODDS_SPORTS = {
|
||||||
|
"cs": "esports_csgo",
|
||||||
|
"dota": "esports_dota2",
|
||||||
|
"football": "soccer_epl",
|
||||||
|
}
|
||||||
|
BET_MAX_AMOUNT = 3.0
|
||||||
|
BET_DB_PATH = "/db/bets.sqlite3"
|
||||||
|
ODDS_CACHE_TTL = 900
|
||||||
|
|
||||||
# --- Magnit API ---
|
# --- Magnit API ---
|
||||||
MAGNIT_API_BASE_URL = os.getenv("MAGNIT_API_BASE_URL", "https://mirror.porno4free.ru/magnit")
|
MAGNIT_API_BASE_URL = os.getenv("MAGNIT_API_BASE_URL", "https://mirror.porno4free.ru/magnit")
|
||||||
MAGNIT_STORE_CODE = os.getenv("MAGNIT_STORE_CODE", "618224")
|
MAGNIT_STORE_CODE = os.getenv("MAGNIT_STORE_CODE", "618224")
|
||||||
|
|
@ -193,6 +205,9 @@ def get_hint_text(templates_str: str) -> str:
|
||||||
"• /talk [текст] — ИИ базарит на фене.\n"
|
"• /talk [текст] — ИИ базарит на фене.\n"
|
||||||
"• /penis_casino [ставка] — казино на размер.\n"
|
"• /penis_casino [ставка] — казино на размер.\n"
|
||||||
"• /gadanie [тема] — гадание на фене с мемом.\n"
|
"• /gadanie [тема] — гадание на фене с мемом.\n"
|
||||||
|
"• /matches [cs|dota|football] — матчи для ставок.\n"
|
||||||
|
"• /bet <спорт> <номер> <команда> <ставка> — поставить см.\n"
|
||||||
|
"• /mybets — мои ставки.\n"
|
||||||
"• /ebalnik — включить/выключить автоответы в чате.\n"
|
"• /ebalnik — включить/выключить автоответы в чате.\n"
|
||||||
"• /polychaetsi — топ по слову «получается».\n"
|
"• /polychaetsi — топ по слову «получается».\n"
|
||||||
"• /prices /fetch — цены и остатки Магнита.\n"
|
"• /prices /fetch — цены и остатки Магнита.\n"
|
||||||
|
|
|
||||||
308
games/betting.py
Normal file
308
games/betting.py
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
import time as _time
|
||||||
|
import sqlite3
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
import config
|
||||||
|
from games.casino import get_user_length, update_user_length
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_matches_cache: dict[str, tuple[float, list]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _init_bets_db() -> None:
|
||||||
|
Path(config.BET_DB_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with sqlite3.connect(config.BET_DB_PATH) as conn:
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS bets (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
match_id TEXT NOT NULL,
|
||||||
|
sport TEXT NOT NULL,
|
||||||
|
home_team TEXT NOT NULL,
|
||||||
|
away_team TEXT NOT NULL,
|
||||||
|
chosen_team TEXT NOT NULL,
|
||||||
|
amount REAL NOT NULL,
|
||||||
|
odds REAL NOT NULL,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
|
created_ts INTEGER
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_bets_user ON bets(user_id, status)")
|
||||||
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_bets_match ON bets(match_id, status)")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
_init_bets_db()
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_matches(sport_key: str) -> list[dict]:
|
||||||
|
now = _time.time()
|
||||||
|
cached = _matches_cache.get(sport_key)
|
||||||
|
if cached and (now - cached[0]) < config.ODDS_CACHE_TTL:
|
||||||
|
return cached[1]
|
||||||
|
|
||||||
|
if not config.ODDS_API_KEY:
|
||||||
|
return []
|
||||||
|
|
||||||
|
url = f"{config.ODDS_API_BASE}/sports/{sport_key}/odds"
|
||||||
|
params = {
|
||||||
|
"apiKey": config.ODDS_API_KEY,
|
||||||
|
"regions": "eu",
|
||||||
|
"markets": "h2h",
|
||||||
|
"oddsFormat": "decimal",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
logger.warning(f"Odds API returned {resp.status}")
|
||||||
|
return cached[1] if cached else []
|
||||||
|
data = await resp.json()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to fetch odds")
|
||||||
|
return cached[1] if cached else []
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
for event in data:
|
||||||
|
best_odds = {}
|
||||||
|
for bookmaker in event.get("bookmakers", []):
|
||||||
|
for market in bookmaker.get("markets", []):
|
||||||
|
if market["key"] == "h2h":
|
||||||
|
for outcome in market["outcomes"]:
|
||||||
|
name = outcome["name"]
|
||||||
|
price = outcome["price"]
|
||||||
|
if name not in best_odds or price > best_odds[name]:
|
||||||
|
best_odds[name] = price
|
||||||
|
|
||||||
|
if len(best_odds) >= 2:
|
||||||
|
matches.append({
|
||||||
|
"id": event["id"],
|
||||||
|
"home": event["home_team"],
|
||||||
|
"away": event["away_team"],
|
||||||
|
"commence": event.get("commence_time", ""),
|
||||||
|
"odds": best_odds,
|
||||||
|
})
|
||||||
|
|
||||||
|
_matches_cache[sport_key] = (now, matches)
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
def format_matches(matches: list[dict], sport_label: str) -> str:
|
||||||
|
if not matches:
|
||||||
|
return f"🏟 {sport_label} — матчей не найдено."
|
||||||
|
|
||||||
|
lines = [f"🏟 {sport_label} — ближайшие матчи:\n"]
|
||||||
|
for i, m in enumerate(matches[:10], 1):
|
||||||
|
odds_parts = []
|
||||||
|
for team, coeff in m["odds"].items():
|
||||||
|
odds_parts.append(f"{team}: {coeff:.2f}")
|
||||||
|
odds_str = " | ".join(odds_parts)
|
||||||
|
|
||||||
|
start_str = ""
|
||||||
|
if m["commence"]:
|
||||||
|
try:
|
||||||
|
dt = datetime.fromisoformat(m["commence"].replace("Z", "+00:00"))
|
||||||
|
msk = dt.astimezone(ZoneInfo(config.DEFAULT_TZ))
|
||||||
|
start_str = f"\n 🕐 {msk.strftime('%d.%m %H:%M')} МСК"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
lines.append(f"{i}️⃣ {m['home']} vs {m['away']}\n {odds_str}{start_str}\n")
|
||||||
|
|
||||||
|
lines.append("Ставка: /bet <номер> <команда> <сумма>")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sport_label(key: str) -> str:
|
||||||
|
labels = {"cs": "🎮 CS2", "dota": "🎮 Dota 2", "football": "⚽ Футбол"}
|
||||||
|
return labels.get(key, key)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_formatted_matches(sport_alias: str | None) -> str:
|
||||||
|
if sport_alias and sport_alias in config.ODDS_SPORTS:
|
||||||
|
sport_key = config.ODDS_SPORTS[sport_alias]
|
||||||
|
matches = await fetch_matches(sport_key)
|
||||||
|
return format_matches(matches, _get_sport_label(sport_alias))
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
for alias, sport_key in config.ODDS_SPORTS.items():
|
||||||
|
matches = await fetch_matches(sport_key)
|
||||||
|
parts.append(format_matches(matches, _get_sport_label(alias)))
|
||||||
|
return "\n\n".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
_last_fetched_matches: dict[str, list[dict]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
async def get_match_by_index(sport_alias: str, index: int) -> dict | None:
|
||||||
|
sport_key = config.ODDS_SPORTS.get(sport_alias)
|
||||||
|
if not sport_key:
|
||||||
|
return None
|
||||||
|
matches = await fetch_matches(sport_key)
|
||||||
|
if 0 < index <= len(matches):
|
||||||
|
return matches[index - 1]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def place_bet(user_id: int, match: dict, sport: str, chosen_team: str, amount: float) -> str:
|
||||||
|
current = get_user_length(user_id)
|
||||||
|
if current is None:
|
||||||
|
return "Сначала заведи счёт через /penis, братуха."
|
||||||
|
|
||||||
|
if current <= 0:
|
||||||
|
return "🚫 С кредитом ставки не принимаем."
|
||||||
|
|
||||||
|
if amount <= 0:
|
||||||
|
return "Ставка должна быть больше нуля."
|
||||||
|
|
||||||
|
if amount > config.BET_MAX_AMOUNT:
|
||||||
|
return f"Максимальная ставка — {config.BET_MAX_AMOUNT} см."
|
||||||
|
|
||||||
|
if amount > current:
|
||||||
|
return f"У тебя {current:.1f} см, а ставишь {amount:.1f}. Не хватает, фраер."
|
||||||
|
|
||||||
|
team_odds = match["odds"].get(chosen_team)
|
||||||
|
if team_odds is None:
|
||||||
|
available = ", ".join(match["odds"].keys())
|
||||||
|
return f"Команда не найдена. Доступные: {available}"
|
||||||
|
|
||||||
|
new_length = update_user_length(user_id, -amount)
|
||||||
|
if new_length is None:
|
||||||
|
return "Ошибка БД."
|
||||||
|
|
||||||
|
now_ts = int(_time.time())
|
||||||
|
try:
|
||||||
|
with sqlite3.connect(config.BET_DB_PATH) as conn:
|
||||||
|
conn.execute(
|
||||||
|
"""INSERT INTO bets (user_id, match_id, sport, home_team, away_team,
|
||||||
|
chosen_team, amount, odds, status, created_ts)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)""",
|
||||||
|
(user_id, match["id"], sport, match["home"], match["away"],
|
||||||
|
chosen_team, amount, team_odds, now_ts),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
except sqlite3.Error:
|
||||||
|
logger.exception("Failed to place bet")
|
||||||
|
update_user_length(user_id, amount)
|
||||||
|
return "Ошибка БД."
|
||||||
|
|
||||||
|
potential = round(amount * team_odds, 1)
|
||||||
|
return (
|
||||||
|
f"✅ Ставка принята!\n"
|
||||||
|
f"🏟 {match['home']} vs {match['away']}\n"
|
||||||
|
f"📌 {chosen_team} (x{team_odds:.2f})\n"
|
||||||
|
f"💰 Ставка: {amount:.1f} см\n"
|
||||||
|
f"🎯 Возможный выигрыш: {potential:.1f} см\n"
|
||||||
|
f"📏 Остаток: {new_length:.1f} см"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_bets(user_id: int) -> str:
|
||||||
|
try:
|
||||||
|
with sqlite3.connect(config.BET_DB_PATH) as conn:
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT * FROM bets WHERE user_id = ? AND status = 'pending' ORDER BY created_ts DESC LIMIT 10",
|
||||||
|
(user_id,),
|
||||||
|
).fetchall()
|
||||||
|
except sqlite3.Error:
|
||||||
|
return "Ошибка БД."
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
return "У тебя нет активных ставок."
|
||||||
|
|
||||||
|
lines = ["📋 Твои ставки:\n"]
|
||||||
|
for r in rows:
|
||||||
|
potential = round(r["amount"] * r["odds"], 1)
|
||||||
|
lines.append(
|
||||||
|
f"• {r['home_team']} vs {r['away_team']}\n"
|
||||||
|
f" {r['chosen_team']} (x{r['odds']:.2f}), {r['amount']:.1f} см → {potential:.1f} см"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
async def settle_bets() -> list[str]:
|
||||||
|
notifications = []
|
||||||
|
try:
|
||||||
|
with sqlite3.connect(config.BET_DB_PATH) as conn:
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
pending = conn.execute(
|
||||||
|
"SELECT DISTINCT match_id, sport FROM bets WHERE status = 'pending'"
|
||||||
|
).fetchall()
|
||||||
|
except sqlite3.Error:
|
||||||
|
return notifications
|
||||||
|
|
||||||
|
for row in pending:
|
||||||
|
match_id = row["match_id"]
|
||||||
|
sport = row["sport"]
|
||||||
|
sport_key = config.ODDS_SPORTS.get(sport, sport)
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{config.ODDS_API_BASE}/sports/{sport_key}/scores"
|
||||||
|
params = {"apiKey": config.ODDS_API_KEY, "daysFrom": 3}
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
continue
|
||||||
|
scores_data = await resp.json()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for event in scores_data:
|
||||||
|
if event.get("id") != match_id:
|
||||||
|
continue
|
||||||
|
if not event.get("completed"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
scores = event.get("scores")
|
||||||
|
if not scores:
|
||||||
|
continue
|
||||||
|
|
||||||
|
winner = None
|
||||||
|
max_score = -1
|
||||||
|
for s in scores:
|
||||||
|
score_val = int(s.get("score", 0))
|
||||||
|
if score_val > max_score:
|
||||||
|
max_score = score_val
|
||||||
|
winner = s["name"]
|
||||||
|
|
||||||
|
if not winner:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
with sqlite3.connect(config.BET_DB_PATH) as conn:
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
bets = conn.execute(
|
||||||
|
"SELECT * FROM bets WHERE match_id = ? AND status = 'pending'",
|
||||||
|
(match_id,),
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
for bet in bets:
|
||||||
|
if bet["chosen_team"] == winner:
|
||||||
|
winnings = round(bet["amount"] * bet["odds"], 1)
|
||||||
|
update_user_length(bet["user_id"], winnings)
|
||||||
|
conn.execute("UPDATE bets SET status = 'won' WHERE id = ?", (bet["id"],))
|
||||||
|
notifications.append(
|
||||||
|
f"🎉 user_id={bet['user_id']}: выиграл {winnings:.1f} см "
|
||||||
|
f"({bet['home_team']} vs {bet['away_team']}, {bet['chosen_team']})"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
conn.execute("UPDATE bets SET status = 'lost' WHERE id = ?", (bet["id"],))
|
||||||
|
notifications.append(
|
||||||
|
f"❌ user_id={bet['user_id']}: проиграл {bet['amount']:.1f} см "
|
||||||
|
f"({bet['home_team']} vs {bet['away_team']}, {bet['chosen_team']})"
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
except sqlite3.Error:
|
||||||
|
logger.exception("Failed to settle bets")
|
||||||
|
|
||||||
|
return notifications
|
||||||
73
main.py
73
main.py
|
|
@ -22,6 +22,7 @@ from aiogram.client.default import DefaultBotProperties
|
||||||
from AI.talk_handler import handle_talk, generate_autoreply, push_message
|
from AI.talk_handler import handle_talk, generate_autoreply, push_message
|
||||||
from games.casino import play_casino
|
from games.casino import play_casino
|
||||||
from games.fortune import generate_fortune
|
from games.fortune import generate_fortune
|
||||||
|
from games.betting import get_formatted_matches, get_match_by_index, place_bet, get_user_bets, settle_bets
|
||||||
from zparser import get_military_data
|
from zparser import get_military_data
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
|
@ -1215,6 +1216,67 @@ async def handle_gadanie_cmd(message: Message):
|
||||||
logger.exception("Gadanie failed")
|
logger.exception("Gadanie failed")
|
||||||
await message.reply("Бля, карты рассыпались, попробуй позже.")
|
await message.reply("Бля, карты рассыпались, попробуй позже.")
|
||||||
|
|
||||||
|
async def handle_matches_cmd(message: Message):
|
||||||
|
parts = message.text.split(maxsplit=1)
|
||||||
|
sport_alias = parts[1].strip().lower() if len(parts) > 1 else None
|
||||||
|
await message.bot.send_chat_action(chat_id=message.chat.id, action="typing")
|
||||||
|
result = await get_formatted_matches(sport_alias)
|
||||||
|
await message.reply(result, parse_mode=None)
|
||||||
|
|
||||||
|
async def handle_bet_cmd(message: Message):
|
||||||
|
if not message.from_user:
|
||||||
|
await message.answer("Команда доступна только пользователям.")
|
||||||
|
return
|
||||||
|
parts = message.text.split()
|
||||||
|
if len(parts) < 4:
|
||||||
|
await message.reply(
|
||||||
|
"Формат: /bet <номер> <команда> <ставка>\n"
|
||||||
|
"Пример: /bet cs 1 NAVI 0.5\n"
|
||||||
|
"Спорт: cs / dota / football"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
# /bet <спорт> <номер> <команда> <ставка>
|
||||||
|
if len(parts) >= 5:
|
||||||
|
sport_alias = parts[1].lower()
|
||||||
|
try:
|
||||||
|
index = int(parts[2])
|
||||||
|
except ValueError:
|
||||||
|
await message.reply("Номер матча должен быть числом.")
|
||||||
|
return
|
||||||
|
team = parts[3]
|
||||||
|
try:
|
||||||
|
amount = round(float(parts[4].replace(",", ".")), 1)
|
||||||
|
except ValueError:
|
||||||
|
await message.reply("Ставка должна быть числом.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
await message.reply("Формат: /bet <спорт> <номер> <команда> <ставка>")
|
||||||
|
return
|
||||||
|
|
||||||
|
match = await get_match_by_index(sport_alias, index)
|
||||||
|
if not match:
|
||||||
|
await message.reply("Матч не найден. Посмотри /matches")
|
||||||
|
return
|
||||||
|
|
||||||
|
result = await asyncio.to_thread(place_bet, message.from_user.id, match, sport_alias, team, amount)
|
||||||
|
await message.reply(result, parse_mode=None)
|
||||||
|
|
||||||
|
async def handle_mybets_cmd(message: Message):
|
||||||
|
if not message.from_user:
|
||||||
|
return
|
||||||
|
result = await asyncio.to_thread(get_user_bets, message.from_user.id)
|
||||||
|
await message.reply(result, parse_mode=None)
|
||||||
|
|
||||||
|
async def _settle_loop(bot: Bot):
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(1800)
|
||||||
|
try:
|
||||||
|
notifications = await settle_bets()
|
||||||
|
for note in notifications:
|
||||||
|
logger.info(note)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("settle_bets failed")
|
||||||
|
|
||||||
# --- Запуск ---
|
# --- Запуск ---
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
@ -1244,6 +1306,9 @@ def main():
|
||||||
dp.message.register(handle_ebalnik_cmd, Command("ebalnik"))
|
dp.message.register(handle_ebalnik_cmd, Command("ebalnik"))
|
||||||
dp.message.register(handle_penis_casino_cmd, Command("penis_casino"))
|
dp.message.register(handle_penis_casino_cmd, Command("penis_casino"))
|
||||||
dp.message.register(handle_gadanie_cmd, Command("gadanie"))
|
dp.message.register(handle_gadanie_cmd, Command("gadanie"))
|
||||||
|
dp.message.register(handle_matches_cmd, Command("matches"))
|
||||||
|
dp.message.register(handle_bet_cmd, Command("bet"))
|
||||||
|
dp.message.register(handle_mybets_cmd, Command("mybets"))
|
||||||
dp.message.register(handle_gen_mem, Command("gen_mem"))
|
dp.message.register(handle_gen_mem, Command("gen_mem"))
|
||||||
dp.message.register(handle_keywords, F.text)
|
dp.message.register(handle_keywords, F.text)
|
||||||
|
|
||||||
|
|
@ -1267,6 +1332,9 @@ def main():
|
||||||
BotCommand(command="ebalnik", description="вкл/выкл автответов"),
|
BotCommand(command="ebalnik", description="вкл/выкл автответов"),
|
||||||
BotCommand(command="penis_casino", description="казино на размер"),
|
BotCommand(command="penis_casino", description="казино на размер"),
|
||||||
BotCommand(command="gadanie", description="гадание на фене"),
|
BotCommand(command="gadanie", description="гадание на фене"),
|
||||||
|
BotCommand(command="matches", description="матчи для ставок"),
|
||||||
|
BotCommand(command="bet", description="поставить см на матч"),
|
||||||
|
BotCommand(command="mybets", description="мои ставки"),
|
||||||
BotCommand(command="svodka", description="СВО: итоги"),
|
BotCommand(command="svodka", description="СВО: итоги"),
|
||||||
]
|
]
|
||||||
scopes = (
|
scopes = (
|
||||||
|
|
@ -1280,6 +1348,11 @@ def main():
|
||||||
logger.warning("Не удалось выставить команды, но бот продолжит работу.")
|
logger.warning("Не удалось выставить команды, но бот продолжит работу.")
|
||||||
|
|
||||||
dp.startup.register(on_startup)
|
dp.startup.register(on_startup)
|
||||||
|
|
||||||
|
async def start_settle(bot: Bot):
|
||||||
|
asyncio.create_task(_settle_loop(bot))
|
||||||
|
dp.startup.register(start_settle)
|
||||||
|
|
||||||
logger.info("Бот запущен...")
|
logger.info("Бот запущен...")
|
||||||
asyncio.run(dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()))
|
asyncio.run(dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue