initial project version
This commit is contained in:
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
BOT_TOKEN = "..." # Токен бота получать у @BotFather
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.venv/
|
||||||
|
.env
|
||||||
|
__pycache__/
|
||||||
|
.idea/
|
||||||
|
bot.log
|
||||||
|
users.db
|
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h1 align="center">LGBot</h1>
|
||||||
|
<p align="center">Бот-модератор для @linux_gaming_ru </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Список дел
|
||||||
|
|
||||||
|
- [X] Команда /start
|
||||||
|
- [ ] Команда /help
|
||||||
|
- [ ] Команда /mute (или мут)
|
||||||
|
- [ ] Команда /ban (или бан)
|
||||||
|
- [ ] Фильтрация сообщений
|
||||||
|
- [ ] Удаление сообщений с матом
|
||||||
|
- [ ] Удаление рекламы
|
||||||
|
|
||||||
|
### Установка зависимостей (через pyenv)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pyenv install 3.11.0
|
||||||
|
|
||||||
|
~/.pyenv/versions/3.11.0/bin/python3 -m venv .venv
|
||||||
|
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка
|
||||||
|
|
||||||
|
Создатите файл **.env** и внесите в него ваш токен, который вы получили у @BotFather.
|
||||||
|
|
||||||
|
### Запуск
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python src/main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
> Используется Python 3.11.0
|
19
requirements.txt
Normal file
19
requirements.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
aiohappyeyeballs==2.6.1
|
||||||
|
aiohttp==3.12.13
|
||||||
|
aiosignal==1.4.0
|
||||||
|
asyncio==3.4.3
|
||||||
|
attrs==25.3.0
|
||||||
|
certifi==2025.6.15
|
||||||
|
charset-normalizer==3.4.2
|
||||||
|
dotenv==0.9.9
|
||||||
|
frozenlist==1.7.0
|
||||||
|
idna==3.10
|
||||||
|
multidict==6.6.3
|
||||||
|
propcache==0.3.2
|
||||||
|
pysqlite3==0.5.4
|
||||||
|
pyTelegramBotAPI==4.27.0
|
||||||
|
python-dotenv==1.1.1
|
||||||
|
requests==2.32.4
|
||||||
|
typing_extensions==4.14.1
|
||||||
|
urllib3==2.5.0
|
||||||
|
yarl==1.20.1
|
9
src/config.py
Normal file
9
src/config.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Директория, где хранятся модули
|
||||||
|
MODULES_DIR = "modules"
|
||||||
|
|
||||||
|
# Название файла db sqlite
|
||||||
|
DATABASE_NAME = "users.db"
|
||||||
|
|
||||||
|
# Текст для команд
|
||||||
|
MESSAGE_FOR_START = "Бот-модератор для чата @linux_gaming_ru"
|
||||||
|
MESSAGE_FOR_HELP = "пусто"
|
66
src/database.py
Normal file
66
src/database.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from config import DATABASE_NAME
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__) # Получаем логгер для текущего модуля
|
||||||
|
|
||||||
|
class Database: # Инициализация класса
|
||||||
|
def __init__(self, db_name: str = DATABASE_NAME):
|
||||||
|
self.db_name = db_name
|
||||||
|
self._init_db()
|
||||||
|
|
||||||
|
# Инициализирует базу данных и создает таблицу, если она не существует
|
||||||
|
def _init_db(self):
|
||||||
|
with self._get_connection() as connect:
|
||||||
|
cursor = connect.cursor()
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
nickname TEXT,
|
||||||
|
tag TEXT
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
connect.commit()
|
||||||
|
|
||||||
|
# Возвращает соединение с базой данных
|
||||||
|
def _get_connection(self):
|
||||||
|
return sqlite3.connect(self.db_name)
|
||||||
|
|
||||||
|
# Добавляет нового пользователя или обновляет существующего
|
||||||
|
def add_or_update_user(self, user_id: int, nickname: Optional[str], tag: Optional[str]):
|
||||||
|
with self._get_connection() as connect:
|
||||||
|
cursor = connect.cursor()
|
||||||
|
|
||||||
|
# Проверяем существование пользователя
|
||||||
|
cursor.execute('SELECT 1 FROM users WHERE id = ?', (user_id,))
|
||||||
|
exists = cursor.fetchone()
|
||||||
|
|
||||||
|
if exists: # Обновляем существующую запись
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE users
|
||||||
|
SET nickname = ?, tag = ?
|
||||||
|
WHERE id = ?
|
||||||
|
''', (nickname, tag, user_id))
|
||||||
|
|
||||||
|
else: # Добавляем нового пользователя
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO users (id, nickname, tag)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
''', (user_id, nickname, tag))
|
||||||
|
logger.info(f"Новый пользователь ({user_id})")
|
||||||
|
|
||||||
|
connect.commit()
|
||||||
|
|
||||||
|
# Возвращает информацию о пользователе
|
||||||
|
def get_user(self, user_id: int) -> Optional[Tuple]:
|
||||||
|
with self._get_connection() as connect:
|
||||||
|
cursor = connect.cursor()
|
||||||
|
cursor.execute('''SELECT id, nickname, tag FROM users WHERE id = ?''', (user_id,))
|
||||||
|
return cursor.fetchone()
|
||||||
|
|
||||||
|
# Создаем экземпляр базы данных для импорта в других модулях
|
||||||
|
db = Database()
|
58
src/logger.py
Normal file
58
src/logger.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
class ColoredFormatter(logging.Formatter): # Цветные логи (для терминала)
|
||||||
|
LEVEL_COLORS = {
|
||||||
|
logging.INFO: '\033[92m',
|
||||||
|
logging.WARNING: '\033[93m',
|
||||||
|
logging.ERROR: '\033[91m',
|
||||||
|
logging.CRITICAL: '\033[91m'
|
||||||
|
}
|
||||||
|
|
||||||
|
LEVEL_NAMES = {
|
||||||
|
logging.INFO: "I",
|
||||||
|
logging.WARNING: "W",
|
||||||
|
logging.ERROR: "E",
|
||||||
|
logging.CRITICAL: "C"
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
local_time = time.localtime(record.created)
|
||||||
|
time_str = time.strftime("%H:%M:%S", local_time)
|
||||||
|
date_str = time.strftime("%d-%m-%Y", local_time)
|
||||||
|
level_name = self.LEVEL_NAMES.get(record.levelno, record.levelname)
|
||||||
|
message = f"[{time_str}] [{date_str}] [{level_name}] {record.getMessage()}"
|
||||||
|
color = self.LEVEL_COLORS.get(record.levelno, "")
|
||||||
|
return f"{color}{message}\033[0m" if color else message
|
||||||
|
|
||||||
|
class UncoloredFormatter(logging.Formatter): # Бесцветные логи (для bot.log)
|
||||||
|
def format(self, record):
|
||||||
|
local_time = time.localtime(record.created)
|
||||||
|
time_str = time.strftime("%H:%M:%S", local_time)
|
||||||
|
date_str = time.strftime("%d-%m-%Y", local_time)
|
||||||
|
level_name = ColoredFormatter.LEVEL_NAMES.get(
|
||||||
|
record.levelno,
|
||||||
|
record.levelname
|
||||||
|
)
|
||||||
|
return f"[{time_str}] [{date_str}] [{level_name}] {record.getMessage()}"
|
||||||
|
|
||||||
|
def setup_logging(): # Инициализирует систему логирования
|
||||||
|
|
||||||
|
# Создаем корневой логгер
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Проверяем, не настроен ли логгер ранее
|
||||||
|
if not logger.hasHandlers():
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(ColoredFormatter())
|
||||||
|
|
||||||
|
# Сохраняем логи в файл
|
||||||
|
file_handler = logging.FileHandler("bot.log", encoding='utf-8')
|
||||||
|
file_handler.setFormatter(UncoloredFormatter())
|
||||||
|
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
return logger
|
90
src/main.py
Normal file
90
src/main.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
from telebot.async_telebot import AsyncTeleBot
|
||||||
|
from telebot.asyncio_handler_backends import BaseMiddleware
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import importlib
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logger import setup_logging
|
||||||
|
|
||||||
|
from database import db
|
||||||
|
|
||||||
|
from config import MODULES_DIR
|
||||||
|
|
||||||
|
load_dotenv() # Загружаем токен бота из .env
|
||||||
|
bot = AsyncTeleBot(os.getenv("BOT_TOKEN"), parse_mode="html")
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__) # Получаем логгер для текущего модуля
|
||||||
|
|
||||||
|
class UserUpdateMiddleware(BaseMiddleware):
|
||||||
|
def __init__(self, db):
|
||||||
|
super().__init__()
|
||||||
|
# message - все обычные сообщения
|
||||||
|
# chat_member - события изменения статуса участников чата
|
||||||
|
self.update_types = ['message', 'chat_member']
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
async def pre_process(self, message, data):
|
||||||
|
|
||||||
|
# Обработка пользователей, отправившие сообщение
|
||||||
|
if message.content_type == 'text':
|
||||||
|
self.db.add_or_update_user(
|
||||||
|
user_id=message.from_user.id,
|
||||||
|
nickname=message.from_user.first_name,
|
||||||
|
tag=message.from_user.username
|
||||||
|
)
|
||||||
|
|
||||||
|
# Обработка новых участников группы
|
||||||
|
elif message.content_type == 'new_chat_members':
|
||||||
|
for new_member in message.new_chat_members:
|
||||||
|
self.db.add_or_update_user(
|
||||||
|
user_id=new_member.id,
|
||||||
|
nickname=new_member.first_name,
|
||||||
|
tag=new_member.username
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def post_process(self, message, data, exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Регистрируем middleware
|
||||||
|
bot.setup_middleware(UserUpdateMiddleware(db))
|
||||||
|
|
||||||
|
async def load_modules(): # Загружает все модули из директории /modules
|
||||||
|
|
||||||
|
setup_logging() # Инициализация логирования
|
||||||
|
|
||||||
|
loaded_count = 0 # Переменная для подсчёта модулей
|
||||||
|
modules_path = os.path.join(os.path.dirname(__file__), MODULES_DIR) # Импортируем относительный путь проекта
|
||||||
|
|
||||||
|
for filename in os.listdir(modules_path):
|
||||||
|
if filename.endswith(".py") and filename != "__init__.py":
|
||||||
|
module_name = filename[:-3] # Убираем расширение .py
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(f"{MODULES_DIR}.{module_name}")
|
||||||
|
if hasattr(module, "register_handlers"):
|
||||||
|
module.register_handlers(bot)
|
||||||
|
loaded_count += 1
|
||||||
|
logger.info(f"Модуль {module_name} успешно загружен")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Модуль {module_name} не содержит функцию register_handlers")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при загрузке модуля {module_name}: {str(e)}")
|
||||||
|
logger.info(f"Загружено модулей: {loaded_count} шт. Бот запущен.")
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
os.system('clear') # Очищаем терминал
|
||||||
|
try:
|
||||||
|
await load_modules() # Проверяем и загружаем модули
|
||||||
|
await bot.infinity_polling() # Запускаем бота
|
||||||
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
||||||
|
logger.info("Бот остановлен.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(f"Критическая ошибка: {str(e)}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
0
src/modules/__init__.py
Normal file
0
src/modules/__init__.py
Normal file
16
src/modules/start.py
Normal file
16
src/modules/start.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from telebot.async_telebot import AsyncTeleBot
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from config import MESSAGE_FOR_START
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__) # Получаем логгер для текущего модуля
|
||||||
|
|
||||||
|
def register_handlers(bot: AsyncTeleBot): # Регистрирует все обработчики команд
|
||||||
|
|
||||||
|
@bot.message_handler(commands=['start']) # Обработчик команды /start
|
||||||
|
async def start_command(message):
|
||||||
|
try:
|
||||||
|
logger.info(f"Команда START ({message.from_user.id})")
|
||||||
|
await bot.send_message(message.chat.id, MESSAGE_FOR_START)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Команда START ({message.from_user.id}) {str(e)}")
|
Reference in New Issue
Block a user