Compare commits
No commits in common. "957d46b735599ef3b0d53ec17efe48d4d2dbbfc2" and "8ea9c50564438c22270bcc7c5d7ac9f39067330d" have entirely different histories.
957d46b735
...
8ea9c50564
1 changed files with 39 additions and 131 deletions
160
main.py
160
main.py
|
|
@ -8,20 +8,11 @@ from io import BytesIO
|
|||
from pathlib import Path
|
||||
from textwrap import wrap
|
||||
import random
|
||||
import sqlite3
|
||||
|
||||
from aiogram import Bot, Dispatcher, F, types
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.filters import Command, CommandStart
|
||||
from aiogram.types import (
|
||||
BufferedInputFile,
|
||||
Message,
|
||||
BotCommand,
|
||||
FSInputFile,
|
||||
BotCommandScopeDefault,
|
||||
BotCommandScopeAllPrivateChats,
|
||||
BotCommandScopeAllGroupChats,
|
||||
)
|
||||
from aiogram.types import BufferedInputFile, Message, BotCommand, FSInputFile
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from datetime import datetime, time, timedelta
|
||||
|
|
@ -33,13 +24,13 @@ BASE_DIR = Path(__file__).parent
|
|||
FONT_PATH = BASE_DIR / "impact.ttf"
|
||||
DEFAULT_TZ = os.getenv("TZ", "Europe/Moscow")
|
||||
|
||||
# Данные миниигры /penis храним в SQLite, чтобы не держать их в RAM.
|
||||
PENIS_DB_PATH = BASE_DIR / "penis_stats.sqlite3"
|
||||
# In-memory состояние миниигры /penis.
|
||||
# Важно: после перезапуска процесса всё обнуляется.
|
||||
PENIS_STATE: dict[int, dict[str, object]] = {}
|
||||
PENIS_START_LENGTH = 10.0
|
||||
PENIS_MIN_DELTA = 0.1
|
||||
PENIS_MAX_DELTA = 7.0
|
||||
PENIS_COOLDOWN = timedelta(hours=24)
|
||||
PENIS_COOLDOWN_SECONDS = int(PENIS_COOLDOWN.total_seconds())
|
||||
PENIS_TOP_LIMIT = 10
|
||||
|
||||
# Конфигурация текстовых макетов /maket.
|
||||
|
|
@ -52,25 +43,6 @@ TEMPLATES = {
|
|||
},
|
||||
}
|
||||
|
||||
|
||||
def init_penis_db() -> None:
|
||||
# Инициализация таблицы для миниигры /penis.
|
||||
with sqlite3.connect(PENIS_DB_PATH) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS penis_stats (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
display_name TEXT NOT NULL,
|
||||
length REAL NOT NULL,
|
||||
last_used_ts INTEGER
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_penis_stats_length ON penis_stats(length DESC)"
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def templates_list_text() -> str:
|
||||
# Собираем человекочитаемый список доступных макетов.
|
||||
parts = []
|
||||
|
|
@ -432,63 +404,35 @@ def play_penis(user_id: int, user_name: str | None = None, now: datetime | None
|
|||
now = datetime.now(ZoneInfo(DEFAULT_TZ))
|
||||
elif now.tzinfo is None:
|
||||
now = now.replace(tzinfo=ZoneInfo(DEFAULT_TZ))
|
||||
now_ts = int(now.timestamp())
|
||||
display_name = user_name.strip() if user_name and user_name.strip() else f"user_{user_id}"
|
||||
|
||||
try:
|
||||
with sqlite3.connect(PENIS_DB_PATH) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
row = conn.execute(
|
||||
"SELECT length, last_used_ts, display_name FROM penis_stats WHERE user_id = ?",
|
||||
(user_id,),
|
||||
).fetchone()
|
||||
state = PENIS_STATE.get(user_id)
|
||||
if state is None:
|
||||
state = {"length": PENIS_START_LENGTH, "last_used_at": None}
|
||||
PENIS_STATE[user_id] = state
|
||||
|
||||
if row is None:
|
||||
current_length = PENIS_START_LENGTH
|
||||
last_used_ts = None
|
||||
stored_name = None
|
||||
else:
|
||||
current_length = float(row["length"])
|
||||
last_used_ts = row["last_used_ts"]
|
||||
stored_name = row["display_name"]
|
||||
if not user_name and isinstance(stored_name, str) and stored_name.strip():
|
||||
display_name = stored_name.strip()
|
||||
# Обновляем отображаемое имя для топа.
|
||||
if user_name:
|
||||
state["display_name"] = user_name
|
||||
|
||||
if last_used_ts is not None:
|
||||
next_ts = int(last_used_ts) + PENIS_COOLDOWN_SECONDS
|
||||
if now_ts < next_ts:
|
||||
# Даже при кулдауне обновляем имя, чтобы топ показывал актуальный ник.
|
||||
if row is not None and row["display_name"] != display_name:
|
||||
conn.execute(
|
||||
"UPDATE penis_stats SET display_name = ? WHERE user_id = ?",
|
||||
(display_name, user_id),
|
||||
)
|
||||
left_seconds = next_ts - now_ts
|
||||
hours = left_seconds // 3600
|
||||
minutes = (left_seconds % 3600) // 60
|
||||
conn.commit()
|
||||
last_used_at = state.get("last_used_at")
|
||||
if isinstance(last_used_at, datetime):
|
||||
next_time = last_used_at + PENIS_COOLDOWN
|
||||
if now < next_time:
|
||||
# Если рано, считаем оставшееся время до следующей попытки.
|
||||
left = next_time - now
|
||||
total_seconds = int(left.total_seconds())
|
||||
hours = total_seconds // 3600
|
||||
minutes = (total_seconds % 3600) // 60
|
||||
return False, f"Сегодня уже кидал. Попробуй через {hours} ч {minutes} мин."
|
||||
|
||||
current_length = float(state.get("length", PENIS_START_LENGTH))
|
||||
delta = round(random.uniform(PENIS_MIN_DELTA, PENIS_MAX_DELTA), 1)
|
||||
sign = random.choice([-1, 1])
|
||||
# Не даём длине уйти в отрицательные значения.
|
||||
new_length = round(max(0.1, current_length + sign * delta), 1)
|
||||
|
||||
# Для нового пользователя создаём строку, для существующего — обновляем.
|
||||
if row is None:
|
||||
conn.execute(
|
||||
"INSERT INTO penis_stats(user_id, display_name, length, last_used_ts) VALUES(?, ?, ?, ?)",
|
||||
(user_id, display_name, new_length, now_ts),
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
"UPDATE penis_stats SET display_name = ?, length = ?, last_used_ts = ? WHERE user_id = ?",
|
||||
(display_name, new_length, now_ts, user_id),
|
||||
)
|
||||
conn.commit()
|
||||
except sqlite3.Error:
|
||||
logger.exception("Failed to process /penis")
|
||||
return False, "Ошибка БД. Попробуй позже."
|
||||
state["length"] = new_length
|
||||
state["last_used_at"] = now
|
||||
|
||||
sign_text = "+" if sign > 0 else "-"
|
||||
return (
|
||||
|
|
@ -510,32 +454,23 @@ def format_penis_user_name(user: types.User) -> str:
|
|||
|
||||
def build_penis_top(limit: int = PENIS_TOP_LIMIT) -> str:
|
||||
# Формируем динамический топ по текущим значениям /penis.
|
||||
try:
|
||||
with sqlite3.connect(PENIS_DB_PATH) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT user_id, display_name, length
|
||||
FROM penis_stats
|
||||
ORDER BY length DESC, user_id ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
(limit,),
|
||||
).fetchall()
|
||||
except sqlite3.Error:
|
||||
logger.exception("Failed to build /top_penis")
|
||||
return "Не удалось загрузить топ. Ошибка БД."
|
||||
rows: list[tuple[float, str]] = []
|
||||
for user_id, state in PENIS_STATE.items():
|
||||
length = state.get("length")
|
||||
if not isinstance(length, (int, float)):
|
||||
continue
|
||||
display_name = state.get("display_name")
|
||||
if not isinstance(display_name, str) or not display_name.strip():
|
||||
display_name = f"user_{user_id}"
|
||||
rows.append((float(length), display_name.strip()))
|
||||
|
||||
if not rows:
|
||||
return "🏆 Топ пуст. Сначала пусть кто-то крутнет /penis."
|
||||
|
||||
rows.sort(key=lambda item: item[0], reverse=True)
|
||||
medals = {1: "🥇", 2: "🥈", 3: "🥉"}
|
||||
lines = ["🏆 Топ по длине:"]
|
||||
for index, row in enumerate(rows, start=1):
|
||||
user_id = int(row["user_id"])
|
||||
length = float(row["length"])
|
||||
name = row["display_name"] if isinstance(row["display_name"], str) else f"user_{user_id}"
|
||||
name = name.strip() if name.strip() else f"user_{user_id}"
|
||||
for index, (length, name) in enumerate(rows[:limit], start=1):
|
||||
prefix = medals.get(index, f"{index}.")
|
||||
lines.append(f"{prefix} {name} — {length:.1f} см")
|
||||
return "\n".join(lines)
|
||||
|
|
@ -778,16 +713,12 @@ def main():
|
|||
if not token:
|
||||
raise RuntimeError("BOT_TOKEN env var is required")
|
||||
|
||||
# Готовим SQLite-таблицу для /penis и /top_penis.
|
||||
init_penis_db()
|
||||
|
||||
bot = Bot(token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||
dp = Dispatcher()
|
||||
|
||||
async def on_startup(bot: Bot):
|
||||
# Выставляем меню команд в интерфейсе Telegram.
|
||||
try:
|
||||
<<<<<<< HEAD
|
||||
await bot.set_my_commands(
|
||||
[
|
||||
BotCommand(command="start", description="Приветствие"),
|
||||
|
|
@ -805,30 +736,7 @@ def main():
|
|||
BotCommand(command="svodka", description="СВО: итоги"),
|
||||
|
||||
]
|
||||
=======
|
||||
commands = [
|
||||
BotCommand(command="start", description="Приветствие"),
|
||||
BotCommand(command="help", description="Подсказки по командам"),
|
||||
BotCommand(command="maket", description="Вписать текст в макет 1"),
|
||||
BotCommand(command="gen_mem", description="Мем из фото (ответом)"),
|
||||
BotCommand(command="break", description="Сколько минут до перекура"),
|
||||
BotCommand(command="size", description="хуй"),
|
||||
BotCommand(command="penis", description="миниигра 1 раз в 24ч"),
|
||||
BotCommand(command="top_penis", description="топ по длине"),
|
||||
BotCommand(command="prices", description="цены x-server"),
|
||||
BotCommand(command="fetch", description="цены (новый источник)"),
|
||||
BotCommand(command="zvetok", description="Цветянский бля"),
|
||||
BotCommand(command="talk", description="Побазарить"),
|
||||
]
|
||||
# Явно задаём команды для default/private/group, чтобы меню через "/" появлялось стабильно.
|
||||
scopes = (
|
||||
BotCommandScopeDefault(),
|
||||
BotCommandScopeAllPrivateChats(),
|
||||
BotCommandScopeAllGroupChats(),
|
||||
>>>>>>> Zhirik1337-wpng
|
||||
)
|
||||
for scope in scopes:
|
||||
await bot.set_my_commands(commands, scope=scope)
|
||||
except Exception: # noqa: WPS440
|
||||
logger.warning("Не удалось выставить команды, но бот продолжит работу.")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue