""" МАРКЕТ ЛКМ37 (v2.0) Доска объявлений с поддержкой фото и удобной навигацией. """ import logging from datetime import datetime, timedelta from aiogram import Router, F from aiogram.types import Message, CallbackQuery, InputMediaPhoto from aiogram.filters import Command, StateFilter from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from database.db import AsyncSessionLocal from database.models import Ad, User from utils.ui_styles import fmt_msg from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton import config logger = logging.getLogger(__name__) router = Router() class AdState(StatesGroup): waiting_for_category = State() waiting_for_title = State() waiting_for_description = State() waiting_for_price = State() waiting_for_photo = State() # ============================================================================= # КЛАВИАТУРЫ # ============================================================================= def get_ads_main_menu(): builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🛒 Все товары", callback_data="ads_view_all")) builder.row(InlineKeyboardButton(text="➕ Создать лот", callback_data="ads_create")) builder.row(InlineKeyboardButton(text="👤 Мои лоты", callback_data="ads_my_items")) return builder.as_markup() def get_categories_kb(): builder = InlineKeyboardBuilder() cats = [ ("Продам", "sell"), ("Куплю", "buy"), ("Отдам", "give"), ("Услуги", "service") ] for name, code in cats: builder.add(InlineKeyboardButton(text=name, callback_data=f"adcat_{code}")) builder.adjust(2) return builder.as_markup() # ============================================================================= # ХЕНДЛЕРЫ # ============================================================================= @router.message(Command("market")) async def cmd_market(message: Message): """Главный вход в маркет""" text = ( f"{fmt_msg('МАРКЕТ ЛКМ37', 'system')}\n\n" "Здесь вы можете найти товары от соседей или предложить свои.\n" "Все лоты проходят модерацию." ) await message.answer(text, reply_markup=get_ads_main_menu()) @router.callback_query(F.data == "ads_create") async def start_ad_creation(callback: CallbackQuery, state: FSMContext): await callback.message.answer("Выберите категорию для вашего лота:", reply_markup=get_categories_kb()) await state.set_state(AdState.waiting_for_category) await callback.answer() @router.callback_query(AdState.waiting_for_category, F.data.startswith("adcat_")) async def process_cat(callback: CallbackQuery, state: FSMContext): cat = callback.data.split("_")[1] await state.update_data(category=cat) await callback.message.answer("Введите краткое название (заголовок) товара:") await state.set_state(AdState.waiting_for_title) await callback.answer() @router.message(AdState.waiting_for_title) async def process_title(message: Message, state: FSMContext): await state.update_data(title=message.text) await message.answer("Опишите товар подробнее (состояние, причина продажи и т.д.):") await state.set_state(AdState.waiting_for_description) @router.message(AdState.waiting_for_description) async def process_desc(message: Message, state: FSMContext): await state.update_data(description=message.text) await message.answer("Укажите цену в рублях (или 0 если даром):") await state.set_state(AdState.waiting_for_price) @router.message(AdState.waiting_for_price) async def process_price(message: Message, state: FSMContext): if not message.text.isdigit(): return await message.answer("Пожалуйста, введите только число.") await state.update_data(price=int(message.text)) await message.answer("Пришлите фото товара (или нажмите /skip если нет фото):") await state.set_state(AdState.waiting_for_photo) @router.message(AdState.waiting_for_photo, F.photo) async def process_photo(message: Message, state: FSMContext): photo_id = message.photo[-1].file_id data = await state.get_data() await save_ad(message, data, photo_id) await state.clear() @router.message(AdState.waiting_for_photo, Command("skip")) async def skip_photo(message: Message, state: FSMContext): data = await state.get_data() await save_ad(message, data, None) await state.clear() async def save_ad(message, data, photo_id): async with AsyncSessionLocal() as session: new_ad = Ad( user_id=message.from_user.id, category=data['category'], title=data['title'], description=data['description'], price=data['price'], photo_file_id=photo_id, expires_at=datetime.utcnow() + timedelta(days=30) ) session.add(new_ad) await session.commit() await message.answer( f"{fmt_msg('ЛОТ СОЗДАН', 'system')}\n\n" "Ваше объявление отправлено на модерацию. После одобрения оно появится в общем списке." ) @router.callback_query(F.data == "ads_view_all") async def view_ads(callback: CallbackQuery, page: int = 0): async with AsyncSessionLocal() as session: stmt = select(Ad).where(Ad.is_active == True, Ad.is_moderated == True).offset(page).limit(1) res = await session.execute(stmt) ad = res.scalar_one_or_none() if not ad: return await callback.message.answer("К сожалению, пока лотов нет.") price_text = f"{ad.price} ₽" if ad.price > 0 else "ДАРOM" text = ( f"{ad.title}\n\n" f"💰 Цена: {price_text}\n" f"📝 {ad.description}\n\n" f"👤 Продавец: ID{ad.user_id}" ) kb = InlineKeyboardBuilder() kb.row(InlineKeyboardButton(text="📧 Написать продавцу", url=f"tg://user?id={ad.user_id}")) if not ad.reserved_by: kb.row(InlineKeyboardButton(text="🤝 БРОНИРОВАТЬ", callback_data=f"ad_reserve_{ad.ad_id}")) else: kb.row(InlineKeyboardButton(text="🤝 ЗАБРОНИРОВАНО", callback_data="ad_reserved_info")) kb.row( InlineKeyboardButton(text="⬅️", callback_data=f"ad_page_{page-1}"), InlineKeyboardButton(text="➡️", callback_data=f"ad_page_{page+1}") ) ... @router.callback_query(F.data.startswith("ad_reserve_")) async def reserve_ad(callback: CallbackQuery): ad_id = int(callback.data.split("_")[2]) async with AsyncSessionLocal() as session: ad = await session.get(Ad, ad_id) if ad and not ad.reserved_by: ad.reserved_by = callback.from_user.id await session.commit() # Уведомляем продавца try: await callback.bot.send_message( ad.user_id, f"🤝 Ваш товар забронирован!\n\n" f"Товар: {ad.title}\n" f"Покупатель: ID{callback.from_user.id} (@{callback.from_user.username or '—'})", parse_mode='HTML' ) except: pass await callback.answer("✅ Товар забронирован. Свяжитесь с продавцом!", show_alert=True) await callback.message.edit_reply_markup(reply_markup=None) # Обновить бы на "Забронировано" else: await callback.answer("❌ Товар уже забронирован или не найден.") kb.row(InlineKeyboardButton(text="🏠 В меню", callback_data="ads_back")) if ad.photo_file_id: await callback.message.answer_photo(ad.photo_file_id, caption=text, reply_markup=kb.as_markup()) else: await callback.message.answer(text, reply_markup=kb.as_markup()) await callback.answer()