Function bodies 69 total
Settings class · python · L9-L31 (23 LOC)bot/config.py
class Settings(BaseSettings):
# Telegram
bot_token: str
# kie.ai
kie_api_key: str
kie_api_url: str = "https://kie.ai"
# OpenRouter (замена OpenAI для работы из РФ)
openrouter_api_key: str
openrouter_base_url: str = "https://openrouter.ai/api/v1"
# YooKassa
yookassa_shop_id: str
yookassa_secret_key: str
yookassa_return_url: str = "https://t.me/photoshoot_generator_bot"
# Settings
debug: bool = False
class Config:
env_file = ".env"
env_file_encoding = "utf-8"Config class · python · L29-L31 (3 LOC)bot/config.py
class Config:
env_file = ".env"
env_file_encoding = "utf-8"CreditPackage class · python · L45-L52 (8 LOC)bot/config.py
class CreditPackage:
"""Пакет генераций для покупки"""
id: str
credits: int
price_rub: int
price_kopecks: int
label: strget_package_by_id function · python · L80-L85 (6 LOC)bot/config.py
def get_package_by_id(package_id: str) -> CreditPackage | None:
"""Возвращает пакет по его ID"""
for pkg in CREDIT_PACKAGES:
if pkg.id == package_id:
return pkg
return Nonebuild_system_prompt function · python · L475-L478 (4 LOC)bot/config.py
def build_system_prompt(gender: str, style: str) -> str:
"""Собирает полный системный промпт из базы + стиль одежды"""
style_section = STYLE_PROMPTS.get((gender, style), PROMPT_CASUAL_MALE)
return PROMPT_BASE + style_section + PROMPT_DIVERSITY_SUFFIXshow_packages function · python · L41-L70 (30 LOC)bot/handlers/payment.py
async def show_packages(callback: CallbackQuery) -> None:
"""Показывает доступные пакеты генераций"""
await callback.answer()
user_id = callback.from_user.id
paid = get_paid_credits(user_id)
text = "💳 <b>Пакеты генераций</b>\n\n"
for pkg in CREDIT_PACKAGES:
per_unit = pkg.price_rub / pkg.credits
text += f"• <b>{pkg.label}</b> ({per_unit:.0f} ₽/шт)\n"
if paid > 0:
text += (
f"\n📊 У тебя сейчас: {paid} оплаченных генераций"
)
# Если кнопка была на сообщении с фото — edit_text невозможен,
# отправляем новое сообщение
if callback.message.photo:
await callback.message.answer(
text,
reply_markup=get_packages_keyboard(),
)
else:
await callback.message.edit_text(
text,
reply_markup=get_packages_keyboard(),
)select_package function · python · L74-L97 (24 LOC)bot/handlers/payment.py
async def select_package(callback: CallbackQuery) -> None:
"""Пользователь выбрал пакет — показываем подтверждение"""
await callback.answer()
package_id = callback.data.split(":")[1]
pkg = get_package_by_id(package_id)
if not pkg:
await callback.message.edit_text(
"Пакет не найден. Попробуй ещё раз."
)
return
text = (
f"📦 <b>Подтверждение покупки</b>\n\n"
f"Пакет: <b>{pkg.credits} генераций</b>\n"
f"Стоимость: <b>{pkg.price_rub} ₽</b>\n\n"
f"Нажми «Оплатить» для продолжения."
)
await callback.message.edit_text(
text,
reply_markup=get_confirm_package_keyboard(package_id),
)All rows above produced by Repobility · https://repobility.com
confirm_buy function · python · L101-L173 (73 LOC)bot/handlers/payment.py
async def confirm_buy(
callback: CallbackQuery, bot: Bot
) -> None:
"""
Подтверждение покупки — создаёт платёж в YooKassa
и отправляет ссылку на оплату.
"""
await callback.answer()
user_id = callback.from_user.id
package_id = callback.data.split(":")[1]
pkg = get_package_by_id(package_id)
if not pkg:
await callback.message.edit_text("Пакет не найден.")
return
# Создаём запись в нашей БД
internal_id = create_payment(
user_id=user_id,
package_id=pkg.id,
credits=pkg.credits,
amount=pkg.price_kopecks,
)
# Создаём платёж в YooKassa
try:
yookassa_id, payment_url = await create_yookassa_payment(
amount_kopecks=pkg.price_kopecks,
description=f"{pkg.credits} генераций фото",
user_id=user_id,
package_id=pkg.id,
internal_payment_id=internal_id,
)
except Exception as e:
logger.error(
f"Yoocheck_payment_status function · python · L177-L260 (84 LOC)bot/handlers/payment.py
async def check_payment_status(callback: CallbackQuery) -> None:
"""Ручная проверка статуса платежа по кнопке"""
await callback.answer("Проверяю статус оплаты...")
internal_id = int(callback.data.split(":")[1])
user_id = callback.from_user.id
payment = get_payment(internal_id)
if not payment:
await callback.message.edit_text(
"❌ Платёж не найден."
)
return
if payment["status"] == "confirmed":
# Уже подтверждён (возможно фоновым polling)
remaining = get_remaining_generations(user_id)
await callback.message.edit_text(
f"✅ <b>Оплата уже подтверждена!</b>\n\n"
f"Доступно генераций: <b>{remaining}</b>",
reply_markup=get_after_payment_keyboard(),
)
return
if payment["status"] != "pending":
await callback.message.edit_text(
"❌ Платёж отменён или истёк.\n"
"Попробуй оформить новый.",
reply_markup=get_packback_from_packages function · python · L264-L274 (11 LOC)bot/handlers/payment.py
async def back_from_packages(
callback: CallbackQuery, state: FSMContext
) -> None:
"""Возврат из экрана пакетов"""
await callback.answer()
await callback.message.edit_text(
"Выбери стиль фотографии:",
reply_markup=get_gender_keyboard(),
)
await state.set_state(GenerationStates.selecting_gender)_poll_payment function · python · L277-L365 (89 LOC)bot/handlers/payment.py
async def _poll_payment(
bot: Bot,
internal_id: int,
yookassa_id: str,
user_id: int,
pkg,
) -> None:
"""
Фоновая задача: периодически проверяет статус платежа
в YooKassa и при успехе зачисляет кредиты.
"""
elapsed = 0
while elapsed < _POLL_MAX_DURATION:
await asyncio.sleep(_POLL_INTERVAL)
elapsed += _POLL_INTERVAL
# Проверяем, не подтверждён ли уже (вручную)
payment = get_payment(internal_id)
if not payment or payment["status"] != "pending":
logger.debug(
f"Poll: payment {internal_id} "
f"is no longer pending, stopping"
)
return
try:
status = await check_yookassa_payment(yookassa_id)
except Exception as e:
logger.warning(
f"Poll: YooKassa check error "
f"for {yookassa_id}: {e}"
)
continue
if status == "succeeded":
succhandle_photo function · python · L24-L171 (148 LOC)bot/handlers/photo.py
async def handle_photo(
message: Message, state: FSMContext, bot: Bot
) -> None:
"""Обработчик получения фото"""
user_id = message.from_user.id
# Проверяем лимит генераций
if not can_generate(user_id):
await message.answer(
"К сожалению, все генерации использованы 😔\n\n"
"Купи пакет генераций, чтобы продолжить!",
reply_markup=get_buy_keyboard(),
)
await state.clear()
return
await state.set_state(GenerationStates.processing)
# Показываем оставшиеся генерации
remaining = get_remaining_generations(user_id)
remaining_text = (
""
if remaining == -1
else f"\n(Осталось генераций: {remaining - 1})"
)
# Отправляем сообщение о начале обработки
processing_msg = await message.answer(
"Фото получено! Создаю профессиональный портрет...\n"
f"Это может занять 1-2 минуты.{remaining_text}"
)
try:
# Получаем данные из состояния
handle_photo_without_state function · python · L175-L185 (11 LOC)bot/handlers/photo.py
async def handle_photo_without_state(
message: Message, state: FSMContext
) -> None:
"""Обработчик фото без выбранного стиля"""
from bot.keyboards.inline import get_gender_keyboard
await message.answer(
"Сначала выбери стиль фотографии:",
reply_markup=get_gender_keyboard(),
)
await state.set_state(GenerationStates.selecting_gender)handle_not_photo function · python · L189-L195 (7 LOC)bot/handlers/photo.py
async def handle_not_photo(message: Message) -> None:
"""Обработчик не-фото сообщений в состоянии ожидания фото"""
await message.answer(
"Пожалуйста, отправь фотографию.\n"
"Лучше всего подойдёт портретное фото, "
"где хорошо видно лицо."
)handle_message_while_processing function · python · L199-L205 (7 LOC)bot/handlers/photo.py
async def handle_message_while_processing(
message: Message,
) -> None:
"""Обработчик сообщений во время обработки"""
await message.answer(
"Подожди, я ещё обрабатываю предыдущее фото..."
)Open data scored by Repobility · https://repobility.com
cmd_start function · python · L33-L76 (44 LOC)bot/handlers/start.py
async def cmd_start(message: Message, state: FSMContext, command: CommandObject) -> None:
"""Обработчик команды /start"""
await state.clear()
user_id = message.from_user.id
# Сохраняем источник перехода (диплинк)
source = command.args
if source:
save_referral(user_id, source)
logger.info(f"User {user_id} came from: {source}")
remaining = get_remaining_generations(user_id)
# Формируем текст о лимите
if is_admin(user_id):
limit_text = "👑 У тебя безлимитный доступ."
elif remaining > 0:
paid = get_paid_credits(user_id)
if paid > 0:
limit_text = f"💳 У тебя {remaining} генераций ({paid} оплаченных)."
else:
limit_text = "🎁 У тебя 1 бесплатная генерация."
else:
limit_text = (
"⚠️ Генерации закончились. "
"Купи пакет, чтобы продолжить!"
)
welcome_text = (
"Привет! Я помогу превратить твоё фото "
"в профессиональный стcmd_stats function · python · L80-L97 (18 LOC)bot/handlers/start.py
async def cmd_stats(message: Message) -> None:
"""Статистика источников трафика (только для админа)"""
if not is_admin(message.from_user.id):
return
stats = get_referral_stats()
if not stats:
await message.answer("Нет данных о переходах.")
return
total = sum(count for _, count in stats)
lines = ["📊 *Источники трафика:*\n"]
for source, count in stats:
pct = round(count / total * 100)
lines.append(f"• `{source}`: {count} чел. ({pct}%)")
lines.append(f"\nВсего: {total}")
await message.answer("\n".join(lines), parse_mode="Markdown")restart_generation function · python · L101-L112 (12 LOC)bot/handlers/start.py
async def restart_generation(
callback: CallbackQuery, state: FSMContext
) -> None:
"""Обработчик кнопки 'Создать ещё'"""
await callback.answer()
await state.clear()
await callback.message.answer(
"Выбери пол:",
reply_markup=get_gender_keyboard(),
)
await state.set_state(GenerationStates.selecting_gender)regenerate_photo function · python · L116-L261 (146 LOC)bot/handlers/start.py
async def regenerate_photo(
callback: CallbackQuery, state: FSMContext
) -> None:
"""Обработчик кнопки 'Сгенерировать заново'"""
await callback.answer()
user_id = callback.from_user.id
# Проверяем лимит генераций
if not can_generate(user_id):
await callback.message.answer(
"К сожалению, все генерации использованы 😔\n\n"
"Купи пакет генераций, чтобы продолжить!",
reply_markup=get_buy_keyboard(),
)
await state.clear()
return
# Получаем последнюю фотографию
photo_url, gender, style = get_last_photo(user_id)
if not photo_url or not gender:
await callback.message.answer(
"У меня нет сохранённой фотографии. "
"Пожалуйста, отправь новое фото.",
reply_markup=get_gender_keyboard(),
)
await state.set_state(GenerationStates.selecting_gender)
return
style = style or "casual"
# Импортируем здесь, чтобы избежать циклselect_gender function · python · L267-L282 (16 LOC)bot/handlers/start.py
async def select_gender(
callback: CallbackQuery, state: FSMContext
) -> None:
"""Обработчик выбора пола → переход к выбору стиля"""
await callback.answer()
gender = callback.data.split(":")[1] # male или female
await state.update_data(gender=gender)
gender_text = "мужской" if gender == "male" else "женский"
await callback.message.edit_text(
f"Пол: {gender_text}\n\n"
"Теперь выбери стиль одежды:",
reply_markup=get_style_keyboard(),
)
await state.set_state(GenerationStates.selecting_style)select_style function · python · L288-L307 (20 LOC)bot/handlers/start.py
async def select_style(
callback: CallbackQuery, state: FSMContext
) -> None:
"""Обработчик выбора стиля одежды → переход к загрузке фото"""
await callback.answer()
style = callback.data.split(":")[1] # business, casual, creative
await state.update_data(style=style)
data = await state.get_data()
gender = data.get("gender", "male")
gender_text = "мужской" if gender == "male" else "женский"
style_text = STYLE_LABELS.get(style, style)
await callback.message.edit_text(
f"Пол: {gender_text}, стиль: {style_text}\n\n"
"Теперь отправь мне своё фото "
"(лучше всего портретное, где хорошо видно лицо)."
)
await state.set_state(GenerationStates.awaiting_photo)get_gender_keyboard function · python · L6-L15 (10 LOC)bot/keyboards/inline.py
def get_gender_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура выбора пола"""
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(text="👨 Мужской", callback_data="gender:male"),
InlineKeyboardButton(text="👩 Женский", callback_data="gender:female"),
]
]
)get_style_keyboard function · python · L18-L26 (9 LOC)bot/keyboards/inline.py
def get_style_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура выбора стиля одежды"""
return InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="👔 Деловой", callback_data="style:business")],
[InlineKeyboardButton(text="👕 Кежуал", callback_data="style:casual")],
[InlineKeyboardButton(text="🎨 Креативный", callback_data="style:creative")],
]
)About: code-quality intelligence by Repobility · https://repobility.com
get_restart_keyboard function · python · L29-L49 (21 LOC)bot/keyboards/inline.py
def get_restart_keyboard(
has_last_photo: bool = False, has_credits: bool = True
) -> InlineKeyboardMarkup:
"""Клавиатура для повторной генерации"""
buttons = []
if has_last_photo:
buttons.append([
InlineKeyboardButton(text="🔄 Сгенерировать заново", callback_data="regenerate"),
])
buttons.append([
InlineKeyboardButton(text="✨ Создать с новым фото", callback_data="restart"),
])
if not has_credits:
buttons.append([
InlineKeyboardButton(text="💳 Купить генерации", callback_data="buy_credits"),
])
return InlineKeyboardMarkup(inline_keyboard=buttons)get_buy_keyboard function · python · L52-L59 (8 LOC)bot/keyboards/inline.py
def get_buy_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура с кнопкой покупки (когда лимит исчерпан)"""
return InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="💳 Купить генерации", callback_data="buy_credits")],
[InlineKeyboardButton(text="✨ Создать с новым фото", callback_data="restart")],
]
)get_packages_keyboard function · python · L62-L75 (14 LOC)bot/keyboards/inline.py
def get_packages_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура выбора пакета генераций"""
buttons = []
for pkg in CREDIT_PACKAGES:
buttons.append([
InlineKeyboardButton(
text=pkg.label,
callback_data=f"package:{pkg.id}",
)
])
buttons.append([
InlineKeyboardButton(text="« Назад", callback_data="back_from_packages"),
])
return InlineKeyboardMarkup(inline_keyboard=buttons)get_confirm_package_keyboard function · python · L78-L91 (14 LOC)bot/keyboards/inline.py
def get_confirm_package_keyboard(package_id: str) -> InlineKeyboardMarkup:
"""Клавиатура подтверждения покупки пакета"""
return InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(
text="Оплатить",
callback_data=f"confirm_buy:{package_id}",
)],
[InlineKeyboardButton(
text="« Выбрать другой пакет",
callback_data="buy_credits",
)],
]
)get_payment_url_keyboard function · python · L94-L113 (20 LOC)bot/keyboards/inline.py
def get_payment_url_keyboard(
payment_url: str, payment_id: int
) -> InlineKeyboardMarkup:
"""Клавиатура со ссылкой на оплату и кнопкой проверки"""
return InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(
text="💳 Перейти к оплате",
url=payment_url,
)],
[InlineKeyboardButton(
text="✅ Проверить оплату",
callback_data=f"check_payment:{payment_id}",
)],
[InlineKeyboardButton(
text="« Отмена",
callback_data="buy_credits",
)],
]
)get_after_payment_keyboard function · python · L116-L125 (10 LOC)bot/keyboards/inline.py
def get_after_payment_keyboard() -> InlineKeyboardMarkup:
"""Клавиатура после успешной оплаты"""
return InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(
text="📸 Создать фото",
callback_data="restart",
)],
]
)setup_logging function · python · L15-L22 (8 LOC)bot/main.py
def setup_logging() -> None:
"""Настройка логирования"""
level = logging.DEBUG if settings.debug else logging.INFO
logging.basicConfig(
level=level,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)main function · python · L25-L52 (28 LOC)bot/main.py
async def main() -> None:
"""Точка входа"""
setup_logging()
logger = logging.getLogger(__name__)
logger.info("Starting bot...")
# Инициализируем БД
init_db()
# Создаём бота и диспетчер
bot = Bot(
token=settings.bot_token,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
dp = Dispatcher(storage=MemoryStorage())
# Регистрируем роутеры
dp.include_router(start.router)
dp.include_router(payment.router)
dp.include_router(photo.router)
# Запускаем бота
try:
logger.info("Bot started successfully!")
await dp.start_polling(bot)
finally:
await bot.session.close()Source: Repobility analyzer · https://repobility.com
KieClientError class · python · L12-L14 (3 LOC)bot/services/kie_client.py
class KieClientError(Exception):
"""Ошибка клиента kie.ai"""
passKieClient class · python · L17-L191 (175 LOC)bot/services/kie_client.py
class KieClient:
"""Клиент для работы с kie.ai API"""
def __init__(self):
self.base_url = settings.kie_api_url
self.api_key = settings.kie_api_key
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
async def create_task(
self,
image_url: str,
prompt: str,
output_format: str = "jpeg",
image_size: str = "auto",
) -> str:
"""
Создаёт задачу на генерацию изображения.
Args:
image_url: URL исходного изображения
prompt: Промпт для трансформации
output_format: Формат выхода (jpeg/png)
image_size: Соотношение сторон
Returns:
str: ID созданной задачи
"""
url = f"{self.base_url}/api/v1/jobs/createTask"
payload = {
"model": "google/nano-banana-edit",
"input": {
"prompt": prompt,
__init__ method · python · L20-L26 (7 LOC)bot/services/kie_client.py
def __init__(self):
self.base_url = settings.kie_api_url
self.api_key = settings.kie_api_key
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}create_task method · python · L28-L79 (52 LOC)bot/services/kie_client.py
async def create_task(
self,
image_url: str,
prompt: str,
output_format: str = "jpeg",
image_size: str = "auto",
) -> str:
"""
Создаёт задачу на генерацию изображения.
Args:
image_url: URL исходного изображения
prompt: Промпт для трансформации
output_format: Формат выхода (jpeg/png)
image_size: Соотношение сторон
Returns:
str: ID созданной задачи
"""
url = f"{self.base_url}/api/v1/jobs/createTask"
payload = {
"model": "google/nano-banana-edit",
"input": {
"prompt": prompt,
"image_urls": [image_url],
"output_format": output_format,
"image_size": image_size,
}
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, headers=self.headers) as resp:
get_task_status method · python · L81-L102 (22 LOC)bot/services/kie_client.py
async def get_task_status(self, task_id: str) -> dict:
"""
Получает статус задачи.
Args:
task_id: ID задачи
Returns:
dict: Информация о задаче (поле data из ответа)
"""
url = f"{self.base_url}/api/v1/jobs/recordInfo"
params = {"taskId": task_id}
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params, headers=self.headers) as resp:
if resp.status != 200:
text = await resp.text()
logger.error(f"Failed to get task status: {resp.status} - {text}")
raise KieClientError(f"Failed to get task status: {resp.status}")
response = await resp.json()
return response.get("data", {})wait_for_result method · python · L104-L153 (50 LOC)bot/services/kie_client.py
async def wait_for_result(
self,
task_id: str,
timeout: int = 300,
poll_interval: int = 3,
) -> str:
"""
Ожидает завершения задачи и возвращает URL результата.
Args:
task_id: ID задачи
timeout: Максимальное время ожидания в секундах
poll_interval: Интервал между проверками в секундах
Returns:
str: URL готового изображения
"""
import json
elapsed = 0
while elapsed < timeout:
data = await self.get_task_status(task_id)
logger.debug(f"Task {task_id} status: {data}")
state = data.get("state")
if state == "success":
# resultJson содержит JSON-строку: {"resultUrls": ["https://..."]}
result_json_str = data.get("resultJson")
if result_json_str:
try:
result_data = json.loads(result_json_str)
transform_photo method · python · L155-L175 (21 LOC)bot/services/kie_client.py
async def transform_photo(
self,
image_url: str,
prompt: str,
output_format: str = "jpeg",
image_size: str = "auto",
) -> str:
"""
Полный цикл трансформации фото: создание задачи и ожидание результата.
Args:
image_url: URL исходного изображения
prompt: Промпт для трансформации
output_format: Формат выхода (jpeg/png)
image_size: Соотношение сторон
Returns:
str: URL готового изображения
"""
task_id = await self.create_task(image_url, prompt, output_format, image_size)
return await self.wait_for_result(task_id)download_image method · python · L177-L191 (15 LOC)bot/services/kie_client.py
async def download_image(self, url: str) -> bytes:
"""
Скачивает изображение по URL.
Args:
url: URL изображения
Returns:
bytes: Содержимое изображения
"""
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status != 200:
raise KieClientError(f"Failed to download image: {resp.status}")
return await resp.read()All rows above produced by Repobility · https://repobility.com
OpenAIClientError class · python · L16-L19 (4 LOC)bot/services/openai_client.py
class OpenAIClientError(Exception):
"""Ошибка клиента OpenAI/OpenRouter"""
passOpenAIClient class · python · L22-L111 (90 LOC)bot/services/openai_client.py
class OpenAIClient:
"""Клиент для генерации промптов через OpenRouter API.
Совместим с OpenAI SDK.
"""
def __init__(self) -> None:
self.client = AsyncOpenAI(
api_key=settings.openrouter_api_key,
base_url=settings.openrouter_base_url,
)
# OpenRouter использует тот же формат модели
# GPT-5.2 для максимального качества генерации промптов
self.model = "openai/gpt-5.2"
async def generate_prompt(
self, gender: str, style: str = "casual", max_retries: int = 3
) -> str:
"""
Генерирует промпт для изображения на основе пола и стиля.
Args:
gender: "male" или "female"
style: "casual", "business" или "creative"
max_retries: максимальное количество попыток
Returns:
Сгенерированный промпт для kie.ai
"""
gender_text = "мужчины" if gender == "male" else "женщины"
style_text = STYLE_LABELS.get(style, "__init__ method · python · L28-L35 (8 LOC)bot/services/openai_client.py
def __init__(self) -> None:
self.client = AsyncOpenAI(
api_key=settings.openrouter_api_key,
base_url=settings.openrouter_base_url,
)
# OpenRouter использует тот же формат модели
# GPT-5.2 для максимального качества генерации промптов
self.model = "openai/gpt-5.2"generate_prompt method · python · L37-L111 (75 LOC)bot/services/openai_client.py
async def generate_prompt(
self, gender: str, style: str = "casual", max_retries: int = 3
) -> str:
"""
Генерирует промпт для изображения на основе пола и стиля.
Args:
gender: "male" или "female"
style: "casual", "business" или "creative"
max_retries: максимальное количество попыток
Returns:
Сгенерированный промпт для kie.ai
"""
gender_text = "мужчины" if gender == "male" else "женщины"
style_text = STYLE_LABELS.get(style, "кежуал")
system_prompt = build_system_prompt(gender, style)
user_message = (
f"Сгенерируй один уникальный промпт для профессионального "
f"студийного портрета {gender_text} в стиле «{style_text}». "
f"Следуй структуре промпта из гайдлайнов. "
f"ВАЖНО: Выбери РАЗНЫЕ предметы одежды, цвет и текстуру, "
f"чем в предыдущих примерах. "
f"Создай оригинальную комбин_get_db_path function · python · L26-L30 (5 LOC)bot/services/user_limits.py
def _get_db_path() -> Path:
"""Возвращает путь к файлу БД"""
if _PROD_DIR.exists():
return _PROD_DB
return _LOCAL_DB_get_json_path function · python · L33-L37 (5 LOC)bot/services/user_limits.py
def _get_json_path() -> Path:
"""Возвращает путь к старому JSON-файлу (для миграции)"""
if _PROD_DIR.exists():
return _PROD_JSON
return _LOCAL_JSON_get_conn function · python · L40-L42 (3 LOC)bot/services/user_limits.py
def _get_conn() -> sqlite3.Connection:
"""Возвращает соединение с БД"""
return sqlite3.connect(_get_db_path())init_db function · python · L45-L104 (60 LOC)bot/services/user_limits.py
def init_db() -> None:
"""Инициализирует БД и мигрирует данные из JSON, если он существует"""
with _get_conn() as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
generations INTEGER NOT NULL DEFAULT 0,
last_photo_url TEXT,
last_gender TEXT
)
""")
# Миграция: добавляем колонку paid_credits
try:
conn.execute(
"ALTER TABLE users ADD COLUMN paid_credits INTEGER NOT NULL DEFAULT 0"
)
logger.info("Added paid_credits column to users table")
except sqlite3.OperationalError:
pass # Колонка уже существует
# Миграция: добавляем колонку last_style
try:
conn.execute(
"ALTER TABLE users ADD COLUMN last_style TEXT"
)
logger.info("Added last_style column to users table")
except sqlite3.OpeOpen data scored by Repobility · https://repobility.com
_migrate_from_json function · python · L107-L147 (41 LOC)bot/services/user_limits.py
def _migrate_from_json(json_path: Path) -> None:
"""Мигрирует данные из старого JSON-файла в SQLite"""
try:
data = json.loads(json_path.read_text())
except Exception as e:
logger.error(f"Failed to read JSON for migration: {e}")
return
migrated = 0
with _get_conn() as conn:
for user_key, user_data in data.items():
try:
user_id = int(user_key)
except ValueError:
continue
# Поддержка старого формата (просто число)
if isinstance(user_data, int):
generations = user_data
last_photo_url = None
last_gender = None
else:
generations = user_data.get("generations", 0)
last_photo_url = user_data.get("last_photo_url")
last_gender = user_data.get("last_gender")
conn.execute(
"""INSERT OR IGNORE INTO users
(user_id, is_admin function · python · L150-L152 (3 LOC)bot/services/user_limits.py
def is_admin(user_id: int) -> bool:
"""Проверяет, является ли пользователь админом"""
return user_id == ADMIN_IDget_generations_count function · python · L155-L162 (8 LOC)bot/services/user_limits.py
def get_generations_count(user_id: int) -> int:
"""Возвращает количество использованных бесплатных генераций"""
with _get_conn() as conn:
row = conn.execute(
"SELECT generations FROM users WHERE user_id = ?",
(user_id,),
).fetchone()
return row[0] if row else 0page 1 / 2next ›