forked from zovos/bot_tg
The old SCHEDULE dict was hand-written for a previous timetable and
never got updated when LESSON_SCHEDULE changed, so Wednesday (day=2)
was left empty ("Сегодня кайфуем дома, перекур не нужен") even though
the new schedule has 4 lessons that day. SCHEDULE is now generated
from the pairs actually present in _LESSON_WEEK plus PAIR_TIMES, so it
can't drift out of sync again.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
205 lines
10 KiB
Python
205 lines
10 KiB
Python
import os
|
||
from pathlib import Path
|
||
from datetime import timedelta, time
|
||
from zoneinfo import ZoneInfo
|
||
|
||
# --- Базовые пути ---
|
||
BASE_DIR = Path(__file__).parent
|
||
FONT_PATH = BASE_DIR / "impact.ttf"
|
||
DEFAULT_TZ = os.getenv("TZ", "Europe/Moscow")
|
||
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://bot:botpass@postgres:5432/botdb")
|
||
|
||
# --- Настройки /penis ---
|
||
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
|
||
|
||
# --- Настройки "получается" ---
|
||
POLYCHAETSI_STATS_PATH = Path(os.getenv("POLYCHAETSI_STATS_PATH", "/db/polychaetsi_stats.json"))
|
||
POLYCHAETSI_TARGET = "получается"
|
||
POLYCHAETSI_TOP_LIMIT = 10
|
||
POLYCHAETSI_IMAGE_PATH = BASE_DIR / "maket" / "cvetok.jpg"
|
||
|
||
# --- Казино ---
|
||
CASINO_MAX_BET = 1.0
|
||
|
||
# --- E621 API ---
|
||
E621_LOGIN = os.getenv("E621_LOGIN", "")
|
||
E621_API_KEY = os.getenv("E621_API_KEY", "")
|
||
|
||
# --- Ставки на матчи ---
|
||
ODDS_API_KEY = os.getenv("ODDS_API_KEY", "")
|
||
ODDS_API_BASE = "https://api.the-odds-api.com/v4"
|
||
BET_MAX_AMOUNT = 3.0
|
||
ODDS_SPORTS = {
|
||
"dota2": "esports_dota2",
|
||
}
|
||
ODDS_CACHE_TTL = 86400 # 24 часа — экономим запросы (500/мес бесплатно)
|
||
|
||
# --- Nude ---
|
||
NUDE_HISTORY_PATH = Path(os.getenv("NUDE_HISTORY_PATH", "/db/nude_history.json"))
|
||
NUDE_POSTS_PER_PAGE = int(os.getenv("NUDE_POSTS_PER_PAGE", "100"))
|
||
NUDE_POSTS_POOL_TARGET = int(os.getenv("NUDE_POSTS_POOL_TARGET", "1200"))
|
||
NUDE_MAX_POSTS_TO_SCAN = int(os.getenv("NUDE_MAX_POSTS_TO_SCAN", "2000"))
|
||
NUDE_MIN_UNSEEN_POOL = int(os.getenv("NUDE_MIN_UNSEEN_POOL", "100"))
|
||
NUDE_HISTORY_MAX_SIZE = int(os.getenv("NUDE_HISTORY_MAX_SIZE", "5000"))
|
||
|
||
# --- Magnit API ---
|
||
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_REQUEST_TIMEOUT = int(os.getenv("MAGNIT_REQUEST_TIMEOUT", "15"))
|
||
# Можно указывать просто строку с названием или словарь с query/label/store_code.
|
||
MAGNIT_PRODUCTS = [
|
||
"RedBull",
|
||
]
|
||
|
||
# --- Конфигурация макетов ---
|
||
TEMPLATES = {
|
||
"1": {
|
||
"path": BASE_DIR / "maket" / "1.png",
|
||
"anchor": (104, 240),
|
||
"padding": 20,
|
||
"label": "Владимир Владимирович Путин самый лучший президент",
|
||
},
|
||
"2": {
|
||
"path": BASE_DIR / "maket" / "2.png",
|
||
"anchor": (40, 40),
|
||
"box_size": (582, 346),
|
||
"label": "Макет 2",
|
||
},
|
||
}
|
||
|
||
# --- Расписание ---
|
||
def make_time(h: int, m: int) -> time:
|
||
return time(hour=h, minute=m)
|
||
|
||
# Если у тебя наоборот, просто поменяй на False.
|
||
UPPER_WEEK_IS_ODD = False
|
||
|
||
PAIR_TIMES = {
|
||
1: (make_time(8, 0), make_time(9, 35)),
|
||
2: (make_time(9, 50), make_time(11, 25)),
|
||
3: (make_time(11, 55), make_time(13, 30)),
|
||
4: (make_time(13, 45), make_time(15, 20)),
|
||
5: (make_time(15, 50), make_time(17, 25)),
|
||
}
|
||
|
||
# Расписание 2 курс, гр. 27 (25ВТ-09.03.03.01-о2), осень 2026-27.
|
||
# Источник: Raspis_osen_2_kurs_2026-27_g.pdf (черновик, водяной знак "Проект", возможны изменения).
|
||
# Разделения на верхнюю/нижнюю неделю в источнике нет — обе недели одинаковые.
|
||
_MON = [
|
||
{"pair": 2, "title": "Экономика и право (практика), Индустриев М.А. до 06.11 / Васильева Е.В. с 07.11, ауд. 504"},
|
||
{"pair": 3, "title": "Экономика и право (лекция), Туманян Ю.Р. до 06.11 / Васильева Е.В. с 07.11, ауд. 510"},
|
||
{"pair": 4, "title": "Геоинформационные системы (лекция), Ушканова Е.В., ауд. 403а (НИИМ и ПМ)"},
|
||
]
|
||
_TUE = [
|
||
{"pair": 3, "title": "Специальные разделы математики (практика), Вакулов Б.Г., ауд. 504"},
|
||
{"pair": 4, "title": "Геоинформационные системы (лаб), Ушканова Е.В., ауд. 403а (НИИМ и ПМ)"},
|
||
]
|
||
_WED = [
|
||
{"pair": 2, "title": "Технология программирования (лекция), Губский Д.С., ауд. 510"},
|
||
{"pair": 3, "title": "1 подгруппа: Основы функционирования вычислительной техники (лаб), Дацюк О.В., ауд. 606 | 2 подгруппа: Геоинформационные системы (лаб), Ушканова Е.В., ауд. 403а (НИИМ и ПМ)"},
|
||
{"pair": 4, "title": "1 подгруппа: Технология программирования (лаб), Губский Д.С., ауд. 609 | 2 подгруппа: Технология программирования (лаб), Иванова И.Н., ауд. 609а"},
|
||
{"pair": 5, "title": "Основы функционирования вычислительной техники (лаб), Дацюк О.В., ауд. 606"},
|
||
]
|
||
_THU = [
|
||
{"pair": 2, "title": "Основы функционирования вычислительной техники (лекция), Дацюк О.В., ауд. 510"},
|
||
{"pair": 3, "title": "1 подгруппа: Основы функционирования вычислительной техники (практика), Дацюк О.В., ауд. 818 | 2 подгруппа: Специальные разделы математики (практика), Вакулов Б.Г., ауд. 507"},
|
||
{"pair": 4, "title": "Специальные разделы математики (лекция), Каплицкий В.М., ауд. 510"},
|
||
]
|
||
_FRI = [
|
||
{"pair": None, "title": "Иностранный язык (время уточняется)"},
|
||
]
|
||
_SAT = [
|
||
{"pair": 1, "title": "Философия (лекция, совместно с гр. 26), Новохатько А.Г., MS Teams"},
|
||
]
|
||
|
||
_LESSON_WEEK = {
|
||
0: _MON,
|
||
1: _TUE,
|
||
2: _WED,
|
||
3: _THU,
|
||
4: _FRI,
|
||
5: _SAT,
|
||
}
|
||
|
||
LESSON_SCHEDULE = {
|
||
"upper": _LESSON_WEEK,
|
||
"lower": _LESSON_WEEK,
|
||
}
|
||
|
||
# SCHEDULE — тайминг перекуров для /break, построен из фактических пар в
|
||
# _LESSON_WEEK, а не задаётся руками, чтобы не рассинхронизироваться при
|
||
# правках расписания (раньше здесь была отдельная захардкоженная таблица,
|
||
# которая не обновилась вместе с LESSON_SCHEDULE и врала на днях с занятиями).
|
||
# Каждая пара (95 мин) состоит из двух академ. часов по 45 мин с 5-минутным
|
||
# перекуром между ними; между парами перекур — фактический зазор по PAIR_TIMES.
|
||
def _time_to_minutes(t: time) -> int:
|
||
return t.hour * 60 + t.minute
|
||
|
||
|
||
def _minutes_to_time(m: int) -> time:
|
||
return make_time(m // 60, m % 60)
|
||
|
||
|
||
def _build_day_schedule(pairs: list[int]) -> list[tuple[time, time, int | None]]:
|
||
entries = []
|
||
for idx, pair in enumerate(pairs):
|
||
start_t, end_t = PAIR_TIMES[pair]
|
||
start_m = _time_to_minutes(start_t)
|
||
end_m = _time_to_minutes(end_t)
|
||
mid_m = start_m + 45
|
||
second_start_m = mid_m + 5
|
||
entries.append((start_t, _minutes_to_time(mid_m), 5))
|
||
if idx == len(pairs) - 1:
|
||
entries.append((_minutes_to_time(second_start_m), end_t, None))
|
||
else:
|
||
next_start_t, _ = PAIR_TIMES[pairs[idx + 1]]
|
||
gap = _time_to_minutes(next_start_t) - end_m
|
||
entries.append((_minutes_to_time(second_start_m), end_t, gap))
|
||
return entries
|
||
|
||
|
||
def _pairs_for_day(day_lessons: list[dict]) -> list[int]:
|
||
return sorted({item["pair"] for item in day_lessons if item.get("pair") is not None})
|
||
|
||
|
||
SCHEDULE = {
|
||
day: _build_day_schedule(_pairs_for_day(lessons))
|
||
for day, lessons in _LESSON_WEEK.items()
|
||
}
|
||
|
||
# --- Остальное ---
|
||
KEYWORDS = {'крип', 'радиохэд', 'creep', 'radiohead'}
|
||
|
||
# --- Авто-ответы ИИ ---
|
||
AUTOREPLY_CHANCE = 0.10
|
||
AUTOREPLY_COOLDOWN = 120
|
||
AUTOREPLY_TRIGGERS = {'бот', 'фраер', 'кент', 'братуха', 'пацаны', 'чё как', 'базар', 'ебать', 'пизда', 'хуй', 'бля', 'сука', 'говно', 'пидор', 'пиздабол', 'пиздатый', 'пиздатая', 'пиздатое', 'пиздатые', 'пиздато', 'ваномас'}
|
||
|
||
def get_hint_text(templates_str: str) -> str:
|
||
return (
|
||
"Команды:\n"
|
||
f"• /maket «id» «текст» — доступные: {templates_str}.\n"
|
||
"• /gen_mem Up:верх Down:низ — отправь с фото.\n"
|
||
"• /break — узнать время до перекура.\n"
|
||
"• /schedule [пн|mon] — расписание; без аргумента еще текущая и следующая пара.\n"
|
||
"• /penis — миниигра 1 раз в 24 часа.\n"
|
||
"• /top_penis — топ по длине.\n"
|
||
"• /talk [текст] — ИИ базарит на фене.\n"
|
||
"• /penis_casino [ставка] — казино на размер.\n"
|
||
"• /gadanie [тема] — гадание на фене с мемом.\n"
|
||
"• /matches — матчи для ставок.\n"
|
||
"• /bet [номер] [исход] [ставка] — поставить.\n"
|
||
"• /mybets — мои активные ставки.\n"
|
||
"• /bet_history — история ставок за месяц + статистика.\n"
|
||
"• /sports_debug — диагностика API матчей.\n"
|
||
"• /ebalnik — включить/выключить автоответы в чате.\n"
|
||
"• /uwu [теги] — случайная картинка с e621.\n"
|
||
"• /polychaetsi — топ по слову «получается».\n"
|
||
"• /prices /fetch — цены и остатки Магнита.\n"
|
||
"Подсказка: в группах пиши /команда@username_бота."
|
||
)
|