init
This commit is contained in:
commit
0aa2d09672
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@ -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
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.env
|
5
HISTORY.md
Normal file
5
HISTORY.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 📑 История версий
|
||||||
|
|
||||||
|
## ...
|
||||||
|
|
||||||
|
...
|
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -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 <http://unlicense.org>
|
2
TODO.md
Normal file
2
TODO.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
- [ ] `podman` контейнер
|
||||||
|
- [ ] command 4 stop requests
|
38
app/__init__.py
Normal file
38
app/__init__.py
Normal file
@ -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 бота"""
|
29
app/__main__.py
Normal file
29
app/__main__.py
Normal file
@ -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())
|
34
app/actions.py
Normal file
34
app/actions.py
Normal file
@ -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))
|
77
app/db.py
Normal file
77
app/db.py
Normal file
@ -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()
|
42
app/methods.py
Normal file
42
app/methods.py
Normal file
@ -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)
|
BIN
assets/caress.jpg
Normal file
BIN
assets/caress.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
BIN
assets/gift.jpg
Normal file
BIN
assets/gift.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
BIN
assets/hello.jpg
Normal file
BIN
assets/hello.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 216 KiB |
BIN
assets/poo.jpg
Normal file
BIN
assets/poo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 187 KiB |
9
env.example
Normal file
9
env.example
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
TOKEN=""
|
||||||
|
|
||||||
|
LOG_PATH="/tmp/a.log"
|
||||||
|
|
||||||
|
DB_ADDR=""
|
||||||
|
DB_PORT=""
|
||||||
|
DB_NAME=""
|
||||||
|
DB_USER=""
|
||||||
|
DB_PWD=""
|
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[tool.black]
|
||||||
|
line-length = 123
|
||||||
|
skip-string-normalization = 1
|
14
sql/issues.sql
Normal file
14
sql/issues.sql
Normal file
@ -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;
|
14
systemd/user/feedback.service
Normal file
14
systemd/user/feedback.service
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user