From 189188db6d96eb40f875664f953df4a6964396ef Mon Sep 17 00:00:00 2001 From: Danil Date: Sat, 7 Mar 2026 15:52:49 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BA=D0=B0=D0=B7=D0=B8=D0=BA=20=D0=B8=20=D0=B3=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 5 ++ games/__init__.py | 0 games/casino.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++ games/fortune.py | 57 +++++++++++++++++++++ main.py | 43 ++++++++++++++++ 5 files changed, 232 insertions(+) create mode 100644 games/__init__.py create mode 100644 games/casino.py create mode 100644 games/fortune.py diff --git a/config.py b/config.py index 26bcebb..dad297b 100644 --- a/config.py +++ b/config.py @@ -18,6 +18,9 @@ PENIS_COOLDOWN = timedelta(hours=24) PENIS_COOLDOWN_SECONDS = int(PENIS_COOLDOWN.total_seconds()) PENIS_TOP_LIMIT = 10 +# --- Казино --- +CASINO_MAX_BET = 1.0 + # --- Конфигурация макетов --- TEMPLATES = { "1": { @@ -97,6 +100,8 @@ def get_hint_text(templates_str: str) -> str: "• /penis — миниигра 1 раз в 24 часа.\n" "• /top_penis — топ по длине.\n" "• /talk [текст] — ИИ базарит на фене.\n" + "• /penis_casino [ставка] — казино на размер.\n" + "• /gadanie [тема] — гадание на фене с мемом.\n" "• /ebalnik — включить/выключить автоответы в чате.\n" "• /prices /fetch — цены.\n" "Подсказка: в группах пиши /команда@username_бота." diff --git a/games/__init__.py b/games/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/games/casino.py b/games/casino.py new file mode 100644 index 0000000..6c4a534 --- /dev/null +++ b/games/casino.py @@ -0,0 +1,127 @@ +import random +import sqlite3 +import logging + +import config + +logger = logging.getLogger(__name__) + +# Символы слотов и их веса (чем выше вес — тем чаще выпадает) +SLOT_SYMBOLS = ["🍒", "🍋", "🔔", "💎", "7️⃣", "🍀"] +SLOT_WEIGHTS = [30, 25, 20, 12, 8, 5] # 🍒 чаще всего, 🍀 реже всего + +# Множители для выигрышей: {кол-во совпадений: (мин, макс)} +MULTIPLIERS = { + 2: (1.2, 1.8), # две одинаковых + 3: (2.0, 3.0), # три одинаковых (джекпот, редко) +} + +CASINO_WIN_CHANCE = 0.35 + + +def spin_slots() -> tuple[list[str], int]: + reels = random.choices(SLOT_SYMBOLS, weights=SLOT_WEIGHTS, k=3) + + if random.random() > CASINO_WIN_CHANCE: + while len(set(reels)) < 3: + reels = random.choices(SLOT_SYMBOLS, weights=SLOT_WEIGHTS, k=3) + return reels, 0 + + counts = {} + for s in reels: + counts[s] = counts.get(s, 0) + 1 + max_match = max(counts.values()) + return reels, max_match + + +def get_user_length(user_id: int) -> float | None: + try: + with sqlite3.connect(config.PENIS_DB_PATH) as conn: + row = conn.execute( + "SELECT length FROM penis_stats WHERE user_id = ?", + (user_id,), + ).fetchone() + return float(row[0]) if row else None + except sqlite3.Error: + logger.exception("casino: failed to get length") + return None + + +def update_user_length(user_id: int, delta: float) -> float | None: + try: + with sqlite3.connect(config.PENIS_DB_PATH) as conn: + row = conn.execute( + "SELECT length FROM penis_stats WHERE user_id = ?", + (user_id,), + ).fetchone() + if row is None: + return None + new_length = round(float(row[0]) + delta, 1) + conn.execute( + "UPDATE penis_stats SET length = ? WHERE user_id = ?", + (new_length, user_id), + ) + conn.commit() + return new_length + except sqlite3.Error: + logger.exception("casino: failed to update length") + return None + + +def play_casino(user_id: int, bet: float) -> str: + current = get_user_length(user_id) + if current is None: + return "Ты ещё ни разу не крутил /penis, братуха. Сначала заведи счёт." + + # Проверяем отрицательный баланс + if current <= 0: + return "🚫 С кредитом в казино не впускают, братуха. Иди наращивай через /penis." + + # Проверяем максимум ставки + if bet > config.CASINO_MAX_BET: + return f"Максимальная ставка — {config.CASINO_MAX_BET} см, не жадничай." + + if bet <= 0: + return "Ставка должна быть больше нуля, фраер." + + # Проверяем хватает ли + if bet > current: + return f"У тебя {current:.1f} см, а ставишь {bet:.1f}. Столько нет, фраер." + + # Крутим + reels, matches = spin_slots() + slots_display = " | ".join(reels) + + if matches >= 2: + min_mult, max_mult = MULTIPLIERS[matches] + multiplier = round(random.uniform(min_mult, max_mult), 1) + winnings = round(bet * multiplier, 1) + new_length = update_user_length(user_id, winnings) + if new_length is None: + return "Ошибка БД. Попробуй позже." + + if matches == 3: + return ( + f"🎰 {slots_display}\n\n" + f"🎉 ДЖЕКПОТ!!! Три одинаковых!\n" + f"Множитель: x{multiplier}\n" + f"Выигрыш: +{winnings:.1f} см\n" + f"📏 Текущий размер: {new_length:.1f} см" + ) + return ( + f"🎰 {slots_display}\n\n" + f"✅ Выигрыш! Две совпали.\n" + f"Множитель: x{multiplier}\n" + f"Выигрыш: +{winnings:.1f} см\n" + f"📏 Текущий размер: {new_length:.1f} см" + ) + else: + new_length = update_user_length(user_id, -bet) + if new_length is None: + return "Ошибка БД. Попробуй позже." + return ( + f"🎰 {slots_display}\n\n" + f"❌ Мимо, братуха.\n" + f"Проигрыш: -{bet:.1f} см\n" + f"📏 Текущий размер: {new_length:.1f} см" + ) diff --git a/games/fortune.py b/games/fortune.py new file mode 100644 index 0000000..7ac5c71 --- /dev/null +++ b/games/fortune.py @@ -0,0 +1,57 @@ +import logging +import aiohttp +import os + +logger = logging.getLogger(__name__) + +LLAMA_API_URL = os.getenv("LLAMA_API_URL", "https://mirror.porno4free.ru/zovos-ai/") + +FORTUNE_PROMPT = ( + "Ты — уличный гадатель с района, базаришь на фене. " + "Пользователь хочет погадать на тему. Выдай шуточное гадание, " + "коротко и дерзко, 2-3 предложения максимум. " + "В конце ОБЯЗАТЕЛЬНО напиши на отдельной строке в формате:\n" + "MEM_UP: <текст сверху для мема, максимум 4 слова>\n" + "MEM_DOWN: <текст снизу для мема, максимум 4 слова>\n" + "Тексты для мема должны быть смешными и короткими." +) + + +async def generate_fortune(topic: str) -> tuple[str, str, str]: + url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions" + payload = { + "messages": [ + {"role": "system", "content": FORTUNE_PROMPT}, + {"role": "user", "content": f"Погадай мне на: {topic}"}, + ], + "max_tokens": 200, + "temperature": 0.9, + "top_p": 0.9, + } + + async with aiohttp.ClientSession() as session: + async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp: + data = await resp.json() + text = data["choices"][0]["message"]["content"].strip() + + up_text = "" + down_text = "" + fortune_lines = [] + + for line in text.split("\n"): + stripped = line.strip() + if stripped.upper().startswith("MEM_UP:"): + up_text = stripped.split(":", 1)[1].strip() + elif stripped.upper().startswith("MEM_DOWN:"): + down_text = stripped.split(":", 1)[1].strip() + else: + fortune_lines.append(line) + + fortune_text = "\n".join(fortune_lines).strip() + + if not up_text: + up_text = "Гадание" + if not down_text: + down_text = "по фене" + + return fortune_text, up_text, down_text diff --git a/main.py b/main.py index 3ed8c4f..6c8936a 100644 --- a/main.py +++ b/main.py @@ -19,6 +19,8 @@ from aiogram.client.default import DefaultBotProperties # Локальные импорты from AI.talk_handler import handle_talk, generate_autoreply, push_message +from games.casino import play_casino +from games.fortune import generate_fortune from zparser import get_military_data import config @@ -711,6 +713,43 @@ async def handle_keywords(message: Message): except Exception: logger.exception("Autoreply failed") +async def handle_penis_casino_cmd(message: Message): + if not message.from_user: + await message.answer("Команда доступна только пользователям.") + return + parts = message.text.split(maxsplit=1) + if len(parts) < 2: + await message.reply("Формат: /penis_casino <ставка>\nПример: /penis_casino 0.5") + return + try: + bet = round(float(parts[1].replace(",", ".")), 1) + except ValueError: + await message.reply("Ставка должна быть числом, фраер. Пример: /penis_casino 0.5") + return + result = await asyncio.to_thread(play_casino, message.from_user.id, bet) + await message.reply(result, parse_mode=None) + +async def handle_gadanie_cmd(message: Message): + parts = message.text.split(maxsplit=1) + if len(parts) < 2 or not parts[1].strip(): + await message.reply("Напиши на что гадать, кент. Пример: /gadanie любовь") + return + topic = parts[1].strip() + await message.bot.send_chat_action(chat_id=message.chat.id, action="typing") + try: + fortune_text, up_text, down_text = await generate_fortune(topic) + await message.reply(f"🔮 Гадание на «{topic}»:\n\n{fortune_text}", parse_mode=None) + photo_path = config.BASE_DIR / "maket" / "vape.jpg" + if photo_path.exists(): + img = Image.open(photo_path).convert("RGB") + meme_output = await asyncio.to_thread(generate_meme, img, up_text, down_text) + await message.reply_photo( + BufferedInputFile(meme_output.getvalue(), filename="gadanie.jpg") + ) + except Exception: + logger.exception("Gadanie failed") + await message.reply("Бля, карты рассыпались, попробуй позже.") + # --- Запуск --- def main(): @@ -735,6 +774,8 @@ def main(): dp.message.register(handle_talk, Command("talk")) dp.message.register(handle_svodka_cmd, Command("svodka")) dp.message.register(handle_ebalnik_cmd, Command("ebalnik")) + dp.message.register(handle_penis_casino_cmd, Command("penis_casino")) + dp.message.register(handle_gadanie_cmd, Command("gadanie")) dp.message.register(handle_gen_mem, Command("gen_mem")) dp.message.register(handle_keywords, F.text) @@ -754,6 +795,8 @@ def main(): BotCommand(command="zvetok", description="Цветянский бля"), BotCommand(command="talk", description="Побазарить"), BotCommand(command="ebalnik", description="вкл/выкл автответов"), + BotCommand(command="penis_casino", description="казино на размер"), + BotCommand(command="gadanie", description="гадание на фене"), BotCommand(command="svodka", description="СВО: итоги"), ] scopes = (