refactor for new aiogram
This commit is contained in:
parent
4c334e0e32
commit
49942b83ed
|
@ -1,16 +1,16 @@
|
|||
FROM python:3.8 AS builder
|
||||
FROM python:3.12 AS builder
|
||||
COPY requirements.txt .
|
||||
|
||||
# install dependencies to the local user directory (eg. /root/.local)
|
||||
RUN pip install --user -r requirements.txt
|
||||
RUN pip3 install --user -r requirements.txt
|
||||
|
||||
# second stage
|
||||
FROM python:3.8-slim
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /code
|
||||
|
||||
# copy only the dependencies that are needed for our application and the source files
|
||||
COPY --from=builder /root/.local /root/.local
|
||||
COPY ./src .
|
||||
COPY ./source .
|
||||
|
||||
# update PATH
|
||||
ENV PATH=/root/.local:$PATH
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
# expense-bot
|
||||
|
||||
## Local run
|
||||
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
# [WIP]
|
||||
|
||||
# Build Image
|
||||
|
@ -14,4 +24,4 @@ $ podman push [hash]|[localhost/expense-bot:0.1.0] docker://quay.io/ksemele/expe
|
|||
$ podman login quay.io
|
||||
$ podman pull quay.io/ksemele/expense-bot
|
||||
$ podman pull quay.io/ksemele/expense-bot:0.1.0
|
||||
```
|
||||
```
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
aiogram<3.0
|
||||
aiogram~=3.3.0
|
||||
environs==9.5.0
|
||||
requests~=2.27.1
|
|
@ -0,0 +1,55 @@
|
|||
from loader import *
|
||||
|
||||
|
||||
@dp.message(CommandStart())
|
||||
async def command_start_handler(message: Message) -> None:
|
||||
"""
|
||||
This handler receives messages with `/start` command
|
||||
"""
|
||||
# Most event objects have aliases for API methods that can be called in events' context
|
||||
# For example if you want to answer to incoming message you can use `message.answer(...)` alias
|
||||
# and the target chat will be passed to :ref:`aiogram.methods.send_message.SendMessage`
|
||||
# method automatically or call API method directly via
|
||||
# Bot instance: `bot.send_message(chat_id=message.chat.id, ...)`
|
||||
await message.answer(f"Hello, {hbold(message.from_user.full_name)}!")
|
||||
|
||||
|
||||
# @dp.message()
|
||||
# async def echo_handler(message: types.Message) -> None:
|
||||
# """
|
||||
# Handler will forward receive a message back to the sender
|
||||
|
||||
# By default, message handler will handle all message types (like a text, photo, sticker etc.)
|
||||
# """
|
||||
# try:
|
||||
# # Send a copy of the received message
|
||||
# await message.send_copy(chat_id=message.chat.id)
|
||||
# except TypeError:
|
||||
# # But not all the types is supported to be copied so need to handle it
|
||||
# await message.answer("Nice try!")
|
||||
|
||||
|
||||
@dp.message(Command("currencies"))
|
||||
async def get_currencies(message: types.Message):
|
||||
url = "https://nbg.gov.ge/gw/api/ct/monetarypolicy/currencies/en/json/"
|
||||
date = datetime.date.today().isoformat()
|
||||
payload = {"currencies": ["USD", "EUR"], "date": date}
|
||||
r = requests.get(url, params=payload)
|
||||
# print(r.text)
|
||||
usd = json.loads(r.text)
|
||||
for elem in usd:
|
||||
# print(elem['currencies'])
|
||||
for each in elem["currencies"]:
|
||||
# print(each)
|
||||
# print(each['code'] + " " + each['rateFormated'])
|
||||
await message.answer(each["code"] + " " + each["rateFormated"])
|
||||
# print(r.status_code)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
|
||||
asyncio.run(main())
|
|
@ -0,0 +1,8 @@
|
|||
from environs import Env
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
BOT_TOKEN = env.str("BOT_TOKEN")
|
||||
ADMINS = env.list("ADMINS")
|
||||
IP = env.str("HOST_IP")
|
|
@ -0,0 +1,26 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
import datetime
|
||||
from os import getenv
|
||||
|
||||
from aiogram import Bot, Dispatcher, Router, types
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.filters import CommandStart
|
||||
from aiogram.types import Message
|
||||
from aiogram.utils.markdown import hbold
|
||||
from aiogram.filters import (
|
||||
Command,
|
||||
CommandObject,
|
||||
ExceptionMessageFilter,
|
||||
ExceptionTypeFilter,
|
||||
)
|
||||
# envs from .env file
|
||||
from data import config
|
||||
|
||||
# Initialize Bot instance with a default parse mode which will be passed to all API calls
|
||||
# And the run events dispatching
|
||||
bot = Bot(token=config.BOT_TOKEN, parse_mode=ParseMode.HTML)
|
||||
dp = Dispatcher()
|
19
src/app.py
19
src/app.py
|
@ -1,19 +0,0 @@
|
|||
from aiogram import executor
|
||||
|
||||
from loader import dp
|
||||
import middlewares, filters, handlers
|
||||
from utils.notify_admins import on_startup_notify
|
||||
from utils.set_bot_commands import set_default_commands
|
||||
|
||||
|
||||
async def on_startup(dispatcher):
|
||||
# Устанавливаем дефолтные команды
|
||||
await set_default_commands(dispatcher)
|
||||
|
||||
# Уведомляет про запуск
|
||||
await on_startup_notify(dispatcher)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, on_startup=on_startup)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from environs import Env
|
||||
|
||||
# Теперь используем вместо библиотеки python-dotenv библиотеку environs
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
BOT_TOKEN = env.str("BOT_TOKEN") # Забираем значение типа str
|
||||
ADMINS = env.list("ADMINS") # Тут у нас будет список из админов
|
||||
IP = env.str("ip") # Тоже str, но для айпи адреса хоста
|
|
@ -1,9 +0,0 @@
|
|||
from aiogram import Dispatcher
|
||||
|
||||
from loader import dp
|
||||
# from .is_admin import AdminFilter
|
||||
|
||||
|
||||
if __name__ == "filters":
|
||||
# dp.filters_factory.bind(AdminFilter)
|
||||
pass
|
|
@ -1,5 +0,0 @@
|
|||
from . import errors
|
||||
from . import users
|
||||
from . import groups
|
||||
from . import channels
|
||||
|
|
@ -1 +0,0 @@
|
|||
from . import error_handler
|
|
@ -1,36 +0,0 @@
|
|||
import logging
|
||||
from aiogram.utils.exceptions import (TelegramAPIError,
|
||||
MessageNotModified,
|
||||
CantParseEntities)
|
||||
|
||||
|
||||
from loader import dp
|
||||
|
||||
|
||||
@dp.errors_handler()
|
||||
async def errors_handler(update, exception):
|
||||
"""
|
||||
Exceptions handler. Catches all exceptions within task factory tasks.
|
||||
:param dispatcher:
|
||||
:param update:
|
||||
:param exception:
|
||||
:return: stdout logging
|
||||
"""
|
||||
|
||||
if isinstance(exception, MessageNotModified):
|
||||
logging.exception('Message is not modified')
|
||||
# do something here?
|
||||
return True
|
||||
|
||||
if isinstance(exception, CantParseEntities):
|
||||
# or here
|
||||
logging.exception(f'CantParseEntities: {exception} \nUpdate: {update}')
|
||||
return True
|
||||
|
||||
# MUST BE THE LAST CONDITION (ЭТО УСЛОВИЕ ВСЕГДА ДОЛЖНО БЫТЬ В КОНЦЕ)
|
||||
if isinstance(exception, TelegramAPIError):
|
||||
logging.exception(f'TelegramAPIError: {exception} \nUpdate: {update}')
|
||||
return True
|
||||
|
||||
# At least you have tried.
|
||||
logging.exception(f'Update: {update} \n{exception}')
|
|
@ -1,7 +0,0 @@
|
|||
from . import help
|
||||
from . import start
|
||||
from . import echo
|
||||
from . import add
|
||||
from . import settings
|
||||
from . import test
|
||||
from . import currencies
|
|
@ -1,25 +0,0 @@
|
|||
from aiogram import types
|
||||
from loader import dp
|
||||
import sqlite3 as sl
|
||||
|
||||
|
||||
@dp.message_handler(commands="add")
|
||||
async def bot_add(message: types.Message):
|
||||
await message.answer(f"This is ADD command, {message.from_user.username}!")
|
||||
con = sl.connect('test.db')
|
||||
|
||||
# @dp.message_handler(state=None) # todo сделать стейты. сейчас это хэндлит все сообщения в любых состояниях!
|
||||
# async def bot_add_parse_message(message: types.Message):
|
||||
# words = message.text.split()
|
||||
# # Uncomment to see debug in console
|
||||
# # print('Split message:')
|
||||
# # i = 0
|
||||
# # for each in words:
|
||||
# # print('[' + str(i) + ']: ' + '[' + each + ']')
|
||||
# # i += 1
|
||||
# # print('[end]')
|
||||
#
|
||||
# if len(words) == 2:
|
||||
# await message.answer('[added] ' + words[0] + ' ' + words[1])
|
||||
# else:
|
||||
# await message.answer("can't parse. wrong arguments!")
|
|
@ -1,22 +0,0 @@
|
|||
from aiogram import types
|
||||
from loader import dp
|
||||
import json
|
||||
|
||||
|
||||
@dp.message_handler(commands="currencies")
|
||||
async def get_currencies(message: types.Message):
|
||||
import requests
|
||||
import datetime
|
||||
url = 'https://nbg.gov.ge/gw/api/ct/monetarypolicy/currencies/en/json/'
|
||||
date = datetime.date.today().isoformat()
|
||||
payload = {'currencies': ['USD', 'EUR'], 'date': date}
|
||||
r = requests.get(url, params=payload)
|
||||
print(r.text)
|
||||
usd = json.loads(r.text)
|
||||
for elem in usd:
|
||||
# print(elem['currencies'])
|
||||
for each in elem['currencies']:
|
||||
# print(each)
|
||||
# print(each['code'] + " " + each['rateFormated'])
|
||||
await message.answer(each['code'] + " " + each['rateFormated'])
|
||||
# print(r.status_code)
|
|
@ -1,21 +0,0 @@
|
|||
from aiogram import types
|
||||
from aiogram.dispatcher import FSMContext
|
||||
|
||||
# from loader import dp
|
||||
#
|
||||
#
|
||||
# # Эхо хендлер, куда летят текстовые сообщения без указанного состояния
|
||||
# @dp.message_handler(state=None)
|
||||
# async def bot_echo(message: types.Message):
|
||||
# await message.answer(f"Эхо без состояния."
|
||||
# f"Сообщение:\n"
|
||||
# f"{message.text}")
|
||||
#
|
||||
#
|
||||
# # Эхо хендлер, куда летят ВСЕ сообщения с указанным состоянием
|
||||
# @dp.message_handler(state="*", content_types=types.ContentTypes.ANY)
|
||||
# async def bot_echo_all(message: types.Message, state: FSMContext):
|
||||
# state = await state.get_state()
|
||||
# await message.answer(f"Эхо в состоянии <code>{state}</code>.\n"
|
||||
# f"\nСодержание сообщения:\n"
|
||||
# f"<code>{message}</code>")
|
|
@ -1,20 +0,0 @@
|
|||
from aiogram import types
|
||||
from aiogram.dispatcher.filters.builtin import CommandHelp
|
||||
|
||||
from loader import dp
|
||||
|
||||
|
||||
@dp.message_handler(CommandHelp())
|
||||
async def bot_help(message: types.Message):
|
||||
text = ("Available commands: ",
|
||||
"/start - Launch bot.",
|
||||
"/help - Commands list, little help.",
|
||||
"/add - Add expense.",
|
||||
"/edit - Edit expense.",
|
||||
"/list - See my expenses.",
|
||||
"/settings - Configure bot.",
|
||||
"/currencies - Get Official Georgian course",
|
||||
"/test - Test command."
|
||||
)
|
||||
|
||||
await message.answer("\n".join(text))
|
|
@ -1,9 +0,0 @@
|
|||
from aiogram import types
|
||||
from aiogram.dispatcher.filters.builtin import CommandSettings
|
||||
|
||||
from loader import dp
|
||||
|
||||
|
||||
@dp.message_handler(CommandSettings())
|
||||
async def bot_settings(message: types.Message):
|
||||
await message.answer(f"Settings being here...")
|
|
@ -1,9 +0,0 @@
|
|||
from aiogram import types
|
||||
from aiogram.dispatcher.filters.builtin import CommandStart
|
||||
|
||||
from loader import dp
|
||||
|
||||
|
||||
@dp.message_handler(CommandStart())
|
||||
async def bot_start(message: types.Message):
|
||||
await message.answer(f"Hello, {message.from_user.username}!")
|
|
@ -1,76 +0,0 @@
|
|||
from aiogram import types
|
||||
from loader import dp
|
||||
|
||||
|
||||
@dp.message_handler(commands="test")
|
||||
async def cmd_random(message: types.Message):
|
||||
keyboard = types.InlineKeyboardMarkup()
|
||||
keyboard.add(types.InlineKeyboardButton(text="Нажми меня", callback_data="random_value"))
|
||||
keyboard.add(types.InlineKeyboardButton(text="Нажми меня 2", callback_data="random_value2"))
|
||||
await message.answer("test reply 1-2-3", reply_markup=keyboard)
|
||||
|
||||
|
||||
@dp.callback_query_handler(text="random_value")
|
||||
async def send_random_value(call: types.CallbackQuery):
|
||||
await call.message.answer('test reply 1')
|
||||
# shows alert with 'Ok' button
|
||||
await call.answer(text="Спасибо, что воспользовались ботом!", show_alert=True)
|
||||
# или просто await call.answer()
|
||||
|
||||
|
||||
@dp.callback_query_handler(text="random_value2")
|
||||
async def send_random_value(call: types.CallbackQuery):
|
||||
await call.message.answer('test reply 2')
|
||||
await call.answer()
|
||||
|
||||
########################################################################################
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from aiogram.utils.exceptions import MessageNotModified
|
||||
from contextlib import suppress
|
||||
# fabnum - префикс, action - название аргумента, которым будем передавать значение
|
||||
callback_numbers = CallbackData("fabnum", "action")
|
||||
# Здесь хранятся пользовательские данные.
|
||||
# Т.к. это словарь в памяти, то при перезапуске он очистится
|
||||
user_data = {}
|
||||
|
||||
|
||||
def get_keyboard_fab():
|
||||
buttons = [
|
||||
types.InlineKeyboardButton(text="-1", callback_data=callback_numbers.new(action="decr")),
|
||||
types.InlineKeyboardButton(text="+1", callback_data=callback_numbers.new(action="incr")),
|
||||
types.InlineKeyboardButton(text="Подтвердить", callback_data=callback_numbers.new(action="finish"))
|
||||
]
|
||||
keyboard = types.InlineKeyboardMarkup(row_width=2)
|
||||
keyboard.add(*buttons)
|
||||
return keyboard
|
||||
|
||||
|
||||
async def update_num_text_fab(message: types.Message, new_value: int):
|
||||
with suppress(MessageNotModified):
|
||||
await message.edit_text(f"Укажите число: {new_value}", reply_markup=get_keyboard_fab())
|
||||
|
||||
|
||||
@dp.message_handler(commands="numbers_fab")
|
||||
async def cmd_numbers(message: types.Message):
|
||||
user_data[message.from_user.id] = 0
|
||||
await message.answer("Укажите число: 0", reply_markup=get_keyboard_fab())
|
||||
|
||||
|
||||
@dp.callback_query_handler(callback_numbers.filter(action=["incr", "decr"]))
|
||||
async def callbacks_num_change_fab(call: types.CallbackQuery, callback_data: dict):
|
||||
user_value = user_data.get(call.from_user.id, 0)
|
||||
action = callback_data["action"]
|
||||
if action == "incr":
|
||||
user_data[call.from_user.id] = user_value + 1
|
||||
await update_num_text_fab(call.message, user_value + 1)
|
||||
elif action == "decr":
|
||||
user_data[call.from_user.id] = user_value - 1
|
||||
await update_num_text_fab(call.message, user_value - 1)
|
||||
await call.answer()
|
||||
|
||||
|
||||
@dp.callback_query_handler(callback_numbers.filter(action=["finish"]))
|
||||
async def callbacks_num_finish_fab(call: types.CallbackQuery):
|
||||
user_value = user_data.get(call.from_user.id, 0)
|
||||
await call.message.edit_text(f"Итого: {user_value}")
|
||||
await call.answer()
|
|
@ -1,2 +0,0 @@
|
|||
from . import default
|
||||
from . import inline
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
|
||||
from data import config
|
||||
|
||||
bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML)
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot, storage=storage)
|
|
@ -1,8 +0,0 @@
|
|||
from aiogram import Dispatcher
|
||||
|
||||
from loader import dp
|
||||
from .throttling import ThrottlingMiddleware
|
||||
|
||||
|
||||
if __name__ == "middlewares":
|
||||
dp.middleware.setup(ThrottlingMiddleware())
|
|
@ -1,37 +0,0 @@
|
|||
import asyncio
|
||||
|
||||
from aiogram import types, Dispatcher
|
||||
from aiogram.dispatcher import DEFAULT_RATE_LIMIT
|
||||
from aiogram.dispatcher.handler import CancelHandler, current_handler
|
||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||
from aiogram.utils.exceptions import Throttled
|
||||
|
||||
|
||||
class ThrottlingMiddleware(BaseMiddleware):
|
||||
"""
|
||||
Simple middleware
|
||||
"""
|
||||
|
||||
def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix='antiflood_'):
|
||||
self.rate_limit = limit
|
||||
self.prefix = key_prefix
|
||||
super(ThrottlingMiddleware, self).__init__()
|
||||
|
||||
async def on_process_message(self, message: types.Message, data: dict):
|
||||
handler = current_handler.get()
|
||||
dispatcher = Dispatcher.get_current()
|
||||
if handler:
|
||||
limit = getattr(handler, "throttling_rate_limit", self.rate_limit)
|
||||
key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}")
|
||||
else:
|
||||
limit = self.rate_limit
|
||||
key = f"{self.prefix}_message"
|
||||
try:
|
||||
await dispatcher.throttle(key, rate=limit)
|
||||
except Throttled as t:
|
||||
await self.message_throttled(message, t)
|
||||
raise CancelHandler()
|
||||
|
||||
async def message_throttled(self, message: types.Message, throttled: Throttled):
|
||||
if throttled.exceeded_count <= 2:
|
||||
await message.reply("Too many requests!")
|
|
@ -1,3 +0,0 @@
|
|||
from . import db_api
|
||||
from . import misc
|
||||
from .notify_admins import on_startup_notify
|
|
@ -1,2 +0,0 @@
|
|||
from .throttling import rate_limit
|
||||
from . import logging
|
|
@ -1,6 +0,0 @@
|
|||
import logging
|
||||
|
||||
logging.basicConfig(format=u'%(filename)s [LINE:%(lineno)d] #%(levelname)-8s [%(asctime)s] %(message)s',
|
||||
level=logging.INFO,
|
||||
# level=logging.DEBUG, # Можно заменить на другой уровень логгирования.
|
||||
)
|
|
@ -1,16 +0,0 @@
|
|||
def rate_limit(limit: int, key=None):
|
||||
"""
|
||||
Decorator for configuring rate limit and key in different functions.
|
||||
|
||||
:param limit:
|
||||
:param key:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
setattr(func, 'throttling_rate_limit', limit)
|
||||
if key:
|
||||
setattr(func, 'throttling_key', key)
|
||||
return func
|
||||
|
||||
return decorator
|
|
@ -1,14 +0,0 @@
|
|||
import logging
|
||||
|
||||
from aiogram import Dispatcher
|
||||
|
||||
from data.config import ADMINS
|
||||
|
||||
|
||||
async def on_startup_notify(dp: Dispatcher):
|
||||
for admin in ADMINS:
|
||||
try:
|
||||
await dp.bot.send_message(admin, "Voodoo21-expense started")
|
||||
|
||||
except Exception as err:
|
||||
logging.exception(err)
|
|
@ -1,17 +0,0 @@
|
|||
from aiogram import types
|
||||
|
||||
|
||||
async def set_default_commands(dp):
|
||||
await dp.bot.set_my_commands(
|
||||
[
|
||||
types.BotCommand("start", "Launch bot."),
|
||||
types.BotCommand("help", "Commands list, little help."),
|
||||
types.BotCommand("add", "Add expense."),
|
||||
types.BotCommand("edit", "Edit expense."),
|
||||
types.BotCommand("list", "See my expenses."),
|
||||
types.BotCommand("settings", "Configure bot."),
|
||||
types.BotCommand("currencies", "USD\EUR - GEL."),
|
||||
types.BotCommand("test", "Test command."),
|
||||
# types.BotCommand("settings3", "Configure bot."),
|
||||
]
|
||||
)
|
Loading…
Reference in New Issue