├── __init__.py ├── data ├── __init__.py └── config.py ├── keyboards ├── __init__.py ├── reply │ └── __init__.py └── inline │ ├── __init__.py │ └── buttons.py ├── utils ├── db │ ├── __init__.py │ └── postgres.py ├── misc │ ├── __init__.py │ └── logging.py ├── api │ ├── __init__.py │ ├── backend.py │ └── base.py ├── __init__.py ├── shortcuts.py ├── set_bot_commands.py ├── notify_admins.py └── pgtoexcel.py ├── handlers ├── errors │ ├── __init__.py │ └── error_handler.py ├── groups │ └── __init__.py ├── users │ ├── __init__.py │ ├── echo.py │ ├── help.py │ ├── start.py │ └── admin.py ├── channels │ └── __init__.py └── __init__.py ├── states ├── __init__.py └── test.py ├── middlewares ├── __init__.py └── throttling.py ├── schemas ├── __init__.py ├── language.py └── user.py ├── requirements.txt ├── filters ├── __init__.py ├── chat_type.py └── admin.py ├── .env.example ├── loader.py ├── locales ├── uz │ └── LC_MESSAGES │ │ └── messages.po ├── en │ └── LC_MESSAGES │ │ └── messages.po └── ru │ └── LC_MESSAGES │ └── messages.po ├── README.md ├── app.py └── .gitignore /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /keyboards/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/db/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /handlers/errors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /handlers/groups/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /handlers/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /keyboards/reply/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /handlers/channels/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/misc/__init__.py: -------------------------------------------------------------------------------- 1 | from . import logging 2 | -------------------------------------------------------------------------------- /keyboards/inline/__init__.py: -------------------------------------------------------------------------------- 1 | from . import buttons -------------------------------------------------------------------------------- /states/__init__.py: -------------------------------------------------------------------------------- 1 | from .test import Test, AdminState 2 | -------------------------------------------------------------------------------- /middlewares/__init__.py: -------------------------------------------------------------------------------- 1 | from .throttling import ThrottlingMiddleware 2 | -------------------------------------------------------------------------------- /utils/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .backend import bot_api_client # noqa 2 | -------------------------------------------------------------------------------- /schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from . import language # noqa 2 | from . import user # noqa 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xalq-mazza-qilsin/Aiogram3-Template/HEAD/requirements.txt -------------------------------------------------------------------------------- /filters/__init__.py: -------------------------------------------------------------------------------- 1 | from .chat_type import ChatTypeFilter # noqa 2 | from .admin import IsBotAdminFilter # noqa 3 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .notify_admins import on_startup_notify 2 | from .set_bot_commands import set_default_commands 3 | from .misc import logging 4 | 5 | -------------------------------------------------------------------------------- /schemas/language.py: -------------------------------------------------------------------------------- 1 | from enum import StrEnum 2 | 3 | 4 | class LanguageEnum(StrEnum): 5 | EN = "en" 6 | UZ = "uz" 7 | RU = "ru" 8 | UNKNOWN = "unknown" 9 | -------------------------------------------------------------------------------- /handlers/users/echo.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router, types 2 | 3 | router = Router() 4 | 5 | 6 | @router.message() 7 | async def start_user(message: types.Message): 8 | await message.answer(message.text) 9 | -------------------------------------------------------------------------------- /states/test.py: -------------------------------------------------------------------------------- 1 | from aiogram.filters.state import StatesGroup, State 2 | 3 | 4 | class Test(StatesGroup): 5 | Q1 = State() 6 | Q2 = State() 7 | 8 | 9 | class AdminState(StatesGroup): 10 | are_you_sure = State() 11 | ask_ad_content = State() 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /schemas/user.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .language import LanguageEnum 4 | 5 | 6 | class User(BaseModel): 7 | telegram_id: int 8 | username: str | None = None 9 | full_name: str | None = None 10 | language: LanguageEnum = LanguageEnum.EN 11 | -------------------------------------------------------------------------------- /keyboards/inline/buttons.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton 2 | 3 | 4 | inline_keyboard = [[ 5 | InlineKeyboardButton(text="✅ Yes", callback_data='yes'), 6 | InlineKeyboardButton(text="❌ No", callback_data='no') 7 | ]] 8 | are_you_sure_markup = InlineKeyboardMarkup(inline_keyboard=inline_keyboard) 9 | -------------------------------------------------------------------------------- /.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 13 | DB_PORT=5432 14 | 15 | BACKEND_HOST=http://127.0.0.1:8000 16 | -------------------------------------------------------------------------------- /utils/api/backend.py: -------------------------------------------------------------------------------- 1 | from .base import BaseAPIClient 2 | from data.config import BACKEND_HOST 3 | 4 | 5 | class BotAPIClient(BaseAPIClient): 6 | def __init__(self): 7 | super().__init__() 8 | self.api_base_url = f"{BACKEND_HOST.rstrip('/')}/api/v1" 9 | self.bot_base_url = self.api_base_url + "/bot" 10 | 11 | 12 | bot_api_client = BotAPIClient() 13 | -------------------------------------------------------------------------------- /handlers/users/help.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router, types 2 | from aiogram.filters.command import Command 3 | 4 | router = Router() 5 | 6 | 7 | @router.message(Command('help')) 8 | async def bot_help(message: types.Message): 9 | text = ("Buyruqlar: ", 10 | "/start - Botni ishga tushirish", 11 | "/help - Yordam") 12 | await message.answer(text="\n".join(text)) 13 | -------------------------------------------------------------------------------- /filters/chat_type.py: -------------------------------------------------------------------------------- 1 | from aiogram.enums import ChatType 2 | from aiogram.filters import BaseFilter 3 | from aiogram.types import Message 4 | 5 | 6 | class ChatTypeFilter(BaseFilter): 7 | def __init__(self, chat_types: list[ChatType]): 8 | self.chat_types = chat_types 9 | 10 | async def __call__(self, message: Message) -> bool: 11 | return message.chat.type in self.chat_types 12 | -------------------------------------------------------------------------------- /filters/admin.py: -------------------------------------------------------------------------------- 1 | from aiogram.filters import BaseFilter 2 | from aiogram.types import Message 3 | 4 | 5 | class IsBotAdminFilter(BaseFilter): 6 | def __init__(self, user_ids: list): 7 | self.user_ids = user_ids 8 | 9 | async def __call__(self, message: Message) -> bool: 10 | admin_ids_int = [int(id) for id in self.user_ids] 11 | return int(message.from_user.id) in admin_ids_int 12 | -------------------------------------------------------------------------------- /utils/api/base.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | 3 | 4 | class BaseAPIClient: 5 | def __init__(self): 6 | self.session = aiohttp.ClientSession() 7 | 8 | async def _send_request( 9 | self, 10 | method, 11 | url, 12 | json=None, 13 | headers=None, 14 | ): 15 | 16 | response = await self.session.request( 17 | method=method, url=url, json=json, headers=headers 18 | ) 19 | return await response.json() 20 | -------------------------------------------------------------------------------- /utils/shortcuts.py: -------------------------------------------------------------------------------- 1 | MARKDOWN_ESCAPE_CHARS = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'] 2 | 3 | 4 | def safe_markdown(_string): 5 | _string = str(_string) 6 | text = "" 7 | for letter in _string: 8 | if letter in MARKDOWN_ESCAPE_CHARS: 9 | text += '\{}'.format(letter) 10 | else: 11 | text += letter 12 | return text 13 | -------------------------------------------------------------------------------- /utils/set_bot_commands.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot 2 | from aiogram.methods.set_my_commands import BotCommand 3 | from aiogram.types import BotCommandScopeAllPrivateChats 4 | 5 | 6 | async def set_default_commands(bot: Bot): 7 | commands = [ 8 | BotCommand(command="/start", description="Botni ishga tushirish"), 9 | BotCommand(command="/help", description="Yordam"), 10 | ] 11 | await bot.set_my_commands(commands=commands, scope=BotCommandScopeAllPrivateChats()) 12 | -------------------------------------------------------------------------------- /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 | DB_PORT = env.str("DB_PORT") 17 | 18 | BACKEND_HOST = env.str("BACKEND_HOST", "http://localhost:8000") 19 | -------------------------------------------------------------------------------- /handlers/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router 2 | from aiogram.enums import ChatType 3 | 4 | from filters import ChatTypeFilter 5 | 6 | 7 | def setup_routers() -> Router: 8 | from .users import admin, start, help, echo 9 | from .errors import error_handler 10 | 11 | router = Router() 12 | 13 | # Agar kerak bo'lsa, o'z filteringizni o'rnating 14 | start.router.message.filter(ChatTypeFilter(chat_types=[ChatType.PRIVATE])) 15 | 16 | router.include_routers(admin.router, start.router, help.router, echo.router, error_handler.router) 17 | 18 | return router 19 | -------------------------------------------------------------------------------- /utils/notify_admins.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from aiogram import Bot 4 | 5 | from data.config import ADMINS 6 | 7 | 8 | async def on_startup_notify(bot: Bot): 9 | for admin in ADMINS: 10 | try: 11 | bot_properties = await bot.me() 12 | message = ["Bot ishga tushdi.\n", 13 | f"Bot ID: {bot_properties.id}", 14 | f"Bot Username: {bot_properties.username}"] 15 | await bot.send_message(int(admin), "\n".join(message)) 16 | except Exception as err: 17 | logging.exception(err) 18 | -------------------------------------------------------------------------------- /loader.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Dispatcher 2 | from aiogram.client.default import DefaultBotProperties 3 | from aiogram.enums.parse_mode import ParseMode 4 | from aiogram.utils.i18n import I18n, FSMI18nMiddleware 5 | from aiogram.fsm.storage.memory import MemoryStorage 6 | 7 | from utils.db.postgres import Database 8 | from data.config import BOT_TOKEN 9 | 10 | 11 | db = Database() 12 | bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) 13 | 14 | i18n = I18n(path="./locales", domain="messages") 15 | I18n.set_current(i18n) 16 | storage = MemoryStorage() 17 | dispatcher = Dispatcher(storage=storage) 18 | i18n_middleware = FSMI18nMiddleware(i18n=i18n) 19 | i18n_middleware.setup(dispatcher) 20 | -------------------------------------------------------------------------------- /locales/uz/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # Uzbek translations for PROJECT. 2 | # Copyright (C) 2024 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2024-10-26 21:39+0500\n" 11 | "PO-Revision-Date: 2024-10-26 21:39+0500\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language: uz\n" 14 | "Language-Team: uz \n" 15 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.9.1\n" 20 | 21 | -------------------------------------------------------------------------------- /locales/en/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # English translations for PROJECT. 2 | # Copyright (C) 2024 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2024-10-26 21:39+0500\n" 11 | "PO-Revision-Date: 2024-10-26 21:39+0500\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language: en\n" 14 | "Language-Team: en \n" 15 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.9.1\n" 20 | 21 | -------------------------------------------------------------------------------- /locales/ru/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # Russian translations for PROJECT. 2 | # Copyright (C) 2024 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2024-10-26 21:39+0500\n" 11 | "PO-Revision-Date: 2024-10-26 21:39+0500\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language: ru\n" 14 | "Language-Team: ru \n" 15 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " 16 | "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=utf-8\n" 19 | "Content-Transfer-Encoding: 8bit\n" 20 | "Generated-By: Babel 2.9.1\n" 21 | 22 | -------------------------------------------------------------------------------- /middlewares/throttling.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from aiogram.dispatcher.middlewares.base import BaseMiddleware 4 | from aiogram.types import Message 5 | 6 | 7 | class ThrottlingMiddleware(BaseMiddleware): 8 | def __init__(self, slow_mode_delay=0.5): 9 | self.user_timeouts = {} 10 | self.slow_mode_delay = slow_mode_delay 11 | super(ThrottlingMiddleware, self).__init__() 12 | 13 | async def __call__(self, handler, event: Message, data): 14 | user_id = event.from_user.id 15 | current_time = time.time() 16 | 17 | # Ushbu foydalanuvchining so'nggi so'rovi bo'yicha yozuv mavjudligini tekshirish 18 | last_request_time = self.user_timeouts.get(user_id, 0) 19 | if current_time - last_request_time < self.slow_mode_delay: 20 | # Agar so'rovlar juda tez-tez bo'lsa, sekin rejimni yoqish 21 | await event.reply("Juda ko'p so'rov! Biroz kuting.") 22 | return 23 | 24 | else: 25 | # Oxirgi so'rovning vaqtini yangilash 26 | self.user_timeouts[user_id] = current_time 27 | # Event ni handlerga o'tkazish 28 | return await handler(event, data) 29 | -------------------------------------------------------------------------------- /utils/pgtoexcel.py: -------------------------------------------------------------------------------- 1 | import openpyxl 2 | from openpyxl.styles import Font 3 | 4 | 5 | async def export_to_excel(data, headings, filepath): 6 | """ 7 | Exports data from PostgreSQL to an Excel spreadsheet using psycopg2. 8 | 9 | Arguments: 10 | connection - an open psycopg2 (this function does not close the connection) 11 | query_string - SQL to get data 12 | headings - list of strings to use as column headings 13 | filepath - path and filename of the Excel file 14 | 15 | psycopg2 and file handling errors bubble up to calling code. 16 | """ 17 | 18 | wb = openpyxl.Workbook() 19 | sheet = wb.active 20 | 21 | sheet.row_dimensions[1].font = Font(bold=True) 22 | 23 | # Spreadsheet row and column indexes start at 1, 24 | # so we use "start = 1" in enumerate, so 25 | # we don't need to add 1 to the indexes. 26 | for colno, heading in enumerate(headings, start=1): 27 | sheet.cell(row=1, column=colno).value = heading 28 | 29 | # This time we use "start = 2" to skip the heading row. 30 | for rowno, row in enumerate(data, start=2): 31 | for colno, cell_value in enumerate(row, start=1): 32 | sheet.cell(row=rowno, column=colno).value = cell_value 33 | 34 | wb.save(filepath) 35 | -------------------------------------------------------------------------------- /handlers/users/start.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router, types 2 | from aiogram.filters import CommandStart 3 | from aiogram.enums.parse_mode import ParseMode 4 | from aiogram.client.session.middlewares.request_logging import logger 5 | from loader import db, bot 6 | from data.config import ADMINS 7 | from utils.shortcuts import safe_markdown 8 | 9 | router = Router() 10 | 11 | 12 | @router.message(CommandStart()) 13 | async def do_start(message: types.Message): 14 | """ 15 | MARKDOWN V2 | HTML 16 | link: [Google](https://google.com/) | Google 17 | bold: *Qalin text* | Qalin text 18 | italic: _Yotiq shriftdagi text_ | Yotiq shriftdagi text 19 | 20 | 21 | 22 | ************** Note ************** 23 | Markdownda _ * [ ] ( ) ~ ` > # + - = | { } . ! belgilari to'g'ridan to'g'ri ishlatilmaydi!!! 24 | Bu belgilarni ishlatish uchun oldidan \ qo'yish esdan chiqmasin. Masalan \. ko'rinishi . belgisini ishlatish uchun yozilgan. 25 | """ 26 | 27 | telegram_id = message.from_user.id 28 | full_name = message.from_user.full_name 29 | username = message.from_user.username 30 | user = None 31 | try: 32 | user = await db.add_user(telegram_id=telegram_id, full_name=full_name, username=username) 33 | except Exception as error: 34 | logger.info(error) 35 | if user: 36 | count = await db.count_users() 37 | msg = (f"[{safe_markdown(user['full_name'])}](tg://user?id={user['telegram_id']}) bazaga qo'shildi\.\nBazada {count} ta foydalanuvchi bor\.") 38 | else: 39 | msg = f"[{safe_markdown(full_name)}](tg://user?id={telegram_id}) bazaga oldin qo'shilgan" 40 | for admin in ADMINS: 41 | try: 42 | await bot.send_message( 43 | chat_id=admin, 44 | text=msg, 45 | parse_mode=ParseMode.MARKDOWN_V2 46 | ) 47 | except Exception as error: 48 | logger.info(f"Data did not send to admin: {admin}. Error: {error}") 49 | await message.answer(f"Assalomu alaykum {safe_markdown(full_name)}\!", parse_mode=ParseMode.MARKDOWN_V2) 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aiogram New Template (aiogram 3) 2 | 3 | ### 1. Create virtual environment and install packages 4 | Windows 5 | ```shell 6 | python -m venv venv && .\venv\Scripts\activate && pip install -r requirements.txt 7 | ``` 8 | 9 | Linux/Mac 10 | ```shell 11 | python3 -m venv venv && source venv/bin/activate && pip3 install -r requirements.txt 12 | ``` 13 | 14 | ### 2. Create .env file and copy all variables from .env_example to it and customize your self (if needed) 15 | 16 | ### 3. Run app.py 17 | Windows 18 | ```shell 19 | python app.py 20 | ``` 21 | Linux/Mac 22 | ```shell 23 | python3 app.py 24 | ``` 25 | 26 | 3. Compile translations in locales dir with this command 27 | ```shell 28 | pybabel compile -d locales -D messages 29 | ``` 30 | 31 | # Set up Postgresql on server 32 | 33 | ### 1. Install postgresql (if needed) 34 | ```shell 35 | sudo apt install -y postgresql postgresql-contrib 36 | ``` 37 | 38 | ### 2. Log in to the postgresql shell 39 | ```shell 40 | sudo -u postres psql 41 | ``` 42 | 43 | ### 3. Create a database (in postgresql shell) 44 | ```shell 45 | CREATE DATABASE database_name WITH template = template0 ENCODING 'UTF8' LC_CTYPE 'C' LC_COLLATE 'C'; 46 | ``` 47 | 48 | ### 4. Create a user (in postgresql shell) 49 | ```shell 50 | CREATE USER user_name WITH PASSWORD 'password'; 51 | ``` 52 | 53 | ### 5. Set encoding (in postgresql shell) 54 | ```shell 55 | ALTER ROLE user_name SET client_encoding TO 'utf8'; 56 | ``` 57 | 58 | ### 6. Restrict transactions from an unexpected db user (in postgresql shell) 59 | ```shell 60 | ALTER ROLE user_name SET default_transaction_isolation TO 'read committed'; 61 | ``` 62 | 63 | ### 7. Set timezone (in postgresql shell) 64 | ```shell 65 | ALTER ROLE user_name SET timezone TO 'UTC'; 66 | ``` 67 | > **_Note:_** If you use another timezone in your project, replace **'UTC'** with yours. 68 | 69 | ### 8. Grant the user the right to manage the db (in postgresql shell) 70 | ```shell 71 | GRANT ALL PRIVILEGES ON DATABASE database_name TO user_name; 72 | ``` 73 | 74 | ### 9. Quit postgresql (in postgresql shell) 75 | ```shell 76 | \q 77 | ``` 78 | 79 | ## If you have questions for this project, join and ask our community: https://t.me/+Wu3loL2thM8yZDMy 80 | 81 |

82 | 83 |

84 | -------------------------------------------------------------------------------- /handlers/users/admin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import asyncio 3 | from aiogram import Router, types 4 | from aiogram.filters import Command 5 | from aiogram.fsm.context import FSMContext 6 | from loader import db, bot 7 | from keyboards.inline.buttons import are_you_sure_markup 8 | from states.test import AdminState 9 | from filters.admin import IsBotAdminFilter 10 | from data.config import ADMINS 11 | from utils.pgtoexcel import export_to_excel 12 | 13 | router = Router() 14 | 15 | 16 | @router.message(Command('allusers'), IsBotAdminFilter(ADMINS)) 17 | async def get_all_users(message: types.Message): 18 | users = await db.select_all_users() 19 | 20 | file_path = f"data/users_list.xlsx" 21 | await export_to_excel(data=users, headings=['ID', 'Full Name', 'Username', 'Telegram ID'], filepath=file_path) 22 | 23 | await message.answer_document(types.input_file.FSInputFile(file_path)) 24 | 25 | 26 | @router.message(Command('reklama'), IsBotAdminFilter(ADMINS)) 27 | async def ask_ad_content(message: types.Message, state: FSMContext): 28 | await message.answer("Reklama uchun post yuboring") 29 | await state.set_state(AdminState.ask_ad_content) 30 | 31 | 32 | @router.message(AdminState.ask_ad_content, IsBotAdminFilter(ADMINS)) 33 | async def send_ad_to_users(message: types.Message, state: FSMContext): 34 | users = await db.select_all_users() 35 | count = 0 36 | for user in users: 37 | user_id = user[-1] 38 | try: 39 | await message.send_copy(chat_id=user_id) 40 | count += 1 41 | await asyncio.sleep(0.05) 42 | except Exception as error: 43 | logging.info(f"Ad did not send to user: {user_id}. Error: {error}") 44 | await message.answer(text=f"Reklama {count} ta foydalauvchiga muvaffaqiyatli yuborildi.") 45 | await state.clear() 46 | 47 | 48 | @router.message(Command('cleandb'), IsBotAdminFilter(ADMINS)) 49 | async def ask_are_you_sure(message: types.Message, state: FSMContext): 50 | msg = await message.reply("Haqiqatdan ham bazani tozalab yubormoqchimisiz?", reply_markup=are_you_sure_markup) 51 | await state.update_data(msg_id=msg.message_id) 52 | await state.set_state(AdminState.are_you_sure) 53 | 54 | 55 | @router.callback_query(AdminState.are_you_sure, IsBotAdminFilter(ADMINS)) 56 | async def clean_db(call: types.CallbackQuery, state: FSMContext): 57 | data = await state.get_data() 58 | msg_id = data.get('msg_id') 59 | if call.data == 'yes': 60 | await db.delete_users() 61 | text = "Baza tozalandi!" 62 | elif call.data == 'no': 63 | text = "Bekor qilindi." 64 | await bot.edit_message_text(text=text, chat_id=call.message.chat.id, message_id=msg_id) 65 | await state.clear() 66 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiogram import Bot, Dispatcher 4 | from aiogram.client.session.middlewares.request_logging import logger 5 | from aiogram.enums import ChatType 6 | from loader import db 7 | 8 | 9 | def setup_handlers(dispatcher: Dispatcher) -> None: 10 | """HANDLERS""" 11 | from handlers import setup_routers 12 | 13 | dispatcher.include_router(setup_routers()) 14 | 15 | 16 | def setup_middlewares(dispatcher: Dispatcher, bot: Bot) -> None: 17 | """MIDDLEWARE""" 18 | from middlewares.throttling import ThrottlingMiddleware 19 | 20 | # Spamdan himoya qilish uchun klassik ichki o'rta dastur. So'rovlar orasidagi asosiy vaqtlar 0,5 soniya 21 | dispatcher.message.middleware(ThrottlingMiddleware(slow_mode_delay=0.5)) 22 | 23 | 24 | def setup_filters(dispatcher: Dispatcher) -> None: 25 | """FILTERS""" 26 | from filters import ChatTypeFilter 27 | 28 | # Chat turini aniqlash uchun klassik umumiy filtr 29 | # Filtrni handlers/users/__init__ -dagi har bir routerga alohida o'rnatish mumkin 30 | dispatcher.message.filter(ChatTypeFilter(chat_types=[ChatType.PRIVATE])) 31 | 32 | 33 | async def setup_aiogram(dispatcher: Dispatcher, bot: Bot) -> None: 34 | logger.info("Configuring aiogram") 35 | setup_handlers(dispatcher=dispatcher) 36 | setup_middlewares(dispatcher=dispatcher, bot=bot) 37 | setup_filters(dispatcher=dispatcher) 38 | logger.info("Configured aiogram") 39 | 40 | 41 | async def database_connected(): 42 | # Ma'lumotlar bazasini yaratamiz: 43 | await db.create() 44 | # await db.drop_users() 45 | await db.create_table_users() 46 | 47 | 48 | async def aiogram_on_startup_polling(dispatcher: Dispatcher, bot: Bot) -> None: 49 | from utils.set_bot_commands import set_default_commands 50 | from utils.notify_admins import on_startup_notify 51 | 52 | logger.info("Database connected") 53 | await database_connected() 54 | 55 | logger.info("Starting polling") 56 | await bot.delete_webhook(drop_pending_updates=True) 57 | await setup_aiogram(bot=bot, dispatcher=dispatcher) 58 | await on_startup_notify(bot=bot) 59 | await set_default_commands(bot=bot) 60 | 61 | 62 | async def aiogram_on_shutdown_polling(dispatcher: Dispatcher, bot: Bot): 63 | logger.info("Stopping polling") 64 | await bot.session.close() 65 | await dispatcher.storage.close() 66 | 67 | 68 | def main(): 69 | """CONFIG""" 70 | from data.config import BOT_TOKEN 71 | from aiogram.enums import ParseMode 72 | from aiogram.fsm.storage.memory import MemoryStorage 73 | 74 | bot = Bot(token=BOT_TOKEN, parse_mode=ParseMode.HTML) 75 | storage = MemoryStorage() 76 | dispatcher = Dispatcher(storage=storage) 77 | 78 | dispatcher.startup.register(aiogram_on_startup_polling) 79 | dispatcher.shutdown.register(aiogram_on_shutdown_polling) 80 | asyncio.run(dispatcher.start_polling(bot, close_bot_session=True)) 81 | # allowed_updates=['message', 'chat_member'] 82 | 83 | 84 | if __name__ == "__main__": 85 | try: 86 | main() 87 | except KeyboardInterrupt: 88 | logger.info("Bot stopped!") 89 | -------------------------------------------------------------------------------- /utils/db/postgres.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 | 32 | async with self.pool.acquire() as connection: 33 | connection: Connection 34 | async with connection.transaction(): 35 | if fetch: 36 | result = await connection.fetch(command, *args) 37 | elif fetchval: 38 | result = await connection.fetchval(command, *args) 39 | elif fetchrow: 40 | result = await connection.fetchrow(command, *args) 41 | elif execute: 42 | result = await connection.execute(command, *args) 43 | return result 44 | 45 | async def create_table_users(self): 46 | sql = """ 47 | CREATE TABLE IF NOT EXISTS Users ( 48 | id SERIAL PRIMARY KEY, 49 | full_name VARCHAR(255) NOT NULL, 50 | username varchar(255) NULL, 51 | telegram_id BIGINT NOT NULL UNIQUE 52 | ); 53 | """ 54 | await self.execute(sql, execute=True) 55 | 56 | @staticmethod 57 | def format_args(sql, parameters: dict): 58 | sql += " AND ".join( 59 | [f"{item} = ${num}" for num, item in enumerate(parameters.keys(), start=1)] 60 | ) 61 | return sql, tuple(parameters.values()) 62 | 63 | async def add_user(self, full_name, username, telegram_id): 64 | sql = "INSERT INTO users (full_name, username, telegram_id) VALUES($1, $2, $3) returning *" 65 | return await self.execute(sql, full_name, username, telegram_id, fetchrow=True) 66 | 67 | async def select_all_users(self): 68 | sql = "SELECT * FROM Users" 69 | return await self.execute(sql, fetch=True) 70 | 71 | async def select_user(self, **kwargs): 72 | sql = "SELECT * FROM Users WHERE " 73 | sql, parameters = self.format_args(sql, parameters=kwargs) 74 | return await self.execute(sql, *parameters, fetchrow=True) 75 | 76 | async def count_users(self): 77 | sql = "SELECT COUNT(*) FROM Users" 78 | return await self.execute(sql, fetchval=True) 79 | 80 | async def update_user_username(self, username, telegram_id): 81 | sql = "UPDATE Users SET username=$1 WHERE telegram_id=$2" 82 | return await self.execute(sql, username, telegram_id, execute=True) 83 | 84 | async def delete_users(self): 85 | await self.execute("DELETE FROM Users WHERE TRUE", execute=True) 86 | 87 | async def drop_users(self): 88 | await self.execute("DROP TABLE Users", execute=True) 89 | -------------------------------------------------------------------------------- /.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 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 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 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | .idea/ 161 | data/*.xlsx -------------------------------------------------------------------------------- /handlers/errors/error_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any 3 | 4 | from aiogram import Router 5 | from aiogram.exceptions import (TelegramAPIError, 6 | TelegramUnauthorizedError, 7 | TelegramBadRequest, 8 | TelegramNetworkError, 9 | TelegramNotFound, 10 | TelegramConflictError, 11 | TelegramForbiddenError, 12 | RestartingTelegram, 13 | CallbackAnswerException, 14 | TelegramEntityTooLarge, 15 | TelegramRetryAfter, 16 | TelegramMigrateToChat, 17 | TelegramServerError) 18 | from aiogram.handlers import ErrorHandler 19 | 20 | 21 | router = Router() 22 | 23 | 24 | @router.errors() 25 | class MyErrorHandler(ErrorHandler): 26 | async def handle(self, ) -> Any: 27 | """ 28 | Exceptions handler. Catches all exceptions within task factory tasks. 29 | :param dispatcher: 30 | :param update: 31 | :param exception: 32 | :return: stdout logging 33 | """ 34 | if isinstance(self.exception_name, TelegramUnauthorizedError): 35 | """ 36 | Bot tokeni yaroqsiz bo'lsa, xatolik uyushtiriladi. 37 | """ 38 | logging.info(f'Unauthorized: {self.exception_message}') 39 | return True 40 | 41 | if isinstance(self.exception_name, TelegramNetworkError): 42 | """ 43 | Telegram tarmog'idagi barcha xatoliklar uchun xatolik uyushtiriladi. 44 | """ 45 | logging.exception(f'NetworkError: {self.exception_message} \nUpdate: {self.update}') 46 | return True 47 | 48 | if isinstance(self.exception_name, TelegramNotFound): 49 | """ 50 | Suhbat, xabar, foydalanuvchi va boshqalar topilmasa, xatolik uyushtiriladi. 51 | """ 52 | logging.exception(f'NotFound: {self.exception_message} \nUpdate: {self.update}') 53 | return True 54 | 55 | if isinstance(self.exception_name, TelegramConflictError): 56 | """ 57 | Bot tokeni takroran ishlatilinayotganida xatolik uyushtiriladi. 58 | """ 59 | logging.exception(f'ConflictError: {self.exception_message} \nUpdate: {self.update}') 60 | return True 61 | 62 | if isinstance(self.exception_name, TelegramForbiddenError): 63 | """ 64 | Bot chatdan chiqarib yuborilishi kabi holatlarda xatolik uyushtiriladi. 65 | """ 66 | logging.exception(f'ForbiddenError: {self.exception_message} \nUpdate: {self.update}') 67 | return True 68 | 69 | if isinstance(self.exception_name, CallbackAnswerException): 70 | """ 71 | Javob qaytmasligi kabi holatlarda xatolik uyushtiriladi. 72 | """ 73 | logging.exception(f'CallbackAnswerException: {self.exception_message} \nUpdate: {self.update}') 74 | return True 75 | 76 | if isinstance(self.exception_name, TelegramMigrateToChat): 77 | """ 78 | Suhbat superguruhga ko'chirilganda xatolik uyushtiriladi. 79 | """ 80 | logging.exception(f'BadRequest: {self.exception_message} \nUpdate: {self.update}') 81 | return True 82 | 83 | if isinstance(self.exception_name, TelegramServerError): 84 | """ 85 | Telegram serveri 5xx xatosini qaytarsa, xatolik uyushtiriladi. 86 | """ 87 | logging.exception(f'BadRequest: {self.exception_message} \nUpdate: {self.update}') 88 | return True 89 | 90 | if isinstance(self.exception_name, TelegramAPIError): 91 | """ 92 | Barcha Telegram API xatoliklari uchun xatolik uyushtiriladi. 93 | """ 94 | logging.exception(f'EntityTooLarge: {self.exception_message} \nUpdate: {self.update}') 95 | return True 96 | 97 | if isinstance(self.exception_name, TelegramRetryAfter): 98 | """ 99 | So'rovlar ko'payib ketganda xatolik uyushtiriladi. 100 | """ 101 | logging.exception(f'BadRequest: {self.exception_message} \nUpdate: {self.update}') 102 | return True 103 | 104 | if isinstance(self.exception_name, TelegramEntityTooLarge): 105 | """ 106 | So'rov paytida ma'lumotlar limitdan oshganda xatolik uyushtiriladi. 107 | """ 108 | logging.exception(f'EntityTooLarge: {self.exception_message} \nUpdate: {self.update}') 109 | return True 110 | 111 | if isinstance(self.exception_name, TelegramBadRequest): 112 | """ 113 | So'rov noto'g'ri formatda bo'lganda xatolik uyushtiriladi. 114 | """ 115 | logging.exception(f'BadRequest: {self.exception_message} \nUpdate: {self.update}') 116 | return True 117 | 118 | if isinstance(self.exception_name, RestartingTelegram): 119 | """ 120 | Telegram serverini qayta ishga tushirishda xatolik uyushtiriladi. 121 | """ 122 | logging.exception(f'RestartingTelegram: {self.exception_message} \nUpdate: {self.update}') 123 | return True 124 | 125 | logging.exception(f'Update: {self.update} \n{self.exception_name}') 126 | --------------------------------------------------------------------------------