Initial commit

This commit is contained in:
Alexey 2022-04-01 23:02:03 +04:00
commit 9a05d542e2
31 changed files with 373 additions and 0 deletions

6
.env.dist Normal file
View File

@ -0,0 +1,6 @@
# ЭТО ПРИМЕР ФАЙЛА .env !! ВАМ НАДО ЭТОТ ФАЙЛ ПЕРЕИМЕНОВАТЬ И ВСТАВИТЬ ТУДА ЗНАЧЕНИЯ.
# ЭТИ КОММЕНТАРИИ НАДО УДАЛИТЬ!
ADMINS=12345678,12345677,12345676
BOT_TOKEN=123452345243:Asdfasdfasf
ip=localhost

131
.gitignore vendored Normal file
View File

@ -0,0 +1,131 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.venv
*/.env
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
.idea/*
.env

19
app.py Normal file
View File

@ -0,0 +1,19 @@
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)

0
data/__init__.py Normal file
View File

10
data/config.py Normal file
View File

@ -0,0 +1,10 @@
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, но для айпи адреса хоста

9
filters/__init__.py Normal file
View File

@ -0,0 +1,9 @@
from aiogram import Dispatcher
from loader import dp
# from .is_admin import AdminFilter
if __name__ == "filters":
# dp.filters_factory.bind(AdminFilter)
pass

5
handlers/__init__.py Normal file
View File

@ -0,0 +1,5 @@
from . import errors
from . import users
from . import groups
from . import channels

View File

View File

@ -0,0 +1 @@
from . import error_handler

View File

@ -0,0 +1,37 @@
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}')

View File

View File

@ -0,0 +1,3 @@
from . import help
from . import start
from . import echo

0
handlers/users/asa.py Normal file
View File

21
handlers/users/echo.py Normal file
View File

@ -0,0 +1,21 @@
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>")

13
handlers/users/help.py Normal file
View File

@ -0,0 +1,13 @@
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 = ("Список команд: ",
"/start - Начать диалог",
"/help - Получить справку")
await message.answer("\n".join(text))

9
handlers/users/start.py Normal file
View File

@ -0,0 +1,9 @@
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"Привет, {message.from_user.full_name}!")

2
keyboards/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from . import default
from . import inline

View File

View File

@ -0,0 +1 @@

8
loader.py Normal file
View File

@ -0,0 +1,8 @@
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)

8
middlewares/__init__.py Normal file
View File

@ -0,0 +1,8 @@
from aiogram import Dispatcher
from loader import dp
from .throttling import ThrottlingMiddleware
if __name__ == "middlewares":
dp.middleware.setup(ThrottlingMiddleware())

37
middlewares/throttling.py Normal file
View File

@ -0,0 +1,37 @@
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!")

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
aiogram<3.0
environs~=8.0.0

0
states/__init__.py Normal file
View File

3
utils/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from . import db_api
from . import misc
from .notify_admins import on_startup_notify

0
utils/db_api/__init__.py Normal file
View File

2
utils/misc/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from .throttling import rate_limit
from . import logging

6
utils/misc/logging.py Normal file
View File

@ -0,0 +1,6 @@
import logging
logging.basicConfig(format=u'%(filename)s [LINE:%(lineno)d] #%(levelname)-8s [%(asctime)s] %(message)s',
level=logging.INFO,
# level=logging.DEBUG, # Можно заменить на другой уровень логгирования.
)

16
utils/misc/throttling.py Normal file
View File

@ -0,0 +1,16 @@
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

14
utils/notify_admins.py Normal file
View File

@ -0,0 +1,14 @@
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, "Бот Запущен")
except Exception as err:
logging.exception(err)

10
utils/set_bot_commands.py Normal file
View File

@ -0,0 +1,10 @@
from aiogram import types
async def set_default_commands(dp):
await dp.bot.set_my_commands(
[
types.BotCommand("start", "Запустить бота"),
types.BotCommand("help", "Вывести справку"),
]
)