commit 0aa2d0967237da61b0e6685eeca63c02f9f49162 Author: Alexander Popov Date: Sun Jan 5 02:50:54 2025 +0300 init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f3430c3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..e4f2f0f --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,5 @@ +# 📑 История версий + +## ... + +... diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..f17d2e7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ... + +... diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..f827b40 --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +- [ ] `podman` контейнер +- [ ] command 4 stop requests diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e0e32f6 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,38 @@ +__author__ = 'Alexander Popov' +"""Автор программы""" +__version__ = '1.0.0' +"""Версия программы""" + +# Импорт системных модулей +from os import getenv + +# Импорт сторонних модулей +from dotenv import load_dotenv +from loguru import logger + +from aiogram import Bot, Dispatcher +from aiogram.client.default import DefaultBotProperties +from aiogram.enums import ParseMode + +# Импорт модулей приложения +from .db import DataBase + +load_dotenv() # Выполяет загрузку переменных окружения из файла .env + +db = DataBase( + getenv('DB_ADDR'), + getenv('DB_PORT'), + getenv('DB_NAME'), + getenv('DB_USER'), + getenv('DB_PWD'), +) +"""Экземпляр класса базы данных""" + +logger.add(getenv('LOG_PATH'), compression='zip') +"""Логгер""" + +dp = Dispatcher() +"""Диспетчер задач Telegram бота""" + +bot = Bot(token=getenv('TOKEN'), default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN_V2)) +"""Клиент Telegram бота""" diff --git a/app/__main__.py b/app/__main__.py new file mode 100644 index 0000000..332f00d --- /dev/null +++ b/app/__main__.py @@ -0,0 +1,29 @@ +# Импорт сторонних модулей +import asyncio + +# Импорт модулей приложения +from . import logger, db, dp, bot +from .methods import * + + +async def main() -> None: + # Подключение к базе данных + logger.info('Подключение к базе данных...') + status = db.connect() + if status == False: + logger.error('Ошибка подключения к базе данных...') + + exit(-1) + else: + logger.info('Подключение к базе данных выполнено успешно!') + + # Запуск Telegram бота + logger.info('Запуск Telegram бота...') + await dp.start_polling(bot) + + logger.info('Отключение от базы данных...') + db.close() + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/app/actions.py b/app/actions.py new file mode 100644 index 0000000..ef49f18 --- /dev/null +++ b/app/actions.py @@ -0,0 +1,34 @@ +# Импорт сторонних модулей +from aiogram import F +from aiogram.types import CallbackQuery, FSInputFile +from aiogram.types import InputMediaPhoto + +# Импорт модулей приложения +from . import dp + +IMAGE = './assets/actions.jpg' +END_MSG = 'Попробуй выполнить команду `/start` ещё раз, чтобы посмотреть на другой результат 😉' + + +@dp.callback_query(F.data == 'action_gift') +async def action_gift(callback: CallbackQuery) -> None: + msg = 'Спасибо за подарок 🤭\n\n' 'Но я принимаю только ' '⛽ нефть ' '🥃 алкоголь ' ' и 🍫 шоколадки\n' '\n' + msg += END_MSG + + await callback.message.edit_media(InputMediaPhoto(media=FSInputFile(path='./assets/gift.jpg'), caption=msg)) + + +@dp.callback_query(F.data == 'action_poo') +async def action_poo(callback: CallbackQuery) -> None: + msg = 'Я увернулся 🫣\n\n' + msg += END_MSG + + await callback.message.edit_media(InputMediaPhoto(media=FSInputFile(path='./assets/poo.jpg'), caption=msg)) + + +@dp.callback_query(F.data == 'action_caress') +async def action_caress(callback: CallbackQuery) -> None: + msg = '🤤\n\n' + msg += END_MSG + + await callback.message.edit_media(InputMediaPhoto(media=FSInputFile(path='./assets/caress.jpg'), caption=msg)) diff --git a/app/db.py b/app/db.py new file mode 100644 index 0000000..a26cd41 --- /dev/null +++ b/app/db.py @@ -0,0 +1,77 @@ +# Импорт сторонних модулей +import psycopg2 + +# Импорт модулей приложения +from . import logger + + +class DataBase(object): + """...""" + + def __init__(self, host: str, port: int, name: str, user: str, password: str): + super(DataBase, self).__init__() + self.host = host + self.port = port + self.name = name + self.user = user + self.password = password + + self.conn = None + + def connect(self) -> bool: + """...""" + + try: + self.conn = psycopg2.connect( + host=self.host, + port=self.port, + user=self.user, + password=self.password, + database=self.name, + ) + + return True + except: + return False + + def check_issues_count(self, telegram_id: int) -> bool: + """...""" + + sql = 'SELECT id FROM issues WHERE telegram_id = %s;' + + cur = self.conn.cursor() + + cur.execute(sql, (telegram_id,)) + + result = cur.fetchall() + + if result == None: + return 0 + else: + return len(result) + + def add_issue(self, telegram_id: int, message: str) -> dict: + """...""" + + sql = 'INSERT INTO issues ' '(telegram_id, message)' ' VALUES (%s, %s);' + + total_issues = self.check_issues_count(telegram_id) + if total_issues >= 3: + return { + 'status': False, + 'message': '⛔ Произошла ошибка.\n' 'Вы оставили много запросов, чтобы я мог оперативно их обработать.', + } + + cur = self.conn.cursor() + + try: + cur.execute(sql, (telegram_id, message)) + self.conn.commit() + + return {'status': True, 'message': '🔔 Замечательно, Ваш запросов отправлен автору.\nЖдите ответа 🙃'} + except Exception as e: + logger.error(e) + return {'status': False, 'message': '⛔ Произошла ошибка'} + + def close(self) -> None: + self.conn.close() diff --git a/app/methods.py b/app/methods.py new file mode 100644 index 0000000..2390b44 --- /dev/null +++ b/app/methods.py @@ -0,0 +1,42 @@ +# Импорт модулей стандартной библиотеки +from random import choice + +# Импорт сторонних модулей +from aiogram import F +from aiogram.filters import CommandStart +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.types import Message, FSInputFile + +# Импорт модулей приложения +from . import logger, db, dp +from .actions import * + + +@dp.message(CommandStart()) +async def command_start_handler(message: Message) -> None: + msg = '👋🏻 Привет\\!\n\nОтправь сообщение боту и 🍪 *Печенька* в скором времени ответит тебе 🤭' + + actions = [ + [InlineKeyboardButton(text='🎁 Сделать подарок', callback_data='action_gift')], + [InlineKeyboardButton(text='💩 Сделать пакость', callback_data='action_poo')], + [InlineKeyboardButton(text='🤤 Погладить', callback_data='action_caress')], + ] + + await message.answer_photo( + photo=FSInputFile(path='./assets/hello.jpg'), + caption=msg, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[choice(actions)]), + ) + + +@dp.message() +async def echo_handler(message: Message) -> None: + telegram_id = message.from_user.id + msg = message.text + + # logger.info('Добавляем запрос от пользователя ...') + status = db.add_issue(telegram_id, msg) + + msg = status['message'].replace('.', '\\.') + + await message.answer(text=msg) diff --git a/assets/caress.jpg b/assets/caress.jpg new file mode 100644 index 0000000..d2f8098 Binary files /dev/null and b/assets/caress.jpg differ diff --git a/assets/gift.jpg b/assets/gift.jpg new file mode 100644 index 0000000..30bb0a7 Binary files /dev/null and b/assets/gift.jpg differ diff --git a/assets/hello.jpg b/assets/hello.jpg new file mode 100644 index 0000000..23b5c12 Binary files /dev/null and b/assets/hello.jpg differ diff --git a/assets/poo.jpg b/assets/poo.jpg new file mode 100644 index 0000000..95a46e5 Binary files /dev/null and b/assets/poo.jpg differ diff --git a/env.example b/env.example new file mode 100644 index 0000000..130e5eb --- /dev/null +++ b/env.example @@ -0,0 +1,9 @@ +TOKEN="" + +LOG_PATH="/tmp/a.log" + +DB_ADDR="" +DB_PORT="" +DB_NAME="" +DB_USER="" +DB_PWD="" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..92afe8c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 123 +skip-string-normalization = 1 diff --git a/sql/issues.sql b/sql/issues.sql new file mode 100644 index 0000000..284ce9f --- /dev/null +++ b/sql/issues.sql @@ -0,0 +1,14 @@ +BEGIN; + +-- CREATE TABLE "issues" --------------------------------------- +CREATE TABLE "public"."issues" ( + "id" Serial NOT NULL, + "telegram_id" BigInt NOT NULL, + "message" Text NOT NULL, + "date" Timestamp Without Time Zone DEFAULT now() NOT NULL, + "reply" Boolean DEFAULT 'false' NOT NULL, + CONSTRAINT "unique_issues_id" UNIQUE( "id" ) ); + ; +-- ------------------------------------------------------------- + +COMMIT; diff --git a/systemd/user/feedback.service b/systemd/user/feedback.service new file mode 100644 index 0000000..07ffb32 --- /dev/null +++ b/systemd/user/feedback.service @@ -0,0 +1,14 @@ +[Unit] +Description=... +After=network.target network-online.target + +[Service] +Type=exec +Environment=APP_DIR=/home/user/feedback_bot +Environment=VIRTUAL_ENV=${APP_DIR}/venv +Environment=PYTHONPATH=${APP_DIR} +Environment=PATH=${VIRTUAL_ENV}/bin:$PATH +ExecStart=/bin/bash -c '${VIRTUAL_ENV}/bin/python3 -m app' + +[Install] +WantedBy=multi-user.target