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