commit 2d20dcc4733a577764cc66749f7e0b23416f0c51 Author: smolkik_adm Date: Sat Jan 10 15:37:36 2026 +0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fefd0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sbtarget/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# Медиа файлы +*.mp3 +*.mp4 +*.mp4.info.json +*.mp3.info.json +*.jpg +*.jpeg +*.png +*.gif +*.mov +*.avi +*.mkv +*.webm +*.flv + +# Кэш и временные файлы бота +/cache/ +/tmp/ +downloads/ +*.cache +*.tmp + +# Конфигурационные файлы с чувствительными данными +config.py +secrets.py +cookies.txt +*.key +*.pem +*.crt + +# Логи +*.log +logs/ + +# Операционные системы +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Редакторы +.vscode/ +.idea/ +*.swp +*.swo +*~ +**.wg0.conf \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/app.py b/app.py new file mode 100644 index 0000000..ad651ab --- /dev/null +++ b/app.py @@ -0,0 +1,125 @@ + import os, logging, io, random +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, LabeledPrice +from telegram.ext import Application, CommandHandler, CallbackQueryHandler, MessageHandler, PreCheckoutQueryHandler, filters, ContextTypes +from PIL import Image, ImageDraw, ImageFont +import requests +from payments import send_invoice +from dotenv import load_dotenv + +load_dotenv() +TOKEN = os.getenv('TELEGRAM_TOKEN') +OLLAMA_URL = os.getenv('OLLAMA_URL', 'http://localhost:11434') +BALANCES = {} # В проде → Redis + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +MEME_TEMPLATES = ['drake.jpg', 'this-is-fine.jpg', 'distracted-bf.jpg'] + +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): + keyboard = [ + [InlineKeyboardButton("🎨 Создать мем (25⭐)", callback_data='buy_meme')], + [InlineKeyboardButton("💰 Баланс", callback_data='balance')] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await update.message.reply_text( + '😺 IT Meme Pro Bot\n\n' + 'Генерирую IT-мемы за 25 Telegram Stars!\n' + 'Коты в проде, Kubernetes фейлы, DevOps драма\n\n' + 'Нажмите кнопку!', + reply_markup=reply_markup, parse_mode='HTML') + +async def generate_meme(prompt: str): + # Ollama текст + url = f"{OLLAMA_URL}/api/generate" + data = { + "model": "qwen2.5:3b", + "prompt": f"""Создай короткий смешной текст IT-мема (макс 12 слов) + на тему: "{prompt}". Стиль: коты в продакшене, devops-проблемы. Только текст!""", + "stream": False + } + resp = requests.post(url, json=data, timeout=30).json() + meme_text = resp['response'].strip() + + # Pillow изображение + template = Image.open(f'templates/{random.choice(MEME_TEMPLATES)}') + draw = ImageDraw.Draw(template) + + try: + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 45) + except: + font = ImageFont.load_default() + + # Текст с обводкой + bbox = draw.textbbox((0, 0), meme_text, font=font) + width, height = bbox[2] - bbox[0], bbox[3] - bbox[1] + x, y = (template.width - width) // 2, template.height - height - 60 + + # Обводка + for dx, dy in [(-2,0),(2,0),(0,-2),(0,2)]: + draw.text((x+dx, y+dy), meme_text, font=font, fill='black') + draw.text((x, y), meme_text, font=font, fill='white') + + bio = io.BytesIO() + template.save(bio, 'PNG') + bio.seek(0) + return bio, meme_text + +async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): + query = update.callback_query + await query.answer() + + if query.data == 'balance': + bal = BALANCES.get(query.from_user.id, 0) + await query.edit_message_text(f'💰 Баланс: {bal} мемов', parse_mode='HTML') + + elif query.data == 'buy_meme': + await send_invoice(update.callback_query.message, context) + +async def precheckout_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): + query = update.pre_checkout_query + await query.answer(ok=True) + +async def successful_payment_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): + user_id = update.effective_user.id + BALANCES[user_id] = BALANCES.get(user_id, 0) + 1 # +1 мем + + await update.message.reply_text( + '✅ Оплата прошла! Баланс: 1 мем\n\n' + '🎨 Отправьте тему мема:\n"kubernetes pod pending"\n"nfs mount failed"', + parse_mode='HTML' + ) + +async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): + user_id = update.effective_user.id + balance = BALANCES.get(user_id, 0) + + if balance <= 0: + await update.message.reply_text('❌ Нет мемов! Купите: /start') + return + + await update.message.reply_text('🎨 Генерирую...') + try: + meme_img, meme_text = await generate_meme(update.message.text) + BALANCES[user_id] -= 1 + + await update.message.reply_photo( + photo=meme_img, + caption=f'😹 Ваш IT-мем готов!\n\n"{meme_text}"\n\n💰 Осталось: {BALANCES[user_id]} мемов', + parse_mode='HTML' + ) + except Exception as e: + logger.error(f"Meme error: {e}") + await update.message.reply_text('❌ Ошибка. Попробуйте позже.') + +def main(): + app = Application.builder().token(TOKEN).build() + app.add_handler(CommandHandler("start", start)) + app.add_handler(CallbackQueryHandler(button_handler)) + app.add_handler(PreCheckoutQueryHandler(precheckout_callback)) + app.add_handler(MessageHandler(filters.SUCCESSFUL_PAYMENT, successful_payment_callback)) + app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)) + app.run_polling() + +if __name__ == '__main__': + main() diff --git a/k8s/01-namespace.yaml b/k8s/01-namespace.yaml new file mode 100644 index 0000000..e69de29 diff --git a/k8s/02-ollama.yaml b/k8s/02-ollama.yaml new file mode 100644 index 0000000..e69de29 diff --git a/k8s/03-bot.yaml b/k8s/03-bot.yaml new file mode 100644 index 0000000..e69de29 diff --git a/k8s/04-secrets.yaml b/k8s/04-secrets.yaml new file mode 100644 index 0000000..e69de29 diff --git a/payments.py b/payments.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..136ea2c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ + python-telegram-bot==20.7 +pillow==10.4.0 +requests==2.32.3 +python-dotenv==1.0.1