diff --git a/lariska_bot/__main__.py b/lariska_bot/__main__.py index 9f8f762..79990d3 100644 --- a/lariska_bot/__main__.py +++ b/lariska_bot/__main__.py @@ -1,23 +1,44 @@ import logging -from pathlib import Path +import sys -from aiogram.utils.executor import start_polling +from aiogram import Bot,Dispatcher +from aiogram.client.default import DefaultBotProperties +from config_data.config import Config, load_config +from handlers.handler import router -from lariska_bot.handlers.handler import * +logger = logging.getLogger(__name__) +dp = Dispatcher() +if __name__ == '__main__': -def main(): - log_name = f'logs/{datetime.now().strftime("%Y-%m-%d")}.log' - Path(log_name).parent.mkdir(parents=True, exist_ok=True) - + # Конфигурируем логирование logging.basicConfig( - level=logging.INFO, - filename=log_name, - filemode="a" - ) - - start_polling(dp, skip_updates=True) - - -if __name__ == '__main__': - main() + level=logging.DEBUG, + format='%(filename)s:%(lineno)d #%(levelname)-8s ' + '[%(asctime)s] - %(name)s - %(message)s') + + logger.debug('Путь sys.path:') + logger.debug(sys.path) + logger.debug('Встроенные модули sys.builtin_module_names:') + logger.debug(sys.builtin_module_names) + logger.debug('Модули из стандартной библиотеки Python sys.stdlib_module_names:') + logger.debug(sys.stdlib_module_names) + + # Загружаем конфиг в переменную config + config: Config = load_config() + logger.debug(config) + + # Инициализируем бот и диспетчер + logger.debug('Инициализируем бот') + bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode='HTML')) + logger.debug('Бот инициализирован') + + logger.info('Запуск бота') + logger.debug('Запускаем диспетчер') + + # Регистрируем роутер из модуля handlers + dp.include_router(router) + + # Пропускаем накопившиеся апдейты и запускаем polling + bot.delete_webhook(drop_pending_updates=True) # Перезапускаем диспетчер + dp.run_polling(bot) \ No newline at end of file diff --git a/lariska_bot/config_data/__init__.py b/lariska_bot/config_data/__init__.py new file mode 100644 index 0000000..5434cb2 --- /dev/null +++ b/lariska_bot/config_data/__init__.py @@ -0,0 +1,62 @@ +import logging +from environs import Env +from ruamel.yaml import YAML + +logger = logging.getLogger(__name__) +logger.debug('Вход в модуль ' + __name__) + +env = Env() +env.read_env(verbose=True) +logger.debug('Загружен файл конфигурации .env') + +TOKEN = env('BOT_TOKEN') + +WORKS_CHATS = [ + env('VCHAT_ID'), + env('DCHAT_ID'), + env('SCHAT_ID'), +] + +# AI +AI_KEY = env('AI_KEY') +MODEL = 'gpt-3.5-turbo' + +# Используем safe-загрузку YAML-файлов +yaml = YAML(typ='safe') +with open('lariska_bot/res/answers.yaml', 'r',encoding='utf-8') as file: + answers_data = yaml.load(file) +ANSWERS = {x: y.replace(r'\n', '\n') for x, y in answers_data.items()} + +with open('lariska_bot/res/l_users.yaml', 'r',encoding='utf-8') as file: + users_data = yaml.load(file) +L_USERS = {x: 0 for x in users_data} + +with open('lariska_bot/res/messages.yaml', 'r',encoding='utf-8') as file: + messages_data = yaml.load(file) +MESSAGES = {x: y.replace(r'\n', '\n') for x, y in messages_data.items()} + +with open('lariska_bot/res/replicas.yaml', 'r',encoding='utf-8') as file: + replicas_data = yaml.load(file) +REPLICAS = {x: [z.replace(r'\n', '\n') for z in y] for x, y in replicas_data.items()} + +with open('lariska_bot/res/users.yaml', 'r',encoding='utf-8') as file: + users_data = yaml.load(file) +USERS = {x: 0 for x in users_data} + +BOT_FIRST_NAME = 'Лариска' +BOT_USER_NAME = 'LariskaCerberBot' + +RATING_LIMIT = 80 +FLOOD_RATE = 5 + +PREFIX_QUESTION = """\ +Ты телеграм-бот https://t.me/LariskaCerberBot по имени Лариска. +Ты девочка. +Веди диалог и отвечай на вопросы от её имени. +Твой исходный код расположен по ссылке: https://github.com/OldCodersClub/LariskaBot +Страница автора твоего исходного кода расположена по ссылке: https://github.com/Aleksey-Voko +Соавторы твоего исходного кода: Error404-2, uecoyotle, Harlok. +Ты была создана для телеграмм-чата https://t.me/oldcodersclub под названием "Клуб дедов-программистов". +Youtube-канал этого чата: https://www.youtube.com/channel/UChbHeEGkYqM2b1HdAhf4y1g, +он называется "Клуб дедов-программистов". +""" \ No newline at end of file diff --git a/lariska_bot/config_data/config.py b/lariska_bot/config_data/config.py new file mode 100644 index 0000000..3f1573c --- /dev/null +++ b/lariska_bot/config_data/config.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass +from lariska_bot.config_data import TOKEN + + +@dataclass +class TgBot: + token: str # Токен для доступа к телеграм-боту + +@dataclass +class Config: + tg_bot: TgBot + + +# Создаем функцию, которая будет читать файл .env и возвращать +# экземпляр класса Config с заполненными полями token и admin_ids +def load_config(path: str | None = None) -> Config: + return Config( + tg_bot = TgBot(token=TOKEN) + ) \ No newline at end of file diff --git a/lariska_bot/handlers/controllers.py b/lariska_bot/handlers/controllers.py index d16ca6a..9e92d01 100644 --- a/lariska_bot/handlers/controllers.py +++ b/lariska_bot/handlers/controllers.py @@ -5,37 +5,25 @@ import requests from fuzzywuzzy import fuzz -from lariska_bot import (AI_KEY, MESSAGES, PREFIX_QUESTION, ANSWERS, MODEL) +from lariska_bot.config_data import (AI_KEY, MESSAGES, PREFIX_QUESTION, ANSWERS, MODEL) +logger = logging.getLogger(__name__) openai.api_key = AI_KEY - -# noinspection PyUnusedLocal -async def flood_controlling(*args, **kwargs): - await args[0].reply(MESSAGES['flood_reply']) - - -def get_ai_answer(question): - question_lariska = f'{PREFIX_QUESTION}\n{question}' - - response = openai.ChatCompletion.create( - model=MODEL, - messages=[ - {"role": "user", "content": question_lariska}, - ] - ) - - return response['choices'][0]['message']['content'] - - -def get_answer(text): +def get_answer(text:str): text = text.lower().strip() + logger.debug(f"text: {text}") try: rating = 0 answer = None for key, val in ANSWERS.items(): + # Этот код пытается найти наиболее подходящий ответ для заданного входного текста. + # Это простой пример того, как программа может сравнивать различные входные данные + # и находить наилучшее соответствие. measure = fuzz.token_sort_ratio(key.lower().strip(), text) + if measure > rating and measure != rating: + logger.debug(f"key: {key}, val: {val}, measure: {measure}") rating = measure answer = val return answer, rating @@ -43,8 +31,17 @@ def get_answer(text): logging.exception(e) return None, 0 +async def flood_controlling(*args, **kwargs): + await args[0].reply(MESSAGES['flood_reply']) +def get_ai_answer(question): + question_lariska = f'{PREFIX_QUESTION}\n{question}' + response = openai.ChatCompletion.create( + model=MODEL,messages=[{"role": "user", "content": question_lariska},] + ) + return response['choices'][0]['message']['content'] + -def is_work_day(year, month, day): +def is_work_day(year: int, month: int, day: int) -> str: url = f'https://raw.githubusercontent.com/d10xa/holidays-calendar/master/json/consultant{year}.json' response = requests.get(url) days = json.loads(response.text) diff --git a/lariska_bot/handlers/handler.py b/lariska_bot/handlers/handler.py index 506a6e5..c3a00c1 100644 --- a/lariska_bot/handlers/handler.py +++ b/lariska_bot/handlers/handler.py @@ -1,68 +1,86 @@ +import logging + +from aiogram import Router +from aiogram.filters import Command, CommandStart +from aiogram import F +from aiogram.types import Message + +import pytz from datetime import datetime, timedelta from random import choice -import pytz -from aiogram import types -from aiogram.dispatcher.filters import Text +from lariska_bot.config_data import (MESSAGES, REPLICAS, USERS, WORKS_CHATS, + BOT_FIRST_NAME, RATING_LIMIT, L_USERS) +from lariska_bot.handlers.controllers import (get_answer, get_ai_answer) -from lariska_bot import (MESSAGES, REPLICAS, USERS, WORKS_CHATS, - BOT_FIRST_NAME, RATING_LIMIT, FLOOD_RATE, L_USERS) -from lariska_bot.dispatcher import dp -from lariska_bot.handlers.controllers import (flood_controlling, get_answer, - get_ai_answer) +logger = logging.getLogger(__name__) +router = Router() -@dp.message_handler(Text(contains=['говно'], ignore_case=True)) -async def skirmish_reply(message: types.Message): - await message.reply(MESSAGES['skirmish']) +@router.message(CommandStart()) +async def send_welcome(message: Message): + logger.debug(f'send_welcome.Message: {message.text}') + await message.answer(MESSAGES['welcome']) -@dp.message_handler(Text(contains=['привет', 'с чего начать'], - ignore_case=True)) -async def hello_where_to_reply(message: types.Message): - await message.reply(MESSAGES['hello']) - await message.answer(MESSAGES['start_here']) - await message.answer(MESSAGES['start_video']) - await message.answer(MESSAGES['message_links']) + +@router.message(Command('privacy')) +async def send_privacy(message: Message): + logger.debug(f'send_privacy.Message: {message.text}') + await message.answer(MESSAGES['privacy']) -@dp.message_handler(Text(contains=['привет'], ignore_case=True)) -async def hello_reply(message: types.Message): +@router.message(F.text.lower().contains('привет')) +async def hello_reply(message: Message): + logger.debug(f'hello_reply.Message: {message.text}') await message.reply(MESSAGES['hello']) -@dp.message_handler(Text(contains=['с чего начать'], ignore_case=True)) -async def where_to_begin(message: types.Message): +@router.message(F.text.lower().contains('с чего начать')) +async def where_to_begin(message: Message): + logger.debug(f'where_to_begin.Message: {message.text}') await message.reply(MESSAGES['start_here']) await message.answer(MESSAGES['start_video']) await message.answer(MESSAGES['message_links']) -@dp.message_handler(Text(contains=['https://t.me/oldcoders_bar'], - ignore_case=True)) -async def bar_reply(message: types.Message): +@router.message(F.text.lower().contains('https://t.me/oldcoders_bar')) +async def bar_reply(message: Message): + logger.debug(f'bar_reply.Message: {message.text}') await message.reply(choice(REPLICAS['bar'])) - -@dp.message_handler(commands=['start', 'help']) -async def send_welcome(message: types.Message): - await message.answer( - MESSAGES['welcome'], parse_mode=types.ParseMode.MARKDOWN) +@router.message(F.text.lower().contains('говно')) +async def skirmish_reply(message: Message): + logger.debug(f'skirmish_reply.Message: {message.text}') + await message.reply(MESSAGES['skirmish']) -@dp.message_handler(content_types=types.ContentTypes.TEXT) -@dp.throttled(flood_controlling, rate=FLOOD_RATE) -async def text_reply(message: types.Message): +@router.message(F.text) +async def text_reply(message: Message): + logger.debug(f'text_reply.Message: {message.text}') username = message.from_user.username + userid = message.from_user.id + + # Если имя пользователя Телеграмм пустое создаем имя пользователя из userid + if username is None: + username = 'User_' + str(userid) + logger.debug(f'У пользователя {userid} не задано имя пользователя! Придумаем имя пользователя: {username}') + user_day = USERS.get(username) + logging.debug(f'User id: {userid}') + logging.debug(f'User name: {username}') + logging.debug(f'User day: {user_day}') tz = pytz.timezone('Europe/Moscow') present_date = datetime.now(tz) + logging.debug(f'tz: {tz}, present_date: {present_date}') current_date = present_date - timedelta(hours=5) current_day = current_date.day + logging.debug(f'current_day: {current_day}') if username in USERS and user_day != current_day: + logging.debug(f'Нашли имя {username} в словаре USERS') USERS[username] = current_day await message.reply(choice(REPLICAS[username])) return @@ -86,6 +104,6 @@ async def text_reply(message: types.Message): await message.reply(choice(REPLICAS['n_users'])) -@dp.message_handler(content_types=types.ContentTypes.PHOTO) -async def photo_reply(message: types.Message): - await message.reply(choice(REPLICAS['photo_reply'])) +@router.message(F.photo) +async def photo_reply(message: Message): + await message.reply(choice(REPLICAS['photo_reply'])) \ No newline at end of file diff --git a/lariska_bot/res/l_users.yaml b/lariska_bot/res/l_users.yaml index 3b07727..8adb435 100644 --- a/lariska_bot/res/l_users.yaml +++ b/lariska_bot/res/l_users.yaml @@ -20,5 +20,4 @@ - Lev_Trishankov - lyrian - Xeno_MC -- IgorL2024 - Student_Tequila diff --git a/lariska_bot/res/users.yaml b/lariska_bot/res/users.yaml index e774c7a..1206843 100644 --- a/lariska_bot/res/users.yaml +++ b/lariska_bot/res/users.yaml @@ -16,3 +16,4 @@ - Lev_Trishankov - Harlok13 #- Student_Tequila +- User_1227611413 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3c25553..28f70de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,51 @@ +aiofiles==23.2.1 +aiogram==3.8.0 +aiohttp==3.9.5 +aiosignal==1.3.1 +annotated-types==0.7.0 +async-timeout==4.0.3 +attrs==23.2.0 +Babel==2.9.1 +certifi==2023.11.17 +charset-normalizer==3.3.2 +colorama==0.4.6 +config==0.5.1 +contourpy==1.2.1 +contrib==0.3.0 +cycler==0.12.1 +environs==11.0.0 +fonttools==4.53.0 +frozenlist==1.4.1 fuzzywuzzy==0.18.0 -pytz==2022.7.1 -aiogram==2.25.1 +idna==3.6 +jsonschema==4.22.0 +jsonschema-specifications==2023.12.1 +kiwisolver==1.4.5 +Levenshtein==0.20.9 +magic-filter==1.0.12 +marshmallow==3.21.3 +matplotlib==3.9.0 +multidict==6.0.4 +numpy==2.0.0 +openai==0.27.2 +packaging==24.1 +pillow==10.3.0 +pydantic==2.7.4 +pydantic_core==2.18.4 +pyparsing==3.1.2 +python-dateutil==2.9.0.post0 python-dotenv==1.0.0 python-Levenshtein==0.20.9 -ruamel.yaml==0.17.21 -openai==0.27.2 -requests==2.28.2 \ No newline at end of file +pytz==2022.7.1 +PyYAML==6.0.1 +rapidfuzz==2.15.2 +referencing==0.35.1 +requests==2.31.0 +rpds-py==0.18.1 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.8 +six==1.16.0 +tqdm==4.66.1 +typing_extensions==4.12.2 +urllib3==1.26.18 +yarl==1.9.4 \ No newline at end of file