├── data
├── __init__.py
└── config.py
├── states
└── __init__.py
├── handlers
├── groups
│ └── __init__.py
├── channels
│ └── __init__.py
├── errors
│ ├── __init__.py
│ └── error_handler.py
├── __init__.py
└── users
│ ├── __init__.py
│ ├── echo.py
│ ├── help.py
│ ├── admin.py
│ └── start.py
├── utils
├── db_api
│ ├── __init__.py
│ └── postgresql.py
├── misc
│ ├── __init__.py
│ ├── logging.py
│ └── throttling.py
├── __init__.py
├── set_bot_commands.py
├── extra_datas.py
└── notify_admins.py
├── keyboards
├── default
│ └── __init__.py
├── inline
│ └── __init__.py
└── __init__.py
├── requirements.txt
├── README.md
├── filters
└── __init__.py
├── middlewares
├── __init__.py
└── throttling.py
├── .env_example
├── loader.py
├── app.py
└── .gitignore
/data/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/states/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/handlers/groups/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/db_api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/handlers/channels/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/keyboards/default/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/keyboards/inline/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/handlers/errors/__init__.py:
--------------------------------------------------------------------------------
1 | from . import error_handler
2 |
--------------------------------------------------------------------------------
/keyboards/__init__.py:
--------------------------------------------------------------------------------
1 | from . import default
2 | from . import inline
3 |
--------------------------------------------------------------------------------
/utils/misc/__init__.py:
--------------------------------------------------------------------------------
1 | from .throttling import rate_limit
2 | from . import logging
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiogram==2.25.1
2 | environs~=8.0.0
3 | pandas==1.5.3
4 | asyncpg==0.27.0
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aiogram-Template
2 | Pythonda Aiogramdan foydalanib bot yaratish uchun shablon
3 |
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from . import db_api
2 | from . import misc
3 | from .notify_admins import on_startup_notify
4 |
--------------------------------------------------------------------------------
/handlers/__init__.py:
--------------------------------------------------------------------------------
1 | from . import errors
2 | from . import users
3 | from . import groups
4 | from . import channels
5 |
--------------------------------------------------------------------------------
/handlers/users/__init__.py:
--------------------------------------------------------------------------------
1 | from . import help
2 | from . import start
3 | from . import admin
4 | from . import echo
5 |
--------------------------------------------------------------------------------
/filters/__init__.py:
--------------------------------------------------------------------------------
1 | from aiogram import Dispatcher
2 |
3 | from loader import dp
4 | # from .is_admin import AdminFilter
5 |
6 |
7 | if __name__ == "filters":
8 | #dp.filters_factory.bind(is_admin)
9 | pass
10 |
--------------------------------------------------------------------------------
/middlewares/__init__.py:
--------------------------------------------------------------------------------
1 | from aiogram import Dispatcher
2 |
3 | from loader import dp
4 | from .throttling import ThrottlingMiddleware
5 |
6 |
7 | if __name__ == "middlewares":
8 | dp.middleware.setup(ThrottlingMiddleware())
9 |
--------------------------------------------------------------------------------
/handlers/users/echo.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 |
3 | from loader import dp
4 |
5 |
6 | # Echo bot
7 | @dp.message_handler(state=None)
8 | async def bot_echo(message: types.Message):
9 | await message.answer(message.text)
10 |
--------------------------------------------------------------------------------
/.env_example:
--------------------------------------------------------------------------------
1 | # YANGI .env FAYL YARATING VA
2 | # QUYIDAGI MA'LUMOTLARNI YOZING:
3 |
4 | ADMINS=12345678,12345677,12345676
5 | BOT_TOKEN=123452345243:ABshDlsFIjklL
6 |
7 |
8 | # PostgreSQL
9 | DB_USER=postgres
10 | DB_PASS=1234567
11 | DB_NAME=telegram_bot
12 | DB_HOST=localhost
--------------------------------------------------------------------------------
/utils/set_bot_commands.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 |
3 |
4 | async def set_default_commands(dp):
5 | await dp.bot.set_my_commands(
6 | [
7 | types.BotCommand("start", "Botni ishga tushurish"),
8 | types.BotCommand("help", "Yordam"),
9 | ]
10 | )
11 |
--------------------------------------------------------------------------------
/utils/misc/logging.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logging.basicConfig(format=u'%(filename)s [LINE:%(lineno)d] #%(levelname)-8s [%(asctime)s] %(message)s',
4 | level=logging.INFO,
5 | # level=logging.DEBUG, # Можно заменить на другой уровень логгирования.
6 | )
7 |
--------------------------------------------------------------------------------
/utils/extra_datas.py:
--------------------------------------------------------------------------------
1 | escape_chars = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']
2 |
3 |
4 | def make_title(title):
5 | name = ""
6 | for letter in title:
7 | if letter in escape_chars:
8 | name += f'\{letter}'
9 | else:
10 | name += letter
11 | return name
12 |
--------------------------------------------------------------------------------
/loader.py:
--------------------------------------------------------------------------------
1 | from aiogram import Bot, Dispatcher, types
2 | from aiogram.contrib.fsm_storage.memory import MemoryStorage
3 | from utils.db_api.postgresql import Database
4 | from data import config
5 |
6 | bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML)
7 | storage = MemoryStorage()
8 | dp = Dispatcher(bot, storage=storage)
9 | db = Database()
10 |
--------------------------------------------------------------------------------
/utils/notify_admins.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from aiogram import Dispatcher
4 |
5 | from data.config import ADMINS
6 |
7 |
8 | async def on_startup_notify(dp: Dispatcher):
9 | for admin in ADMINS:
10 | try:
11 | await dp.bot.send_message(admin, "Bot ishga tushdi")
12 |
13 | except Exception as err:
14 | logging.exception(err)
15 |
--------------------------------------------------------------------------------
/handlers/users/help.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 | from aiogram.dispatcher.filters.builtin import CommandHelp
3 |
4 | from loader import dp
5 |
6 |
7 | @dp.message_handler(CommandHelp())
8 | async def bot_help(message: types.Message):
9 | text = ("Buyruqlar: ",
10 | "/start - Botni ishga tushirish",
11 | "/help - Yordam")
12 |
13 | await message.answer("\n".join(text))
14 |
--------------------------------------------------------------------------------
/data/config.py:
--------------------------------------------------------------------------------
1 | from environs import Env
2 |
3 | # environs kutubxonasidan foydalanish
4 | env = Env()
5 | env.read_env()
6 |
7 | # .env fayl ichidan quyidagilarni o'qiymiz
8 | BOT_TOKEN = env.str("BOT_TOKEN") # Bot Token
9 | ADMINS = env.list("ADMINS") # adminlar ro'yxati
10 |
11 |
12 | DB_USER = env.str("DB_USER")
13 | DB_PASS = env.str("DB_PASS")
14 | DB_NAME = env.str("DB_NAME")
15 | DB_HOST = env.str("DB_HOST")
16 |
--------------------------------------------------------------------------------
/utils/misc/throttling.py:
--------------------------------------------------------------------------------
1 | def rate_limit(limit: int, key=None):
2 | """
3 | Decorator for configuring rate limit and key in different functions.
4 |
5 | :param limit:
6 | :param key:
7 | :return:
8 | """
9 |
10 | def decorator(func):
11 | setattr(func, 'throttling_rate_limit', limit)
12 | if key:
13 | setattr(func, 'throttling_key', key)
14 | return func
15 |
16 | return decorator
17 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | from aiogram import executor
2 |
3 | from loader import dp, db
4 | import middlewares, filters, handlers
5 | from utils.notify_admins import on_startup_notify
6 | from utils.set_bot_commands import set_default_commands
7 |
8 |
9 | async def on_startup(dispatcher):
10 | # Ma'lumotlar bazasini yaratamiz:
11 | await db.create()
12 | # await db.drop_users()
13 | await db.create_table_users()
14 |
15 | # Birlamchi komandalar (/start va /help)
16 | await set_default_commands(dispatcher)
17 |
18 | # Bot ishga tushgani haqida adminga xabar berish
19 | await on_startup_notify(dispatcher)
20 |
21 |
22 | if __name__ == '__main__':
23 | executor.start_polling(dp, on_startup=on_startup)
24 |
--------------------------------------------------------------------------------
/handlers/users/admin.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from aiogram import types
3 | from data.config import ADMINS
4 | from loader import dp, db, bot
5 | import pandas as pd
6 |
7 | @dp.message_handler(text="/allusers", user_id=ADMINS)
8 | async def get_all_users(message: types.Message):
9 | users = await db.select_all_users()
10 | id = []
11 | name = []
12 | for user in users:
13 | id.append(user[-1])
14 | name.append(user[1])
15 | data = {
16 | "Telegram ID": id,
17 | "Name": name
18 | }
19 | pd.options.display.max_rows = 10000
20 | df = pd.DataFrame(data)
21 | if len(df) > 50:
22 | for x in range(0, len(df), 50):
23 | await bot.send_message(message.chat.id, df[x:x + 50])
24 | else:
25 | await bot.send_message(message.chat.id, df)
26 |
27 |
28 | @dp.message_handler(text="/reklama", user_id=ADMINS)
29 | async def send_ad_to_all(message: types.Message):
30 | users = await db.select_all_users()
31 | for user in users:
32 | user_id = user[-1]
33 | await bot.send_message(chat_id=user_id, text="@BekoDev kanaliga obuna bo'ling!")
34 | await asyncio.sleep(0.05)
35 |
36 | @dp.message_handler(text="/cleandb", user_id=ADMINS)
37 | async def get_all_users(message: types.Message):
38 | await db.delete_users()
39 | await message.answer("Baza tozalandi!")
40 |
--------------------------------------------------------------------------------
/middlewares/throttling.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | from aiogram import types, Dispatcher
4 | from aiogram.dispatcher import DEFAULT_RATE_LIMIT
5 | from aiogram.dispatcher.handler import CancelHandler, current_handler
6 | from aiogram.dispatcher.middlewares import BaseMiddleware
7 | from aiogram.utils.exceptions import Throttled
8 |
9 |
10 | class ThrottlingMiddleware(BaseMiddleware):
11 | """
12 | Simple middleware
13 | """
14 |
15 | def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix='antiflood_'):
16 | self.rate_limit = limit
17 | self.prefix = key_prefix
18 | super(ThrottlingMiddleware, self).__init__()
19 |
20 | async def on_process_message(self, message: types.Message, data: dict):
21 | handler = current_handler.get()
22 | dispatcher = Dispatcher.get_current()
23 | if handler:
24 | limit = getattr(handler, "throttling_rate_limit", self.rate_limit)
25 | key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}")
26 | else:
27 | limit = self.rate_limit
28 | key = f"{self.prefix}_message"
29 | try:
30 | await dispatcher.throttle(key, rate=limit)
31 | except Throttled as t:
32 | await self.message_throttled(message, t)
33 | raise CancelHandler()
34 |
35 | async def message_throttled(self, message: types.Message, throttled: Throttled):
36 | if throttled.exceeded_count <= 2:
37 | await message.reply("Too many requests!")
38 |
--------------------------------------------------------------------------------
/handlers/users/start.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 | from aiogram.dispatcher.filters.builtin import CommandStart
3 | from loader import dp, db, bot
4 | from data.config import ADMINS
5 | from utils.extra_datas import make_title
6 |
7 |
8 | @dp.message_handler(CommandStart())
9 | async def bot_start(message: types.Message):
10 | """
11 | MARKDOWN V2 | HTML
12 | link: [Google](https://google.com/) | Google
13 | bold: *Qalin text* | Qalin text
14 | italic: _Yotiq shriftdagi text_ | Yotiq shriftdagi text
15 |
16 |
17 |
18 | ************** Note **************
19 | Markdownda _ * [ ] ( ) ~ ` > # + - = | { } . ! belgilari to'g'ridan to'g'ri ishlatilmaydi!!!
20 | Bu belgilarni ishlatish uchun oldidan \ qo'yish esdan chiqmasin. Masalan \. ko'rinishi . belgisini ishlatish uchun yozilgan.
21 | """
22 |
23 |
24 | full_name = message.from_user.full_name
25 | user = await db.select_user(telegram_id=message.from_user.id)
26 | if user is None:
27 | user = await db.add_user(
28 | telegram_id=message.from_user.id,
29 | full_name=full_name,
30 | username=message.from_user.username,
31 | )
32 | # ADMINGA xabar beramiz
33 | count = await db.count_users()
34 | msg = f"[{make_title(user['full_name'])}](tg://user?id={user['telegram_id']}) bazaga qo'shildi\.\nBazada {count} ta foydalanuvchi bor\."
35 | await bot.send_message(chat_id=ADMINS[0], text=msg, parse_mode=types.ParseMode.MARKDOWN_V2)
36 | else:
37 | await bot.send_message(chat_id=ADMINS[0], text=f"[{make_title(full_name)}](tg://user?id={message.from_user.id}) bazaga oldin qo'shilgan", disable_web_page_preview=True, parse_mode=types.ParseMode.MARKDOWN_V2)
38 | await message.answer(f"Xush kelibsiz\! {make_title(full_name)}", parse_mode=types.ParseMode.MARKDOWN_V2)
39 |
--------------------------------------------------------------------------------
/handlers/errors/error_handler.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from aiogram.utils.exceptions import (Unauthorized, InvalidQueryID, TelegramAPIError,
3 | CantDemoteChatCreator, MessageNotModified, MessageToDeleteNotFound,
4 | MessageTextIsEmpty, RetryAfter,
5 | CantParseEntities, MessageCantBeDeleted)
6 |
7 |
8 | from loader import dp
9 |
10 |
11 | @dp.errors_handler()
12 | async def errors_handler(update, exception):
13 | """
14 | Exceptions handler. Catches all exceptions within task factory tasks.
15 | :param dispatcher:
16 | :param update:
17 | :param exception:
18 | :return: stdout logging
19 | """
20 |
21 | if isinstance(exception, CantDemoteChatCreator):
22 | logging.exception("Can't demote chat creator")
23 | return True
24 |
25 | if isinstance(exception, MessageNotModified):
26 | logging.exception('Message is not modified')
27 | return True
28 | if isinstance(exception, MessageCantBeDeleted):
29 | logging.exception('Message cant be deleted')
30 | return True
31 |
32 | if isinstance(exception, MessageToDeleteNotFound):
33 | logging.exception('Message to delete not found')
34 | return True
35 |
36 | if isinstance(exception, MessageTextIsEmpty):
37 | logging.exception('MessageTextIsEmpty')
38 | return True
39 |
40 | if isinstance(exception, Unauthorized):
41 | logging.exception(f'Unauthorized: {exception}')
42 | return True
43 |
44 | if isinstance(exception, InvalidQueryID):
45 | logging.exception(f'InvalidQueryID: {exception} \nUpdate: {update}')
46 | return True
47 |
48 | if isinstance(exception, TelegramAPIError):
49 | logging.exception(f'TelegramAPIError: {exception} \nUpdate: {update}')
50 | return True
51 | if isinstance(exception, RetryAfter):
52 | logging.exception(f'RetryAfter: {exception} \nUpdate: {update}')
53 | return True
54 | if isinstance(exception, CantParseEntities):
55 | logging.exception(f'CantParseEntities: {exception} \nUpdate: {update}')
56 | return True
57 |
58 | logging.exception(f'Update: {update} \n{exception}')
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/utils/db_api/postgresql.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | import asyncpg
4 | from asyncpg import Connection
5 | from asyncpg.pool import Pool
6 |
7 | from data import config
8 |
9 |
10 | class Database:
11 | def __init__(self):
12 | self.pool: Union[Pool, None] = None
13 |
14 | async def create(self):
15 | self.pool = await asyncpg.create_pool(
16 | user=config.DB_USER,
17 | password=config.DB_PASS,
18 | host=config.DB_HOST,
19 | database=config.DB_NAME,
20 | )
21 |
22 | async def execute(
23 | self,
24 | command,
25 | *args,
26 | fetch: bool = False,
27 | fetchval: bool = False,
28 | fetchrow: bool = False,
29 | execute: bool = False,
30 | ):
31 | async with self.pool.acquire() as connection:
32 | connection: Connection
33 | async with connection.transaction():
34 | if fetch:
35 | result = await connection.fetch(command, *args)
36 | elif fetchval:
37 | result = await connection.fetchval(command, *args)
38 | elif fetchrow:
39 | result = await connection.fetchrow(command, *args)
40 | elif execute:
41 | result = await connection.execute(command, *args)
42 | return result
43 |
44 | async def create_table_users(self):
45 | sql = """
46 | CREATE TABLE IF NOT EXISTS Users (
47 | id SERIAL PRIMARY KEY,
48 | full_name VARCHAR(255) NOT NULL,
49 | username varchar(255) NULL,
50 | telegram_id BIGINT NOT NULL UNIQUE
51 | );
52 | """
53 | await self.execute(sql, execute=True)
54 |
55 | @staticmethod
56 | def format_args(sql, parameters: dict):
57 | sql += " AND ".join(
58 | [f"{item} = ${num}" for num, item in enumerate(parameters.keys(), start=1)]
59 | )
60 | return sql, tuple(parameters.values())
61 |
62 | async def add_user(self, full_name, username, telegram_id):
63 | sql = "INSERT INTO users (full_name, username, telegram_id) VALUES($1, $2, $3) returning *"
64 | return await self.execute(sql, full_name, username, telegram_id, fetchrow=True)
65 |
66 | async def select_all_users(self):
67 | sql = "SELECT * FROM Users"
68 | return await self.execute(sql, fetch=True)
69 |
70 | async def select_user(self, **kwargs):
71 | sql = "SELECT * FROM Users WHERE "
72 | sql, parameters = self.format_args(sql, parameters=kwargs)
73 | return await self.execute(sql, *parameters, fetchrow=True)
74 |
75 | async def count_users(self):
76 | sql = "SELECT COUNT(*) FROM Users"
77 | return await self.execute(sql, fetchval=True)
78 |
79 | async def update_user_username(self, username, telegram_id):
80 | sql = "UPDATE Users SET username=$1 WHERE telegram_id=$2"
81 | return await self.execute(sql, username, telegram_id, execute=True)
82 |
83 | async def delete_users(self):
84 | await self.execute("DELETE FROM Users WHERE TRUE", execute=True)
85 |
86 | async def drop_users(self):
87 | await self.execute("DROP TABLE Users", execute=True)
88 |
--------------------------------------------------------------------------------