Всегда на связи

Коротко обо мне и все порталы, где меня можно найти

Now Playing

Loading...

Разработчик на Python. Пишу код для автоматизации, создаю приложения и решаю сложные задачи. Интересуюсь веб-разработкой, скрейпингом и программированием в целом.

Почему стоит написать

  • новые проекты на Python
  • помощь с кодом и консультации
  • обсуждение идей и техники
  • friendly networking :)
портфолио

Мои работы

Проекты на Python, которыми я горжусь

💼 Личный веб-сайт с портфолио

🟢 Легко

Современный портфолио-сайт на чистом HTML/CSS/JavaScript с темной темой, адаптивным дизайном и интеграцией API ВКонтакте для отображения текущей музыки. Развернут на собственном VDS с HTTPS и кастомным доменом.

HTML5 CSS3 JavaScript VK API Responsive Design VDS
📄 index.html - Основной код сайта
<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Портфолио | onxml.fun</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;500;600;700&display=swap" rel="stylesheet" />
    <style>
      :root {
        color-scheme: dark;
        --bg-1: #05030f;
        --bg-2: #0f1a45;
        --accent: #6a7bff;
        --accent-2: #42e8e0;
        --text-main: #f5f8ff;
        --text-muted: rgba(245, 248, 255, 0.7);
        --card-bg: rgba(255, 255, 255, 0.05);
        --border: rgba(255, 255, 255, 0.12);
        --glow: 0 10px 40px rgba(80, 131, 255, 0.15);
      }

      * { box-sizing: border-box; }

      body {
        margin: 0;
        font-family: "Inter", system-ui, sans-serif;
        min-height: 100vh;
        background: radial-gradient(circle at top, var(--bg-2), var(--bg-1) 40%);
        color: var(--text-main);
        overflow-x: hidden;
      }

      main {
        position: relative;
        padding: 80px 24px;
        max-width: 960px;
        margin: 0 auto;
      }

      header {
        text-align: center;
        margin-bottom: 60px;
      }

      h1 {
        font-size: clamp(2.5rem, 6vw, 4.2rem);
        margin: 0 0 16px;
        letter-spacing: -0.03em;
      }

      .card {
        position: relative;
        padding: 24px;
        background: var(--card-bg);
        border: 1px solid var(--border);
        border-radius: 24px;
        backdrop-filter: blur(12px);
        box-shadow: var(--glow);
        overflow: hidden;
        transition: transform 0.5s ease, border-color 0.5s ease;
      }

      .card:hover {
        transform: translateY(-8px);
        border-color: rgba(88, 119, 255, 0.9);
      }

      .card-icon {
        width: 48px;
        height: 48px;
        border-radius: 16px;
        background: rgba(255, 255, 255, 0.06);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        margin-bottom: 18px;
        font-size: 24px;
      }

      .card-title {
        font-size: 1.2rem;
        margin: 0 0 6px;
        font-weight: 600;
      }

      .contact-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
        gap: 24px;
        margin-top: 48px;
      }

      nav {
        display: flex;
        gap: 24px;
        justify-content: space-between;
        margin-bottom: 60px;
        align-items: center;
        flex-wrap: wrap;
      }

      nav > div:first-child {
        display: flex;
        gap: 24px;
      }

      nav a {
        text-decoration: none;
        color: var(--text-main);
        font-weight: 600;
        padding: 8px 16px;
        border-radius: 8px;
        transition: background 0.3s ease;
      }

      nav a.active {
        color: var(--accent-2);
        background: rgba(66, 232, 224, 0.12);
      }

      .language-switcher {
        display: flex;
        gap: 8px;
        align-items: center;
        background: transparent;
        border: 1px solid rgba(106, 123, 255, 0.4);
        border-radius: 999px;
        padding: 6px 12px;
      }

      .lang-btn {
        padding: 8px 14px;
        border: none;
        background: transparent;
        color: var(--text-muted);
        border-radius: 999px;
        cursor: pointer;
        font-weight: 600;
        font-size: 0.85rem;
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        position: relative;
        letter-spacing: 0.08em;
        text-transform: uppercase;
      }

      .lang-btn:hover {
        color: var(--accent-2);
      }

      .lang-btn.active {
        background: rgba(106, 123, 255, 0.25);
        color: var(--accent-2);
        box-shadow: inset 0 0 12px rgba(106, 123, 255, 0.2);
        border: 1px solid rgba(106, 123, 255, 0.5);
      }

      .lang-toggle {
        display: flex;
        gap: 8px;
        align-items: center;
        background: transparent;
        border: 1px solid rgba(106, 123, 255, 0.4);
        border-radius: 999px;
        padding: 6px 12px;
      }

      .page { display: none; }
      .page.active { display: block; }

      @media (max-width: 600px) {
        main { padding-top: 60px; }
        .card { padding: 20px; }
      }
    </style>
  </head>
  <body>
    <main>
      <nav>
        <div>
          <a href="#" class="nav-link active" data-page="contacts" data-i18n="nav-contacts">Контакты</a>
          <a href="#" class="nav-link" data-page="works" data-i18n="nav-works">Работы</a>
        </div>
        <div class="language-switcher">
          <button class="lang-btn active" onclick="changeLanguage('ru')" data-lang="ru">РУ</button>
          <button class="lang-btn" onclick="changeLanguage('en')" data-lang="en">EN</button>
        </div>
      </nav>

      <div id="contacts" class="page active">
        <header>
          <h1 data-i18n="title">Всегда на связи</h1>
          <p data-i18n="subtitle">Коротко обо мне и все порталы, где меня можно найти</p>
        </header>

        <div class="contact-grid">
          <a href="https://vk.com/hahasorryidie" target="_blank" class="card-wrapper">
            <article class="card">
              <div class="card-icon">VK</div>
              <h2 class="card-title" data-i18n="vk-title">ВКонтакте</h2>
              <p data-i18n="vk-desc">vk.com/hahasorryidie →</p>
            </article>
          </a>

          <a href="https://t.me/jzdox" target="_blank" class="card-wrapper">
            <article class="card">
              <div class="card-icon">TG</div>
              <h2 class="card-title" data-i18n="tg-title">Telegram</h2>
              <p data-i18n="tg-desc">t.me/jzdox →</p>
            </article>
          </a>

          <a href="mailto:doxmepls@mail.ru" class="card-wrapper">
            <article class="card">
              <div class="card-icon">@</div>
              <h2 class="card-title" data-i18n="mail-title">Почта</h2>
              <p data-i18n="mail-desc">doxmepls@mail.ru →</p>
            </article>
          </a>
        </div>
      </div>

      <div id="works" class="page">
        <header>
          <h1 data-i18n="works-title">Мои работы</h1>
        </header>
      </div>
    </main>

    <script>
      document.querySelectorAll('.nav-link').forEach(link => {
        link.addEventListener('click', (e) => {
          e.preventDefault();
          const page = link.dataset.page;
          
          document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
          document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
          
          link.classList.add('active');
          document.getElementById(page).classList.add('active');
        });
      });
    </script>
  </body>
</html>

🤖 Telegram бот с системой ролей

🟡 Средне

Полнофункциональный Telegram бот на Python с системой управления пользователями, ролями, мини-игры, магазин товаров, администраторская панель и база данных SQLite.

Python python-telegram-bot SQLite CRUD Games
🐍 bottg2.py - Основной код (без чувствительных данных)
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CommandHandler, CallbackQueryHandler
import sqlite3
import logging
from datetime import datetime, timedelta

# Конфигурация
BOT_TOKEN = "YOUR_BOT_TOKEN_HERE"
ADMIN_IDS = [YOUR_ADMIN_ID]
DATABASE_NAME = "bot_database.db"

# Система ролей
ROLES = {
    "member": {"name": "👤 Участник", "level": 1},
    "vip": {"name": "⭐ VIP", "level": 2, "permissions": ["fast_chat"]},
    "premium": {"name": "💎 Премиум", "level": 3, "permissions": ["fast_chat", "extra_coins"]},
    "moderator": {"name": "🛡️ Модератор", "level": 4, "permissions": ["warn_users"]},
    "admin": {"name": "👑 Админ", "level": 5, "permissions": ["all"]}
}

def init_database():
    """Инициализация базы данных"""
    conn = sqlite3.connect(DATABASE_NAME)
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            user_id INTEGER PRIMARY KEY,
            username TEXT,
            first_name TEXT,
            coins INTEGER DEFAULT 0,
            role TEXT DEFAULT 'member',
            warnings INTEGER DEFAULT 0,
            message_count INTEGER DEFAULT 0,
            join_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    conn.commit()
    conn.close()

def add_user(user_id, username, first_name):
    """Добавляет пользователя в БД"""
    conn = sqlite3.connect(DATABASE_NAME)
    cursor = conn.cursor()
    
    role = "admin" if user_id in ADMIN_IDS else "member"
    cursor.execute('''
        INSERT OR IGNORE INTO users (user_id, username, first_name, role)
        VALUES (?, ?, ?, ?)
    ''', (user_id, username, first_name, role))
    
    conn.commit()
    conn.close()

def get_user_role(user_id):
    """Получает роль пользователя"""
    conn = sqlite3.connect(DATABASE_NAME)
    cursor = conn.cursor()
    cursor.execute('SELECT role FROM users WHERE user_id = ?', (user_id,))
    result = cursor.fetchone()
    conn.close()
    return result[0] if result else "member"

def add_coins(user_id, amount):
    """Добавляет монеты"""
    conn = sqlite3.connect(DATABASE_NAME)
    cursor = conn.cursor()
    cursor.execute('UPDATE users SET coins = coins + ? WHERE user_id = ?', (amount, user_id))
    conn.commit()
    conn.close()

async def start(update: Update, context):
    user = update.effective_user
    add_user(user.id, user.username, user.first_name)
    
    keyboard = [
        [InlineKeyboardButton("📋 Информация", callback_data="info")],
        [InlineKeyboardButton("🎮 Мини-игры", callback_data="games")],
        [InlineKeyboardButton("🛍️ Магазин", callback_data="shop")],
        [InlineKeyboardButton("👥 Профиль", callback_data="profile")]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    
    await update.message.reply_text(
        f"Привет, {user.first_name}! 👋\nДобро пожаловать в улучшенного бота!",
        reply_markup=reply_markup
    )

async def button_handler(update: Update, context):
    query = update.callback_query
    await query.answer()
    
    if query.data == "info":
        await query.edit_message_text("ℹ️ Информация о боте...")

def main():
    init_database()
    application = Application.builder().token(BOT_TOKEN).build()
    
    application.add_handler(CommandHandler("start", start))
    application.add_handler(CallbackQueryHandler(button_handler))
    
    application.run_polling()

if __name__ == "__main__":
    main()

📅 Парсер расписания университета

🔴 Сложно

Сложный парсер на Python с авторизацией на сайте, обработкой OAuth, парсингом HTML, системой управления пользователями, уведомлениями в Telegram и автоматическими обновлениями.

Python BeautifulSoup Requests OAuth Web Scraping Schedule
🐍 parserkingvvsu.py - Основной код (без учетных данных)
from bs4 import BeautifulSoup
import requests
import json
import logging
from datetime import datetime, timedelta
from urllib.parse import urljoin
import telebot
import schedule
import threading
import os

# Конфигурация
BOT_TOKEN = "YOUR_BOT_TOKEN_HERE"
ADMIN_CHAT_ID = "YOUR_ADMIN_ID"
WEBSITE_URL = "https://cabinet.vvsu.ru/time-table/"
LOGIN_URL = "https://cabinet.vvsu.ru/sign-in"

class VVGUScheduleParser:
    def __init__(self):
        self.session = requests.Session()
        self.is_authenticated = False
        self.cookie_file = "vvgu_cookies.json"
        
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
        })

    def login(self):
        """Авторизация на сайте с обработкой OAuth"""
        try:
            logger.info("🔐 Начало авторизации...")
            
            login_page = self.session.get(LOGIN_URL)
            soup = BeautifulSoup(login_page.content, 'html.parser')
            
            # Ищем форму входа
            form = soup.find('form')
            if not form:
                logger.error("❌ Форма входа не найдена")
                return False
            
            # Собираем данные формы
            login_data = {}
            hidden_inputs = form.find_all('input', type='hidden')
            
            for hidden in hidden_inputs:
                name = hidden.get('name')
                value = hidden.get('value', '')
                if name:
                    login_data[name] = value
            
            # Ищем поля логина и пароля
            username_field = form.find('input', {
                'name': lambda x: x and any(word in x.lower() 
                    for word in ['username', 'login', 'email'])
            })
            password_field = form.find('input', {'type': 'password'})
            
            if username_field and password_field:
                login_data[username_field.get('name')] = "YOUR_USERNAME"
                login_data[password_field.get('name')] = "YOUR_PASSWORD"
            
            action_url = form.get('action', '')
            if action_url:
                action_url = urljoin(LOGIN_URL, action_url)
            else:
                action_url = LOGIN_URL
            
            # Отправляем запрос на авторизацию
            headers = {'Referer': LOGIN_URL}
            response = self.session.post(action_url, data=login_data, 
                                        headers=headers, allow_redirects=True)
            
            # Обработка OAuth редиректа
            if 'fort.vvsu.ru' in response.url:
                return self._handle_oauth_redirect(response)
            
            return self._check_auth_success(response)
                
        except Exception as e:
            logger.error(f"❌ Ошибка при авторизации: {e}")
            return False

    def _handle_oauth_redirect(self, response):
        """Обрабатывает OAuth редирект"""
        try:
            soup = BeautifulSoup(response.content, 'html.parser')
            form = soup.find('form')
            
            if not form:
                return False
            
            oauth_data = {}
            for hidden in form.find_all('input', type='hidden'):
                name = hidden.get('name')
                if name:
                    oauth_data[name] = hidden.get('value', '')
            
            oauth_url = form.get('action', '')
            if not oauth_url.startswith('http'):
                oauth_url = urljoin(response.url, oauth_url)
            
            oauth_response = self.session.post(oauth_url, data=oauth_data, 
                                             allow_redirects=True)
            
            return self._check_auth_success(oauth_response)
            
        except Exception as e:
            logger.error(f"❌ Ошибка OAuth: {e}")
            return False

    def parse_schedule(self):
        """Парсит расписание"""
        try:
            logger.info("📖 Получение расписания...")
            response = self.session.get(WEBSITE_URL)
            
            soup = BeautifulSoup(response.content, 'html.parser')
            schedule_data = self._extract_schedule_data(soup)
            
            return schedule_data
            
        except Exception as e:
            logger.error(f"❌ Ошибка парсинга: {e}")
            return None

    def _extract_schedule_data(self, soup):
        """Извлекает данные расписания"""
        schedule_data = []
        
        tables = soup.find_all('table')
        for table in tables:
            rows = table.find_all('tr')
            for row in rows:
                cells = row.find_all(['td', 'th'])
                if cells:
                    schedule_data.append([cell.get_text(strip=True) for cell in cells])
        
        return schedule_data

    def format_message(self, schedule_data):
        """Форматирует сообщение"""
        if not schedule_data:
            return "📭 Расписание не найдено"
        
        message = "🎓 Расписание ВВГУ\n\n"
        for row in schedule_data[:10]:
            message += " | ".join(row[:3]) + "\n"
        
        return message

# Инициализация
parser = VVGUScheduleParser()
bot = telebot.TeleBot(BOT_TOKEN)

def send_schedule():
    """Отправляет расписание"""
    schedule_data = parser.parse_schedule()
    message = parser.format_message(schedule_data)
    bot.send_message(ADMIN_CHAT_ID, message, parse_mode='HTML')

# Расписание обновлений
schedule.every().day.at("07:00").do(send_schedule)
schedule.every().day.at("12:00").do(send_schedule)
schedule.every().day.at("17:00").do(send_schedule)

def run_scheduler():
    """Запускает планировщик"""
    while True:
        schedule.run_pending()

if __name__ == "__main__":
    scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
    scheduler_thread.start()
    
    bot.infinity_polling()