From 6bb78e61d6a4f581e595cf50d236adfd205f383e Mon Sep 17 00:00:00 2001 From: ProgerOffline Date: Wed, 27 Sep 2023 11:33:09 +0300 Subject: [PATCH 1/8] make migrations to aiogram 3.x --- requirements.txt | 1 - src/bot/app.py | 25 ++++++++++++----- src/bot/filters/__init__.py | 6 ---- src/bot/filters/user_not_subscribed.py | 9 +++--- src/bot/filters/user_subscribed.py | 9 +++--- src/bot/handlers/__init__.py | 8 +++--- src/bot/handlers/channels_join_requests.py | 6 ++-- src/bot/handlers/close_functionality.py | 9 ++++-- src/bot/handlers/payment.py | 28 +++++++++++++------ src/bot/handlers/start.py | 9 +++--- src/bot/keyboards/inline.py | 7 +++-- src/bot/keyboards/reply.py | 28 ++++++++----------- src/bot/loader.py | 8 ++++-- src/bot/middlewares/__init__.py | 6 ---- src/bot/middlewares/create_user_middleware.py | 24 ++++++++-------- src/bot/middlewares/logger_middleware.py | 17 ++++++++--- src/bot/statesgroup.py | 2 +- 17 files changed, 113 insertions(+), 89 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0f91ca9..5a505fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ aiofiles==23.1.0 -aiogram==2.24 aiohttp==3.8.5 aiosignal==1.3.1 aiosqlite==0.19.0 diff --git a/src/bot/app.py b/src/bot/app.py index 7d3c886..32a20df 100644 --- a/src/bot/app.py +++ b/src/bot/app.py @@ -1,20 +1,31 @@ # -*- coding: utf-8 -*- -from aiogram import Dispatcher, executor +import asyncio + from database import \ create_schema_if_not_exist as database_create_schema_if_not_exist -from loader import dp, tasks_scheduler +from handlers import (channels_join_requests_router, + close_functionality_router, payment_router, start_router) +from loader import bot, dp, tasks_scheduler +from middlewares import CreateUserMiddleware, UpdateLoggerMiddleware + +async def on_startup(): + dp.update.middleware(UpdateLoggerMiddleware()) + dp.message.middleware(CreateUserMiddleware()) -async def on_startup(dp: Dispatcher): - import filters - import handlers - import middlewares + dp.include_routers( + start_router, + payment_router, + close_functionality_router, + channels_join_requests_router, + ) await database_create_schema_if_not_exist() tasks_scheduler.start() + await dp.start_polling(bot) if __name__ == "__main__": - executor.start_polling(dp, on_startup=on_startup, skip_updates=True) + asyncio.run(on_startup()) diff --git a/src/bot/filters/__init__.py b/src/bot/filters/__init__.py index f6143cd..65ca39e 100644 --- a/src/bot/filters/__init__.py +++ b/src/bot/filters/__init__.py @@ -1,10 +1,4 @@ # -*- coding: utf-8 -*- -from loader import dp - from .user_not_subscribed import UserNotSubscribedFilter from .user_subscribed import UserSubscribedFilter - -if __name__ == "filters": - dp.filters_factory.bind(UserSubscribedFilter) - dp.filters_factory.bind(UserNotSubscribedFilter) diff --git a/src/bot/filters/user_not_subscribed.py b/src/bot/filters/user_not_subscribed.py index 6a99419..63c863e 100644 --- a/src/bot/filters/user_not_subscribed.py +++ b/src/bot/filters/user_not_subscribed.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- from aiogram import types -from aiogram.dispatcher.filters import Filter +from aiogram.filters import BaseFilter from database import users -class UserNotSubscribedFilter(Filter): - key = "user_not_subscribed" +class UserNotSubscribedFilter(BaseFilter): + def __init__(self): + pass - async def check(self, message: types.Message): + async def __call__(self, message: types.Message) -> bool: user = await users.get(telegram_id=message.from_user.id) if user is None: return False return user.days_sub_end <= 0 diff --git a/src/bot/filters/user_subscribed.py b/src/bot/filters/user_subscribed.py index 2797e85..842ed07 100644 --- a/src/bot/filters/user_subscribed.py +++ b/src/bot/filters/user_subscribed.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- from aiogram import types -from aiogram.dispatcher.filters import Filter +from aiogram.filters import BaseFilter from database import users -class UserSubscribedFilter(Filter): - key = "user_subscribed" +class UserSubscribedFilter(BaseFilter): + def __init__(self): + pass - async def check(self, message: types.Message): + async def __call__(self, message: types.Message) -> bool: user = await users.get(telegram_id=message.from_user.id) if user is None: return False return user.days_sub_end >= 1 diff --git a/src/bot/handlers/__init__.py b/src/bot/handlers/__init__.py index 8fbee22..e0e66e2 100644 --- a/src/bot/handlers/__init__.py +++ b/src/bot/handlers/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from .channels_join_requests import * -from .close_functionality import * -from .payment import * -from .start import * +from .channels_join_requests import channels_join_requests_router +from .close_functionality import close_functionality_router +from .payment import payment_router +from .start import start_router diff --git a/src/bot/handlers/channels_join_requests.py b/src/bot/handlers/channels_join_requests.py index 04c0bd4..62636ca 100644 --- a/src/bot/handlers/channels_join_requests.py +++ b/src/bot/handlers/channels_join_requests.py @@ -1,9 +1,9 @@ -from aiogram import types +from aiogram import Router, types from database import users -from loader import dp +channels_join_requests_router = Router() -@dp.chat_join_request_handler() +@channels_join_requests_router.chat_join_request() async def private_channel_join_request(chat_join_request: types.ChatJoinRequest): user = await users.get(telegram_id=chat_join_request.from_user.id) if user is None: return diff --git a/src/bot/handlers/close_functionality.py b/src/bot/handlers/close_functionality.py index a6689dc..730cb4a 100644 --- a/src/bot/handlers/close_functionality.py +++ b/src/bot/handlers/close_functionality.py @@ -1,10 +1,13 @@ -from aiogram import types +from aiogram import F, Router, types from filters import UserSubscribedFilter from keyboards import inline as inline_keyboard -from loader import dp +close_functionality_router = Router() -@dp.message_handler(UserSubscribedFilter(), text='Show close functionality') +@close_functionality_router.message( + UserSubscribedFilter(), + F.text=='Show close functionality', +) async def show_private_channels(message: types.Message): await message.answer( text='You can subscribe to closed channels.', diff --git a/src/bot/handlers/payment.py b/src/bot/handlers/payment.py index 2665c7f..90c7479 100644 --- a/src/bot/handlers/payment.py +++ b/src/bot/handlers/payment.py @@ -1,18 +1,21 @@ # -*- coding: utf-8 -*- -from aiogram import types -from aiogram.dispatcher import FSMContext +from aiogram import F, Router, types +from aiogram.fsm.context import FSMContext from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20, USDT_TRC20_WALLET_ADDRESS) from database import transactions from filters.user_not_subscribed import UserNotSubscribedFilter from keyboards import reply as reply_keyboards -from loader import dp from statesgroup import GetTxidFromUser from utils import tronscan_service +payment_router = Router() -@dp.message_handler(UserNotSubscribedFilter(), text='Make subscription') +@payment_router.message( + UserNotSubscribedFilter(), + F.text=='Make subscription', +) async def make_subscription(message: types.Message): await message.answer( text=f'To pay, use this USDT TC20 wallet: {USDT_TRC20_WALLET_ADDRESS}.\n' @@ -22,21 +25,28 @@ async def make_subscription(message: types.Message): ) -@dp.message_handler(UserNotSubscribedFilter(), text='Confirm transfer') -async def confirm_transfer(message: types.Message): - await GetTxidFromUser.state.set() +@payment_router.message( + UserNotSubscribedFilter(), + F.text=='Confirm transfer', +) +async def confirm_transfer(message: types.Message, state: FSMContext): + await state.set_state(GetTxidFromUser.state) await message.answer( text='Great, send me the transaction txid to verify the transfer.', reply_markup=types.ReplyKeyboardRemove(), ) -@dp.message_handler(UserNotSubscribedFilter(), state=GetTxidFromUser.state) +@payment_router.message( + UserNotSubscribedFilter(), + GetTxidFromUser.state, +) async def check_transaction(message: types.Message, state: FSMContext): transaction = await transactions.get(txid=message.text) + if message.text is None: return if transaction is None and tronscan_service.is_valid_transaction_hash(message.text): - await state.finish() + await state.clear() await transactions.create(message.text, message.from_user.id) await message.answer( text='Great, wait for the end of the transaction, ' diff --git a/src/bot/handlers/start.py b/src/bot/handlers/start.py index 16a3c59..6d90efa 100644 --- a/src/bot/handlers/start.py +++ b/src/bot/handlers/start.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- -from aiogram import types +from aiogram import Router, types +from aiogram.filters import CommandStart from database import users from filters.user_not_subscribed import UserNotSubscribedFilter from filters.user_subscribed import UserSubscribedFilter from keyboards import reply as reply_keyboards -from loader import dp +start_router = Router() -@dp.message_handler(UserSubscribedFilter(), commands=['start'], state="*") +@start_router.message(UserSubscribedFilter(), CommandStart()) async def start_for_subsribed_user(message: types.Message): user = await users.get(telegram_id=message.from_user.id) if user is None:return @@ -20,7 +21,7 @@ async def start_for_subsribed_user(message: types.Message): reply_markup=await reply_keyboards.close_functionality(), ) -@dp.message_handler(UserNotSubscribedFilter(), commands=['start'], state="*") +@start_router.message(UserNotSubscribedFilter(), CommandStart()) async def start_for_not_subsribed_user(message: types.Message): await message.answer( text='Hello. Subscribe to the bot to get access to the closed functionality.', diff --git a/src/bot/keyboards/inline.py b/src/bot/keyboards/inline.py index ea0e3f3..bf0ce17 100644 --- a/src/bot/keyboards/inline.py +++ b/src/bot/keyboards/inline.py @@ -1,16 +1,17 @@ from aiogram import types +from aiogram.utils.keyboard import InlineKeyboardBuilder from data.config import private_channels async def channels() -> types.InlineKeyboardMarkup: - keyboard = types.InlineKeyboardMarkup() + builder = InlineKeyboardBuilder() for name in private_channels.keys(): - keyboard.add( + builder.add( types.InlineKeyboardButton( text=name, url=private_channels[name]['invite_url'], ) ) - return keyboard + return builder.as_markup(resize_keyboard=True) diff --git a/src/bot/keyboards/reply.py b/src/bot/keyboards/reply.py index 7b4a8ac..80d5180 100644 --- a/src/bot/keyboards/reply.py +++ b/src/bot/keyboards/reply.py @@ -4,32 +4,28 @@ async def close_functionality(): - return types.ReplyKeyboardMarkup(resize_keyboard=True).add( - types.KeyboardButton( - text='Show close functionality', - ) + return types.ReplyKeyboardMarkup( + keyboard=[[types.KeyboardButton(text='Show close functionality')]], + resize_keyboard=True, ) async def make_subscribtion(): - return types.ReplyKeyboardMarkup(resize_keyboard=True).add( - types.KeyboardButton( - text='Make subscription', - ) + return types.ReplyKeyboardMarkup( + keyboard=[[types.KeyboardButton(text='Make subscription')]], + resize_keyboard=True, ) async def confirm_transfer(): - return types.ReplyKeyboardMarkup(resize_keyboard=True).add( - types.KeyboardButton( - text='Confirm transfer', - ) + return types.ReplyKeyboardMarkup( + keyboard=[[types.KeyboardButton(text='Confirm transfer')]], + resize_keyboard=True, ) async def check_transaction(): - return types.ReplyKeyboardMarkup(resize_keyboard=True).add( - types.KeyboardButton( - text='Check transaction', - ) + return types.ReplyKeyboardMarkup( + keyboard=[[types.KeyboardButton( text='Check transaction')]], + resize_keyboard=True, ) diff --git a/src/bot/loader.py b/src/bot/loader.py index ffe4c71..33b0578 100644 --- a/src/bot/loader.py +++ b/src/bot/loader.py @@ -1,10 +1,12 @@ import os -from aiogram import Bot, Dispatcher, types -from aiogram.contrib.fsm_storage.memory import MemoryStorage +from aiogram import Bot, Dispatcher +from aiogram.enums import ParseMode +from aiogram.fsm.storage.memory import MemoryStorage from apscheduler.schedulers.asyncio import AsyncIOScheduler from data.config import BOT_TOKEN from logzero import logfile, logger +from middlewares import CreateUserMiddleware, UpdateLoggerMiddleware from utils import (decrease_subscription_days, kick_users_from_channels, subscription_checker) @@ -12,7 +14,7 @@ os.system('mkdir logs') logfile('logs/bot.log') -bot = Bot(token=BOT_TOKEN, parse_mode=types.ParseMode.HTML) +bot = Bot(token=BOT_TOKEN, parse_mode=ParseMode.HTML) storage = MemoryStorage() dp = Dispatcher(bot=bot, storage=storage) diff --git a/src/bot/middlewares/__init__.py b/src/bot/middlewares/__init__.py index 55bdfb6..d9694c5 100644 --- a/src/bot/middlewares/__init__.py +++ b/src/bot/middlewares/__init__.py @@ -1,10 +1,4 @@ # -*- coding: utf-8 -*- -from loader import dp - from .create_user_middleware import CreateUserMiddleware from .logger_middleware import UpdateLoggerMiddleware - -if __name__ == "middlewares": - dp.middleware.setup(UpdateLoggerMiddleware()) - dp.middleware.setup(CreateUserMiddleware()) diff --git a/src/bot/middlewares/create_user_middleware.py b/src/bot/middlewares/create_user_middleware.py index 1303b99..076a9d1 100644 --- a/src/bot/middlewares/create_user_middleware.py +++ b/src/bot/middlewares/create_user_middleware.py @@ -1,26 +1,28 @@ # -*- coding: utf-8 -*- -from aiogram import types -from aiogram.dispatcher.middlewares import BaseMiddleware +from collections.abc import Awaitable, Callable +from typing import Any, Dict + +from aiogram import BaseMiddleware +from aiogram.types import Message +from aiogram.types.base import TelegramObject from database import users class CreateUserMiddleware(BaseMiddleware): - async def on_pre_process_message(self, message: types.Message, data: dict): + async def __call__( + self, + handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], + message: Message, + data: Dict[str, Any] + ) -> Any: await self._create_user_if_not_exist( message.from_user.id, message.from_user.first_name, message.from_user.last_name, message.from_user.username, ) - - async def on_pre_process_callback_query(self, call: types.CallbackQuery, data: dict): - await self._create_user_if_not_exist( - call.from_user.id, - call.from_user.first_name, - call.from_user.last_name, - call.from_user.username, - ) + await handler(message, data) async def _create_user_if_not_exist(self, telegram_id: int, first_name: str, last_name: str, username: str): await users.create_if_not_exist(telegram_id, first_name, last_name, username) diff --git a/src/bot/middlewares/logger_middleware.py b/src/bot/middlewares/logger_middleware.py index 383d5e3..326be61 100644 --- a/src/bot/middlewares/logger_middleware.py +++ b/src/bot/middlewares/logger_middleware.py @@ -1,10 +1,19 @@ # -*- coding: utf-8 -*- -from aiogram import types -from aiogram.dispatcher.middlewares import BaseMiddleware +from collections.abc import Awaitable, Callable +from typing import Any, Dict + +from aiogram import BaseMiddleware, types +from aiogram.types import TelegramObject from loader import logger class UpdateLoggerMiddleware(BaseMiddleware): - async def on_process_update(self, update: types.Update, data: dict): - logger.info(update) + async def __call__( + self, + handler: Callable[[types.TelegramObject, Dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: Dict[str, Any] + ) -> Any: + logger.info(event) + await handler(event, data) diff --git a/src/bot/statesgroup.py b/src/bot/statesgroup.py index fb1355c..a328606 100644 --- a/src/bot/statesgroup.py +++ b/src/bot/statesgroup.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.fsm.state import State, StatesGroup class GetTxidFromUser(StatesGroup): From cfe8b8108d0f7269e2799fd588d0f7ce26a9f6e7 Mon Sep 17 00:00:00 2001 From: ProgerOffline Date: Wed, 27 Sep 2023 11:34:57 +0300 Subject: [PATCH 2/8] add requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 5a505fb..4219551 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ aiofiles==23.1.0 +aiogram==3.1.1 aiohttp==3.8.5 aiosignal==1.3.1 aiosqlite==0.19.0 From 5c3ea246f8683d2c2456bbf3d9f02782e10d488a Mon Sep 17 00:00:00 2001 From: progeroffline Date: Sun, 21 Jan 2024 22:59:46 +0200 Subject: [PATCH 3/8] Make new little bit functional --- src/bot/app.py | 25 +++--- src/bot/data/__init__.py | 0 src/bot/data/config.py | 42 ++++++--- src/bot/database/__init__.py | 0 src/bot/database/models.py | 23 ++--- src/bot/database/transactions.py | 87 +++++++++++-------- src/bot/database/users.py | 87 +++++++++++-------- src/bot/filters/__init__.py | 3 +- src/bot/filters/is_admin.py | 14 +++ src/bot/filters/user_not_subscribed.py | 12 ++- src/bot/filters/user_subscribed.py | 12 ++- src/bot/handlers/__init__.py | 4 +- src/bot/handlers/admin.py | 31 +++++++ src/bot/handlers/channels_join_requests.py | 12 ++- src/bot/handlers/check_subscription.py | 34 ++++++++ src/bot/handlers/close_functionality.py | 7 +- src/bot/handlers/payment.py | 69 ++++++++------- src/bot/handlers/start.py | 25 +++--- src/bot/keyboards/__init__.py | 0 src/bot/keyboards/inline.py | 4 +- src/bot/keyboards/reply.py | 49 ++++++++--- src/bot/loader.py | 22 +++-- src/bot/middlewares/__init__.py | 2 - src/bot/middlewares/create_user_middleware.py | 21 ++--- src/bot/middlewares/logger_middleware.py | 5 +- src/bot/statesgroup.py | 2 - src/bot/utils/__init__.py | 0 src/bot/utils/ban_users_from_channels.py | 27 ++++++ src/bot/utils/decrease_subscription_days.py | 27 +++--- src/bot/utils/kick_users_from_channels.py | 25 ------ src/bot/utils/subscription_checker.py | 34 +++++--- src/bot/utils/tronscan_service.py | 48 +++++----- 32 files changed, 482 insertions(+), 271 deletions(-) mode change 100644 => 100755 src/bot/app.py mode change 100644 => 100755 src/bot/data/__init__.py mode change 100644 => 100755 src/bot/data/config.py mode change 100644 => 100755 src/bot/database/__init__.py mode change 100644 => 100755 src/bot/database/models.py mode change 100644 => 100755 src/bot/database/transactions.py mode change 100644 => 100755 src/bot/database/users.py mode change 100644 => 100755 src/bot/filters/__init__.py create mode 100644 src/bot/filters/is_admin.py mode change 100644 => 100755 src/bot/filters/user_not_subscribed.py mode change 100644 => 100755 src/bot/filters/user_subscribed.py mode change 100644 => 100755 src/bot/handlers/__init__.py create mode 100644 src/bot/handlers/admin.py mode change 100644 => 100755 src/bot/handlers/channels_join_requests.py create mode 100644 src/bot/handlers/check_subscription.py mode change 100644 => 100755 src/bot/handlers/close_functionality.py mode change 100644 => 100755 src/bot/handlers/payment.py mode change 100644 => 100755 src/bot/handlers/start.py mode change 100644 => 100755 src/bot/keyboards/__init__.py mode change 100644 => 100755 src/bot/keyboards/inline.py mode change 100644 => 100755 src/bot/keyboards/reply.py mode change 100644 => 100755 src/bot/loader.py mode change 100644 => 100755 src/bot/middlewares/__init__.py mode change 100644 => 100755 src/bot/middlewares/create_user_middleware.py mode change 100644 => 100755 src/bot/middlewares/logger_middleware.py mode change 100644 => 100755 src/bot/statesgroup.py mode change 100644 => 100755 src/bot/utils/__init__.py create mode 100755 src/bot/utils/ban_users_from_channels.py mode change 100644 => 100755 src/bot/utils/decrease_subscription_days.py delete mode 100644 src/bot/utils/kick_users_from_channels.py mode change 100644 => 100755 src/bot/utils/subscription_checker.py mode change 100644 => 100755 src/bot/utils/tronscan_service.py diff --git a/src/bot/app.py b/src/bot/app.py old mode 100644 new mode 100755 index 32a20df..a177382 --- a/src/bot/app.py +++ b/src/bot/app.py @@ -1,31 +1,36 @@ -# -*- coding: utf-8 -*- - import asyncio -from database import \ - create_schema_if_not_exist as database_create_schema_if_not_exist -from handlers import (channels_join_requests_router, - close_functionality_router, payment_router, start_router) +from database import create_schema_if_not_exist as database_create_schema_if_not_exist +from handlers import ( + admin_router, + channels_join_requests_router, + check_subscription_router, + close_functionality_router, + payment_router, + start_router, +) from loader import bot, dp, tasks_scheduler from middlewares import CreateUserMiddleware, UpdateLoggerMiddleware async def on_startup(): - dp.update.middleware(UpdateLoggerMiddleware()) - dp.message.middleware(CreateUserMiddleware()) + dp.update.outer_middleware(UpdateLoggerMiddleware()) + dp.message.outer_middleware(CreateUserMiddleware()) dp.include_routers( + admin_router, start_router, payment_router, close_functionality_router, channels_join_requests_router, + check_subscription_router, ) - + await database_create_schema_if_not_exist() tasks_scheduler.start() await dp.start_polling(bot) - + if __name__ == "__main__": asyncio.run(on_startup()) diff --git a/src/bot/data/__init__.py b/src/bot/data/__init__.py old mode 100644 new mode 100755 diff --git a/src/bot/data/config.py b/src/bot/data/config.py old mode 100644 new mode 100755 index 9f14ac7..cef6b70 --- a/src/bot/data/config.py +++ b/src/bot/data/config.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os from pathlib import Path @@ -10,21 +8,43 @@ BOT_TOKEN = env.str("BOT_TOKEN") -database_filename = 'database.db' -schema_filename = 'database_schema.sql' +database_filename = "database.db" +schema_filename = "database_schema.sql" project_filepath = Path(__file__).resolve().parent.parent.parent -sqlite_database_filepath = os.path.join(project_filepath, 'db', database_filename) -sqlite_schema_filepath = os.path.join(project_filepath, 'db', schema_filename) +sqlite_database_filepath = os.path.join(project_filepath, "db", database_filename) +sqlite_schema_filepath = os.path.join(project_filepath, "db", schema_filename) + +USDT_TRC20_WALLET_ADDRESS = "TF8aSMqpwtniPN77wS2EZTTcUKaaJhyorb" -USDT_TRC20_WALLET_ADDRESS = 'TF8aSMqpwtniPN77wS2EZTTcUKaaJhyorb' +# Price for 1 month SUBSCRIBE_AMOUNT_IN_USDT_TRC20 = 5 NUMBER_DAYS_FROM_ONE_PAYMENT = 30 +SUBSCRIBE_END_NOTIFICATION_DAYS = [7, 3, 1] +ADMINS_ID_LIST = [535327818] private_channels = { - 'Channel 1': { - 'id': -100123456789, - 'invite_url': 'https://t.me/+ABCDEFGHIJKL' - }, + "Channel 1": {"id": -100123456789, "invite_url": "https://t.me/+ABCDEFGHIJKL"}, } + +""" + Use HTML to format text + + bold, bold + italic, italic + underline, underline + strikethrough, strikethrough, strikethrough + spoiler, spoiler + bold italic bold italic bold strikethrough italic bold strikethrough spoiler underline italic bold bold + inline URL + inline mention of a user + 👍 + inline fixed-width code +
pre-formatted fixed-width code block
+
pre-formatted fixed-width code block written in the Python programming language
+
Block quotation started\nBlock quotation continued\nThe last line of the block quotation
+ + And user \n to print the next text on a new line +""" +MAILING_TEXT = "Hello" diff --git a/src/bot/database/__init__.py b/src/bot/database/__init__.py old mode 100644 new mode 100755 diff --git a/src/bot/database/models.py b/src/bot/database/models.py old mode 100644 new mode 100755 index 18c00a4..0e68c47 --- a/src/bot/database/models.py +++ b/src/bot/database/models.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import dataclasses from typing import Optional @@ -11,15 +9,17 @@ class User: first_name: Optional[str] last_name: Optional[str] username: Optional[str] - days_sub_end: int - + days_sub_end: str + @staticmethod def get_fields_for_sql_query(): - return '(%s)' % ', '.join([ field.name for field in dataclasses.fields(User) ][1:]) + return "(%s)" % ", ".join( + [field.name for field in dataclasses.fields(User)][1:] + ) @staticmethod def get_table_name(): - return 'Users' + return "Users" @dataclasses.dataclass() @@ -28,12 +28,15 @@ class Transaction: txid: str owner_telegram_id: int status: bool + months: int created_at_timestamp: int - + @staticmethod def get_fields_for_sql_query(): - return '(%s)' % ', '.join([ field.name for field in dataclasses.fields(Transaction) ][1:]) - + return "(%s)" % ", ".join( + [field.name for field in dataclasses.fields(Transaction)][1:] + ) + @staticmethod def get_table_name(): - return 'Transactions' + return "Transactions" diff --git a/src/bot/database/transactions.py b/src/bot/database/transactions.py old mode 100644 new mode 100755 index 419502f..95a0251 --- a/src/bot/database/transactions.py +++ b/src/bot/database/transactions.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from datetime import datetime, timedelta from typing import List, Optional, Union @@ -9,56 +7,71 @@ from .models import Transaction -async def get(database_id: Optional[int] = None, - txid: Optional[str] = None) -> Union[Transaction, None]: +async def get( + database_id: Optional[int] = None, txid: Optional[str] = None +) -> Union[Transaction, None]: async with aiosqlite.connect(sqlite_database_filepath) as connection: if database_id is not None: - sql_query = "SELECT * FROM %s WHERE id=%s" % (Transaction.get_table_name(), database_id) + sql_query = "SELECT * FROM %s WHERE id=%s" % ( + Transaction.get_table_name(), + database_id, + ) elif txid is not None: - sql_query = "SELECT * FROM %s WHERE txid='%s'" % (Transaction.get_table_name(), txid) + sql_query = "SELECT * FROM %s WHERE txid='%s'" % ( + Transaction.get_table_name(), + txid, + ) else: return None - - cursor = await connection.execute(sql_query) + + cursor = await connection.execute(sql_query) row = await cursor.fetchone() - if row is None: return row - + if row is None: + return row + return Transaction(*row) -async def create(txid: str, user_telegram_id: int) -> None: +async def create(txid: str, user_telegram_id: int, months: int = 1) -> None: async with aiosqlite.connect(sqlite_database_filepath) as connection: - await connection.execute(""" + await connection.execute( + """ INSERT INTO %s %s VALUES - ('%s', %s, %s, %s) + ('%s', %s, %s, %s, %s) ; - """ % ( - Transaction.get_table_name(), - Transaction.get_fields_for_sql_query(), - txid, - user_telegram_id, - False, - int(datetime.now().timestamp()), - )) + """ + % ( + Transaction.get_table_name(), + Transaction.get_fields_for_sql_query(), + txid, + user_telegram_id, + False, + months, + int(datetime.now().timestamp()), + ) + ) await connection.commit() - - -async def set_status(status: bool, - database_id: Optional[int] = None, - txid: Optional[str] = None) -> None: - transaction = await get(database_id, txid) - if transaction is None: return None - + + +async def set_status( + status: bool, database_id: Optional[int] = None, txid: Optional[str] = None +) -> None: + transaction = await get(database_id, txid) + if transaction is None: + return None + async with aiosqlite.connect(sqlite_database_filepath) as connection: sql_query = "UPDATE %s SET status=%s WHERE id=%s" % ( - Transaction.get_table_name(), status, transaction.id, + Transaction.get_table_name(), + status, + transaction.id, ) - + await connection.execute(sql_query) await connection.commit() - + async def get_new() -> List[Transaction]: async with aiosqlite.connect(sqlite_database_filepath) as connection: @@ -69,10 +82,10 @@ async def get_new() -> List[Transaction]: """ % ( Transaction.get_table_name(), False, - int((datetime.now() - timedelta(minutes=20)).timestamp()) + int((datetime.now() - timedelta(minutes=20)).timestamp()), ) - - cursor = await connection.execute(sql_query) + + cursor = await connection.execute(sql_query) rows = await cursor.fetchall() - - return [ Transaction(*row) for row in rows ] + + return [Transaction(*row) for row in rows] diff --git a/src/bot/database/users.py b/src/bot/database/users.py old mode 100644 new mode 100755 index 6b996e4..8dd60d8 --- a/src/bot/database/users.py +++ b/src/bot/database/users.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from typing import List, Optional, Union import aiosqlite @@ -8,64 +6,85 @@ from .models import User -async def get(database_id: Optional[int] = None, - telegram_id: Optional[int] = None) -> Union[User, None]: +async def get( + database_id: Optional[int] = None, telegram_id: Optional[int] = None +) -> Union[User, None]: async with aiosqlite.connect(sqlite_database_filepath) as connection: if database_id is not None: - sql_query = "SELECT * FROM %s WHERE id=%s" % (User.get_table_name(), database_id) + sql_query = "SELECT * FROM %s WHERE id=%s" % ( + User.get_table_name(), + database_id, + ) elif telegram_id is not None: - sql_query = "SELECT * FROM %s WHERE telegram_id=%s" % (User.get_table_name(), telegram_id) + sql_query = "SELECT * FROM %s WHERE telegram_id=%s" % ( + User.get_table_name(), + telegram_id, + ) else: return None - + cursor = await connection.execute(sql_query) row = await cursor.fetchone() - if row is None: return row - + if row is None: + return row + return User(*row) -async def set_days_sub_end(count_days: int, - database_id: Optional[int] = None, - telegram_id: Optional[int] = None) -> None: +async def update_subscription_date( + date: str, + database_id: Optional[int] = None, + telegram_id: Optional[int] = None, +) -> None: user = await get(database_id, telegram_id) - if user is None: return None - + if user is None: + return None + async with aiosqlite.connect(sqlite_database_filepath) as connection: - sql_query = "UPDATE %s SET days_sub_end=%s WHERE id=%s" % ( - User.get_table_name(), count_days, user.id, + sql_query = "UPDATE %s SET days_sub_end='%s' WHERE id=%s" % ( + User.get_table_name(), + date, + user.id, ) - + await connection.execute(sql_query) await connection.commit() - - -async def create_if_not_exist(telegram_id: int, - firstname: str, - lastname: str, - username: str) -> None: + + +async def create_if_not_exist( + telegram_id: int, + firstname: Union[str, None], + lastname: Union[str, None], + username: Union[str, None], +) -> None: record = await get(telegram_id=telegram_id) if record is None: async with aiosqlite.connect(sqlite_database_filepath) as connection: - await connection.execute(""" + await connection.execute( + """ INSERT INTO %s %s VALUES - (%s, '%s', '%s', '%s', %s) + (%s, '%s', '%s', '%s', datetime('now')) ; - """ % ( - User.get_table_name(), - User.get_fields_for_sql_query(), - telegram_id, firstname, lastname, username, 0, - )) + """ + % ( + User.get_table_name(), + User.get_fields_for_sql_query(), + telegram_id, + firstname, + lastname, + username, + ) + ) await connection.commit() async def get_all() -> List[User]: async with aiosqlite.connect(sqlite_database_filepath) as connection: sql_query = "SELECT * FROM %s" % User.get_table_name() - - cursor = await connection.execute(sql_query) + + cursor = await connection.execute(sql_query) rows = await cursor.fetchall() - - return [ User(*row) for row in rows ] + + return [User(*row) for row in rows] diff --git a/src/bot/filters/__init__.py b/src/bot/filters/__init__.py old mode 100644 new mode 100755 index 65ca39e..47d30ab --- a/src/bot/filters/__init__.py +++ b/src/bot/filters/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- - +from .is_admin import IsAdminFilter from .user_not_subscribed import UserNotSubscribedFilter from .user_subscribed import UserSubscribedFilter diff --git a/src/bot/filters/is_admin.py b/src/bot/filters/is_admin.py new file mode 100644 index 0000000..af943f9 --- /dev/null +++ b/src/bot/filters/is_admin.py @@ -0,0 +1,14 @@ +from aiogram import types +from aiogram.filters import BaseFilter +from data.config import ADMINS_ID_LIST + + +class IsAdminFilter(BaseFilter): + + def __init__(self): + pass + + async def __call__(self, message: types.Message) -> bool: + if message.from_user is None: + return False + return message.from_user.id in ADMINS_ID_LIST diff --git a/src/bot/filters/user_not_subscribed.py b/src/bot/filters/user_not_subscribed.py old mode 100644 new mode 100755 index 63c863e..f8114b4 --- a/src/bot/filters/user_not_subscribed.py +++ b/src/bot/filters/user_not_subscribed.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +from datetime import datetime from aiogram import types from aiogram.filters import BaseFilter @@ -6,10 +6,16 @@ class UserNotSubscribedFilter(BaseFilter): + def __init__(self): pass async def __call__(self, message: types.Message) -> bool: + if message.from_user is None: + return False user = await users.get(telegram_id=message.from_user.id) - if user is None: return False - return user.days_sub_end <= 0 + if user is None: + return False + return datetime.now() > datetime.strptime( + user.days_sub_end, "%Y-%m-%d %H:%M:%S" + ) diff --git a/src/bot/filters/user_subscribed.py b/src/bot/filters/user_subscribed.py old mode 100644 new mode 100755 index 842ed07..4f4ff1a --- a/src/bot/filters/user_subscribed.py +++ b/src/bot/filters/user_subscribed.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +from datetime import datetime from aiogram import types from aiogram.filters import BaseFilter @@ -6,10 +6,16 @@ class UserSubscribedFilter(BaseFilter): + def __init__(self): pass async def __call__(self, message: types.Message) -> bool: + if message.from_user is None: + return False user = await users.get(telegram_id=message.from_user.id) - if user is None: return False - return user.days_sub_end >= 1 + if user is None: + return False + return datetime.now() <= datetime.strptime( + user.days_sub_end, "%Y-%m-%d %H:%M:%S" + ) diff --git a/src/bot/handlers/__init__.py b/src/bot/handlers/__init__.py old mode 100644 new mode 100755 index e0e66e2..1d92275 --- a/src/bot/handlers/__init__.py +++ b/src/bot/handlers/__init__.py @@ -1,6 +1,6 @@ -# -*- coding: utf-8 -*- - +from .admin import admin_router from .channels_join_requests import channels_join_requests_router +from .check_subscription import check_subscription_router from .close_functionality import close_functionality_router from .payment import payment_router from .start import start_router diff --git a/src/bot/handlers/admin.py b/src/bot/handlers/admin.py new file mode 100644 index 0000000..99aac14 --- /dev/null +++ b/src/bot/handlers/admin.py @@ -0,0 +1,31 @@ +from datetime import datetime + +from aiogram import Router, types +from aiogram.filters import Command +from data.config import MAILING_TEXT +from database import users +from filters import IsAdminFilter +from loader import bot + +admin_router = Router() + + +@admin_router.message(IsAdminFilter(), Command("start_mailing")) +async def start_mailing_to_not_subscribed_users(message: types.Message): + """Start mailing to not subscribet users""" + + if message.from_user is None: + return + users_records = await users.get_all() + for user in users_records: + if user.telegram_id == message.from_user.id: + continue + if datetime.now() > datetime.strptime(user.days_sub_end, "%Y-%m-%d %H:%M:%S"): + await bot.send_message( + chat_id=user.telegram_id, + text=MAILING_TEXT, + ) + await message.answer( + text="The message was successfully sent to %s" + % (user.telegram_id, user.first_name) + ) diff --git a/src/bot/handlers/channels_join_requests.py b/src/bot/handlers/channels_join_requests.py old mode 100644 new mode 100755 index 62636ca..421f1c8 --- a/src/bot/handlers/channels_join_requests.py +++ b/src/bot/handlers/channels_join_requests.py @@ -1,11 +1,17 @@ +from datetime import datetime + from aiogram import Router, types from database import users channels_join_requests_router = Router() + @channels_join_requests_router.chat_join_request() async def private_channel_join_request(chat_join_request: types.ChatJoinRequest): - user = await users.get(telegram_id=chat_join_request.from_user.id) - if user is None: return - if user.days_sub_end >= 1: + user = await users.get(telegram_id=chat_join_request.from_user.id) + if user is None: + return + if datetime.now() < datetime.strptime(user.days_sub_end, "%Y-%m-%d %H:%M:%S"): await chat_join_request.approve() + else: + await chat_join_request.decline() diff --git a/src/bot/handlers/check_subscription.py b/src/bot/handlers/check_subscription.py new file mode 100644 index 0000000..f5ef0e0 --- /dev/null +++ b/src/bot/handlers/check_subscription.py @@ -0,0 +1,34 @@ +from datetime import datetime + +from aiogram import F, Router, types +from database import users + +check_subscription_router = Router() + + +@check_subscription_router.message(F.text == "Check subscription") +async def check_subscription(message: types.Message): + if message.from_user is None: + return + user_database_record = await users.get(telegram_id=message.from_user.id) + if user_database_record is None: + return + + datetime_now = datetime.now() + sub_date_end = datetime.strptime( + user_database_record.days_sub_end, "%Y-%m-%d %H:%M:%S" + ) + + # User have subscription + if datetime_now < sub_date_end: + await message.answer( + text="Your subscription is active until %s" + % user_database_record.days_sub_end + ) + + # User don't have subscription + elif datetime_now >= sub_date_end: + await message.answer( + text="Your subscription has expired %s" + % user_database_record.days_sub_end + ) diff --git a/src/bot/handlers/close_functionality.py b/src/bot/handlers/close_functionality.py old mode 100644 new mode 100755 index 730cb4a..24f9548 --- a/src/bot/handlers/close_functionality.py +++ b/src/bot/handlers/close_functionality.py @@ -4,12 +4,13 @@ close_functionality_router = Router() + @close_functionality_router.message( - UserSubscribedFilter(), - F.text=='Show close functionality', + UserSubscribedFilter(), + F.text == "Show close functionality", ) async def show_private_channels(message: types.Message): await message.answer( - text='You can subscribe to closed channels.', + text="You can subscribe to closed channels.", reply_markup=await inline_keyboard.channels(), ) diff --git a/src/bot/handlers/payment.py b/src/bot/handlers/payment.py old mode 100644 new mode 100755 index 90c7479..5c61716 --- a/src/bot/handlers/payment.py +++ b/src/bot/handlers/payment.py @@ -1,58 +1,67 @@ -# -*- coding: utf-8 -*- - from aiogram import F, Router, types from aiogram.fsm.context import FSMContext -from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20, - USDT_TRC20_WALLET_ADDRESS) +from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20, USDT_TRC20_WALLET_ADDRESS) from database import transactions -from filters.user_not_subscribed import UserNotSubscribedFilter from keyboards import reply as reply_keyboards from statesgroup import GetTxidFromUser from utils import tronscan_service payment_router = Router() -@payment_router.message( - UserNotSubscribedFilter(), - F.text=='Make subscription', -) + +@payment_router.message(F.text == "Make subscription") +@payment_router.message(F.text == "Renew subscription") async def make_subscription(message: types.Message): await message.answer( - text=f'To pay, use this USDT TC20 wallet: {USDT_TRC20_WALLET_ADDRESS}.\n' - f'Transfer {SUBSCRIBE_AMOUNT_IN_USDT_TRC20} USDT TRC20.\n' - 'After submitting, click the Confirm button.', + text=f"Choose subscription plan", + reply_markup=await reply_keyboards.subscription_termins(), + ) + + +@payment_router.message(F.text.contains("month")) +async def set_subscribtion_termin(message: types.Message, state: FSMContext): + if message.text is None: + return + termin = int(message.text.split(" ")[0]) + await state.set_data({"subscription_termin": termin}) + await message.answer( + text=f"To pay, use this USDT TC20 wallet: {USDT_TRC20_WALLET_ADDRESS}.\n" + f"Transfer {SUBSCRIBE_AMOUNT_IN_USDT_TRC20*termin} USDT TRC20.\n" + "After submitting, click the Confirm button.", reply_markup=await reply_keyboards.confirm_transfer(), ) - - -@payment_router.message( - UserNotSubscribedFilter(), - F.text=='Confirm transfer', -) + + +@payment_router.message(F.text == "Confirm transfer") async def confirm_transfer(message: types.Message, state: FSMContext): await state.set_state(GetTxidFromUser.state) await message.answer( - text='Great, send me the transaction txid to verify the transfer.', + text="Great, send me the transaction txid to verify the transfer.", reply_markup=types.ReplyKeyboardRemove(), ) - -@payment_router.message( - UserNotSubscribedFilter(), - GetTxidFromUser.state, -) + +@payment_router.message(GetTxidFromUser.state) async def check_transaction(message: types.Message, state: FSMContext): transaction = await transactions.get(txid=message.text) - - if message.text is None: return + + if message.text is None or message.from_user is None: + return + if transaction is None and tronscan_service.is_valid_transaction_hash(message.text): + data = await state.get_data() + await transactions.create( + message.text, + message.from_user.id, + data["subscription_termin"], + ) await state.clear() - await transactions.create(message.text, message.from_user.id) await message.answer( - text='Great, wait for the end of the transaction, ' - 'and I will notify you when the subscription is charged.', + text="Great, wait for the end of the transaction, " + "and I will notify you when the subscription is charged.", + reply_markup=await reply_keyboards.back_to_main_menu(), ) else: await message.answer( - text='Please send me a new transaction, or check the txid', + text="Please send me a new transaction, or check the txid", ) diff --git a/src/bot/handlers/start.py b/src/bot/handlers/start.py old mode 100644 new mode 100755 index 6d90efa..5e543d2 --- a/src/bot/handlers/start.py +++ b/src/bot/handlers/start.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- - -from aiogram import Router, types +from aiogram import F, Router, types from aiogram.filters import CommandStart from database import users from filters.user_not_subscribed import UserNotSubscribedFilter @@ -9,21 +7,28 @@ start_router = Router() + @start_router.message(UserSubscribedFilter(), CommandStart()) +@start_router.message(UserSubscribedFilter(), F.text == "Back to main menu") async def start_for_subsribed_user(message: types.Message): + if message.from_user is None: + return user = await users.get(telegram_id=message.from_user.id) - if user is None:return - + if user is None: + return + await message.answer( - text='Hello.\n' - f'There are {user.days_sub_end} days left until the end of the subscription. \n' - 'Do not miss the day of payment to always have access to closed functionality.', + text="Hello.\n" + f"Your subscription is active until {user.days_sub_end}.\n" + "Do not miss the day of payment to always have access to closed functionality.", reply_markup=await reply_keyboards.close_functionality(), ) - + + @start_router.message(UserNotSubscribedFilter(), CommandStart()) +@start_router.message(UserNotSubscribedFilter(), F.text == "Back to main menu") async def start_for_not_subsribed_user(message: types.Message): await message.answer( - text='Hello. Subscribe to the bot to get access to the closed functionality.', + text="Hello. Subscribe to the bot to get access to the closed functionality.", reply_markup=await reply_keyboards.make_subscribtion(), ) diff --git a/src/bot/keyboards/__init__.py b/src/bot/keyboards/__init__.py old mode 100644 new mode 100755 diff --git a/src/bot/keyboards/inline.py b/src/bot/keyboards/inline.py old mode 100644 new mode 100755 index bf0ce17..2aa729a --- a/src/bot/keyboards/inline.py +++ b/src/bot/keyboards/inline.py @@ -5,12 +5,12 @@ async def channels() -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() - + for name in private_channels.keys(): builder.add( types.InlineKeyboardButton( text=name, - url=private_channels[name]['invite_url'], + url=private_channels[name]["invite_url"], ) ) diff --git a/src/bot/keyboards/reply.py b/src/bot/keyboards/reply.py old mode 100644 new mode 100755 index 80d5180..5095e70 --- a/src/bot/keyboards/reply.py +++ b/src/bot/keyboards/reply.py @@ -1,31 +1,60 @@ -# -*- coding: utf-8 -*- - from aiogram import types -async def close_functionality(): +async def close_functionality() -> types.ReplyKeyboardMarkup: + return types.ReplyKeyboardMarkup( + keyboard=[ + [types.KeyboardButton(text="Renew subscription")], + [types.KeyboardButton(text="Show close functionality")], + [types.KeyboardButton(text="Check subscription")], + ], + resize_keyboard=True, + ) + + +async def make_subscribtion() -> types.ReplyKeyboardMarkup: + return types.ReplyKeyboardMarkup( + keyboard=[ + [types.KeyboardButton(text="Make subscription")], + [types.KeyboardButton(text="Check subscription")], + ], + resize_keyboard=True, + ) + + +async def confirm_transfer() -> types.ReplyKeyboardMarkup: return types.ReplyKeyboardMarkup( - keyboard=[[types.KeyboardButton(text='Show close functionality')]], + keyboard=[ + [types.KeyboardButton(text="Confirm transfer")], + [types.KeyboardButton(text="Back to main menu")], + ], resize_keyboard=True, ) -async def make_subscribtion(): +async def check_transaction() -> types.ReplyKeyboardMarkup: return types.ReplyKeyboardMarkup( - keyboard=[[types.KeyboardButton(text='Make subscription')]], + keyboard=[[types.KeyboardButton(text="Check transaction")]], resize_keyboard=True, ) -async def confirm_transfer(): +async def back_to_main_menu() -> types.ReplyKeyboardMarkup: return types.ReplyKeyboardMarkup( - keyboard=[[types.KeyboardButton(text='Confirm transfer')]], + keyboard=[[types.KeyboardButton(text="Back to main menu")]], resize_keyboard=True, ) -async def check_transaction(): +async def subscription_termins() -> types.ReplyKeyboardMarkup: return types.ReplyKeyboardMarkup( - keyboard=[[types.KeyboardButton( text='Check transaction')]], + keyboard=[ + [ + types.KeyboardButton(text="1 month"), + types.KeyboardButton(text="3 months"), + types.KeyboardButton(text="6 months"), + ], + [types.KeyboardButton(text="Back to main menu")], + ], resize_keyboard=True, ) diff --git a/src/bot/loader.py b/src/bot/loader.py old mode 100644 new mode 100755 index 33b0578..7b0fff1 --- a/src/bot/loader.py +++ b/src/bot/loader.py @@ -6,19 +6,23 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from data.config import BOT_TOKEN from logzero import logfile, logger -from middlewares import CreateUserMiddleware, UpdateLoggerMiddleware -from utils import (decrease_subscription_days, kick_users_from_channels, - subscription_checker) +from utils import ( + ban_users_from_channels, + decrease_subscription_days, + subscription_checker, +) -if not os.path.exists('logs/'): - os.system('mkdir logs') -logfile('logs/bot.log') +if not os.path.exists("logs/"): + os.system("mkdir logs") +logfile("logs/bot.log") bot = Bot(token=BOT_TOKEN, parse_mode=ParseMode.HTML) storage = MemoryStorage() dp = Dispatcher(bot=bot, storage=storage) tasks_scheduler = AsyncIOScheduler() -tasks_scheduler.add_job(subscription_checker.task, 'interval', minutes=1, args=(bot, )) -tasks_scheduler.add_job(decrease_subscription_days.task, 'interval', days=1, args=(bot, )) -tasks_scheduler.add_job(kick_users_from_channels.task, 'interval', days=1, args=(bot, )) +tasks_scheduler.add_job(subscription_checker.task, "interval", minutes=1, args=(bot,)) +tasks_scheduler.add_job( + decrease_subscription_days.task, "interval", days=1, args=(bot,) +) +tasks_scheduler.add_job(ban_users_from_channels.task, "interval", days=1, args=(bot,)) diff --git a/src/bot/middlewares/__init__.py b/src/bot/middlewares/__init__.py old mode 100644 new mode 100755 index d9694c5..21f5344 --- a/src/bot/middlewares/__init__.py +++ b/src/bot/middlewares/__init__.py @@ -1,4 +1,2 @@ -# -*- coding: utf-8 -*- - from .create_user_middleware import CreateUserMiddleware from .logger_middleware import UpdateLoggerMiddleware diff --git a/src/bot/middlewares/create_user_middleware.py b/src/bot/middlewares/create_user_middleware.py old mode 100644 new mode 100755 index 076a9d1..d78898f --- a/src/bot/middlewares/create_user_middleware.py +++ b/src/bot/middlewares/create_user_middleware.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from collections.abc import Awaitable, Callable from typing import Any, Dict @@ -10,19 +8,18 @@ class CreateUserMiddleware(BaseMiddleware): + async def __call__( self, handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], message: Message, - data: Dict[str, Any] + data: Dict[str, Any], ) -> Any: - await self._create_user_if_not_exist( - message.from_user.id, - message.from_user.first_name, - message.from_user.last_name, - message.from_user.username, - ) + if message.from_user is not None: + await users.create_if_not_exist( + message.from_user.id, + message.from_user.first_name, + message.from_user.last_name, + message.from_user.username, + ) await handler(message, data) - - async def _create_user_if_not_exist(self, telegram_id: int, first_name: str, last_name: str, username: str): - await users.create_if_not_exist(telegram_id, first_name, last_name, username) diff --git a/src/bot/middlewares/logger_middleware.py b/src/bot/middlewares/logger_middleware.py old mode 100644 new mode 100755 index 326be61..0cca5f2 --- a/src/bot/middlewares/logger_middleware.py +++ b/src/bot/middlewares/logger_middleware.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from collections.abc import Awaitable, Callable from typing import Any, Dict @@ -9,11 +7,12 @@ class UpdateLoggerMiddleware(BaseMiddleware): + async def __call__( self, handler: Callable[[types.TelegramObject, Dict[str, Any]], Awaitable[Any]], event: TelegramObject, - data: Dict[str, Any] + data: Dict[str, Any], ) -> Any: logger.info(event) await handler(event, data) diff --git a/src/bot/statesgroup.py b/src/bot/statesgroup.py old mode 100644 new mode 100755 index a328606..07232f2 --- a/src/bot/statesgroup.py +++ b/src/bot/statesgroup.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from aiogram.fsm.state import State, StatesGroup diff --git a/src/bot/utils/__init__.py b/src/bot/utils/__init__.py old mode 100644 new mode 100755 diff --git a/src/bot/utils/ban_users_from_channels.py b/src/bot/utils/ban_users_from_channels.py new file mode 100755 index 0000000..a83b12c --- /dev/null +++ b/src/bot/utils/ban_users_from_channels.py @@ -0,0 +1,27 @@ +import typing +from datetime import datetime + +from data.config import private_channels +from database import users + +if typing.TYPE_CHECKING: + import aiogram + + +async def task(bot: "aiogram.Bot"): + users_records = await users.get_all() + channels = [ + await bot.get_chat(private_channels[name]["id"]) + for name in private_channels.keys() + ] + for user in users_records: + if datetime.now() < datetime.strptime(user.days_sub_end, "%Y-%m-%d %H:%M:%S"): + continue + + for channel in channels: + member = await bot.get_chat_member(channel.id, user.telegram_id) + if member.status in ["left", "creator"]: + continue + + await bot.ban_chat_member(channel.id, user.telegram_id) + await bot.unban_chat_member(channel.id, user.telegram_id) diff --git a/src/bot/utils/decrease_subscription_days.py b/src/bot/utils/decrease_subscription_days.py old mode 100644 new mode 100755 index 8bb4240..d9e851b --- a/src/bot/utils/decrease_subscription_days.py +++ b/src/bot/utils/decrease_subscription_days.py @@ -1,23 +1,30 @@ import typing +from datetime import datetime +from data.config import SUBSCRIBE_END_NOTIFICATION_DAYS from database import users if typing.TYPE_CHECKING: import aiogram - -async def task(bot: 'aiogram.Bot'): + +async def task(bot: "aiogram.Bot"): users_records = await users.get_all() for user in users_records: - if user.days_sub_end >= 1: - await users.set_days_sub_end( - count_days=user.days_sub_end-1, - database_id=user.id, - ) + datetime_now = datetime.now() + sub_end_date = datetime.strptime(user.days_sub_end, "%Y-%m-%d %H:%M:%S") - if user.days_sub_end == 1: + days_left = (sub_end_date - datetime_now).days + 1 + if days_left in SUBSCRIBE_END_NOTIFICATION_DAYS: + if days_left == 1: + await bot.send_message( + chat_id=user.telegram_id, + text="Your subscription will end soon!\n" + f"{days_left} day left until the end.", + ) + else: await bot.send_message( chat_id=user.telegram_id, - text='Your subscription will end soon!\n' - f'{user.days_sub_end} day left until the end.' + text="Your subscription will end soon!\n" + f"{days_left} days left until the end.", ) diff --git a/src/bot/utils/kick_users_from_channels.py b/src/bot/utils/kick_users_from_channels.py deleted file mode 100644 index b4a3723..0000000 --- a/src/bot/utils/kick_users_from_channels.py +++ /dev/null @@ -1,25 +0,0 @@ -import typing - -from data.config import private_channels -from database import users - -if typing.TYPE_CHECKING: - import aiogram - - -async def task(bot: 'aiogram.Bot'): - users_records = await users.get_all() - channels = [ - await bot.get_chat(private_channels[name]['id']) - for name in private_channels.keys() - ] - for user in users_records: - if user.days_sub_end >= 1: continue - - for channel in channels: - member = await bot.get_chat_member(channel.id, user.telegram_id) - if member.status in ['left', 'creator']: continue - - await bot.kick_chat_member(channel.id, user.telegram_id) - - diff --git a/src/bot/utils/subscription_checker.py b/src/bot/utils/subscription_checker.py old mode 100644 new mode 100755 index 19c795c..426f3e5 --- a/src/bot/utils/subscription_checker.py +++ b/src/bot/utils/subscription_checker.py @@ -1,24 +1,30 @@ import typing +from datetime import datetime, timedelta from data.config import NUMBER_DAYS_FROM_ONE_PAYMENT from database import transactions, users +from keyboards import reply from utils import tronscan_service if typing.TYPE_CHECKING: - import aiogram + import aiogram - -async def task(bot: 'aiogram.Bot'): - records = await transactions.get_new() - for record in records: - if await tronscan_service.check_transaction_for_correct_data(record.txid): - await transactions.set_status(True, database_id=record.id) - await users.set_days_sub_end( - count_days=NUMBER_DAYS_FROM_ONE_PAYMENT, - telegram_id=record.owner_telegram_id, - ) + +async def task(bot: "aiogram.Bot"): + transactions_records = await transactions.get_new() + for transaction in transactions_records: + if await tronscan_service.check_transaction_for_correct_data(transaction.txid): + await transactions.set_status(True, database_id=transaction.id) + + await users.update_subscription_date( + date=( + datetime.now() + + timedelta(days=NUMBER_DAYS_FROM_ONE_PAYMENT * transaction.months) + ).strftime("%Y-%m-%d %H:%M:%S"), + telegram_id=transaction.owner_telegram_id, + ) await bot.send_message( - chat_id=record.owner_telegram_id, - text='Congratulations, you now have access to limited functionality.' + chat_id=transaction.owner_telegram_id, + text="Congratulations, you now have access to limited functionality.", + reply_markup=await reply.close_functionality(), ) - diff --git a/src/bot/utils/tronscan_service.py b/src/bot/utils/tronscan_service.py old mode 100644 new mode 100755 index 9162769..b1b8eb2 --- a/src/bot/utils/tronscan_service.py +++ b/src/bot/utils/tronscan_service.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- - import re from typing import Dict from aiohttp import ClientSession -from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20, - USDT_TRC20_WALLET_ADDRESS) +from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20, USDT_TRC20_WALLET_ADDRESS) def is_valid_transaction_hash(txid: str) -> bool: @@ -14,9 +11,9 @@ def is_valid_transaction_hash(txid: str) -> bool: async def get_transaction_info(txid: str) -> Dict: - api_endpoint = 'https://apilist.tronscanapi.com/api/transaction-info' - params = {'hash': txid} - + api_endpoint = "https://apilist.tronscanapi.com/api/transaction-info" + params = {"hash": txid} + async with ClientSession() as session: async with session.get(url=api_endpoint, params=params) as response: response = await response.json() @@ -24,22 +21,25 @@ async def get_transaction_info(txid: str) -> Dict: async def check_transaction_for_correct_data(txid: str) -> bool: - transaction = await get_transaction_info(txid) - transaction_status = transaction.get('contractRet') - if transaction_status is None: return False - - transaction_trc20_data = transaction.get('trc20TransferInfo') - if transaction_trc20_data is None: return False + transaction = await get_transaction_info(txid) + transaction_status = transaction.get("contractRet") + if transaction_status is None: + return False + + transaction_trc20_data = transaction.get("trc20TransferInfo") + if transaction_trc20_data is None: + return False transaction_trc20_data = transaction_trc20_data[0] - - decimals_amount = int('1' + '0' * transaction_trc20_data['decimals']) - transaction_amount = int(transaction_trc20_data['amount_str']) / decimals_amount - transaction_to_wallet = transaction_trc20_data['to_address'] - - if transaction_to_wallet == USDT_TRC20_WALLET_ADDRESS \ - and transaction_amount >= SUBSCRIBE_AMOUNT_IN_USDT_TRC20 \ - and transaction_status == 'SUCCESS': - return True - else: + + decimals_amount = int("1" + "0" * transaction_trc20_data["decimals"]) + transaction_amount = int(transaction_trc20_data["amount_str"]) / decimals_amount + transaction_to_wallet = transaction_trc20_data["to_address"] + + if ( + transaction_to_wallet == USDT_TRC20_WALLET_ADDRESS + and transaction_amount >= SUBSCRIBE_AMOUNT_IN_USDT_TRC20 + and transaction_status == "SUCCESS" + ): + return True + else: return False - From a814831db964a936593c032f042cba0cba84450f Mon Sep 17 00:00:00 2001 From: progeroffline Date: Tue, 23 Jan 2024 13:07:14 +0200 Subject: [PATCH 4/8] Make referral links for all users. Make customising subs plans --- src/bot/app.py | 15 ++++----- src/bot/data/config.py | 12 ++++++-- src/bot/database/models.py | 2 ++ src/bot/database/users.py | 44 ++++++++++++++++++++++++++- src/bot/handlers/__init__.py | 1 + src/bot/handlers/payment.py | 8 +++-- src/bot/handlers/referral.py | 18 +++++++++++ src/bot/handlers/start.py | 20 ++++++++++-- src/bot/keyboards/reply.py | 12 ++++---- src/bot/utils/subscription_checker.py | 23 ++++++++++++-- src/bot/utils/tronscan_service.py | 8 +++-- 11 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 src/bot/handlers/referral.py diff --git a/src/bot/app.py b/src/bot/app.py index a177382..956a2a8 100755 --- a/src/bot/app.py +++ b/src/bot/app.py @@ -1,14 +1,10 @@ import asyncio -from database import create_schema_if_not_exist as database_create_schema_if_not_exist -from handlers import ( - admin_router, - channels_join_requests_router, - check_subscription_router, - close_functionality_router, - payment_router, - start_router, -) +from database import \ + create_schema_if_not_exist as database_create_schema_if_not_exist +from handlers import (admin_router, channels_join_requests_router, + check_subscription_router, close_functionality_router, + payment_router, referral_router, start_router) from loader import bot, dp, tasks_scheduler from middlewares import CreateUserMiddleware, UpdateLoggerMiddleware @@ -24,6 +20,7 @@ async def on_startup(): close_functionality_router, channels_join_requests_router, check_subscription_router, + referral_router, ) await database_create_schema_if_not_exist() diff --git a/src/bot/data/config.py b/src/bot/data/config.py index cef6b70..155a113 100755 --- a/src/bot/data/config.py +++ b/src/bot/data/config.py @@ -18,10 +18,18 @@ USDT_TRC20_WALLET_ADDRESS = "TF8aSMqpwtniPN77wS2EZTTcUKaaJhyorb" -# Price for 1 month -SUBSCRIBE_AMOUNT_IN_USDT_TRC20 = 5 +# Key - count months +# Value - subscribe amount +# You can customise this dict +SUBSCRIBE_AMOUNT_BY_PLANS = { + 1: 5, + # 3: 15, + # 6: 30, + # 7: 31, +} NUMBER_DAYS_FROM_ONE_PAYMENT = 30 SUBSCRIBE_END_NOTIFICATION_DAYS = [7, 3, 1] +REFERAL_REWARD = 5 ADMINS_ID_LIST = [535327818] private_channels = { diff --git a/src/bot/database/models.py b/src/bot/database/models.py index 0e68c47..78d6a7c 100755 --- a/src/bot/database/models.py +++ b/src/bot/database/models.py @@ -10,6 +10,8 @@ class User: last_name: Optional[str] username: Optional[str] days_sub_end: str + balance: int + referrer_id: int @staticmethod def get_fields_for_sql_query(): diff --git a/src/bot/database/users.py b/src/bot/database/users.py index 8dd60d8..7b6fc57 100755 --- a/src/bot/database/users.py +++ b/src/bot/database/users.py @@ -65,7 +65,7 @@ async def create_if_not_exist( INSERT INTO %s %s VALUES - (%s, '%s', '%s', '%s', datetime('now')) + (%s, '%s', '%s', '%s', datetime('now'), %s, %s) ; """ % ( @@ -75,6 +75,8 @@ async def create_if_not_exist( firstname, lastname, username, + 0, + 0, ) ) await connection.commit() @@ -88,3 +90,43 @@ async def get_all() -> List[User]: rows = await cursor.fetchall() return [User(*row) for row in rows] + + +async def update_referrer_id( + referrer_id: int, + to_database_id: Optional[int] = None, + to_telegram_id: Optional[int] = None, +) -> None: + user = await get(to_database_id, to_telegram_id) + if user is None: + return + + async with aiosqlite.connect(sqlite_database_filepath) as connection: + sql_query = "UPDATE %s SET referrer_id=%s WHERE id=%s" % ( + User.get_table_name(), + referrer_id, + user.id, + ) + + await connection.execute(sql_query) + await connection.commit() + + +async def increase_balance_by( + points: int, + database_id: Optional[int] = None, + telegram_id: Optional[int] = None, +) -> None: + user = await get(database_id, telegram_id) + if user is None: + return + + async with aiosqlite.connect(sqlite_database_filepath) as connection: + sql_query = "UPDATE %s SET balance=%s WHERE id=%s" % ( + User.get_table_name(), + user.balance + points, + user.id, + ) + + await connection.execute(sql_query) + await connection.commit() diff --git a/src/bot/handlers/__init__.py b/src/bot/handlers/__init__.py index 1d92275..9eaf850 100755 --- a/src/bot/handlers/__init__.py +++ b/src/bot/handlers/__init__.py @@ -3,4 +3,5 @@ from .check_subscription import check_subscription_router from .close_functionality import close_functionality_router from .payment import payment_router +from .referral import referral_router from .start import start_router diff --git a/src/bot/handlers/payment.py b/src/bot/handlers/payment.py index 5c61716..6301aa5 100755 --- a/src/bot/handlers/payment.py +++ b/src/bot/handlers/payment.py @@ -1,6 +1,6 @@ from aiogram import F, Router, types from aiogram.fsm.context import FSMContext -from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20, USDT_TRC20_WALLET_ADDRESS) +from data.config import SUBSCRIBE_AMOUNT_BY_PLANS, USDT_TRC20_WALLET_ADDRESS from database import transactions from keyboards import reply as reply_keyboards from statesgroup import GetTxidFromUser @@ -14,7 +14,9 @@ async def make_subscription(message: types.Message): await message.answer( text=f"Choose subscription plan", - reply_markup=await reply_keyboards.subscription_termins(), + reply_markup=await reply_keyboards.subscription_termins( + SUBSCRIBE_AMOUNT_BY_PLANS.keys() + ), ) @@ -26,7 +28,7 @@ async def set_subscribtion_termin(message: types.Message, state: FSMContext): await state.set_data({"subscription_termin": termin}) await message.answer( text=f"To pay, use this USDT TC20 wallet: {USDT_TRC20_WALLET_ADDRESS}.\n" - f"Transfer {SUBSCRIBE_AMOUNT_IN_USDT_TRC20*termin} USDT TRC20.\n" + f"Transfer {SUBSCRIBE_AMOUNT_BY_PLANS[termin]} USDT TRC20.\n" "After submitting, click the Confirm button.", reply_markup=await reply_keyboards.confirm_transfer(), ) diff --git a/src/bot/handlers/referral.py b/src/bot/handlers/referral.py new file mode 100644 index 0000000..80487b1 --- /dev/null +++ b/src/bot/handlers/referral.py @@ -0,0 +1,18 @@ +from aiogram import F, Router, types +from database import users +from loader import bot + +referral_router = Router() + + +@referral_router.message(F.text == "Referral link") +async def referral_link(message: types.Message): + if message.from_user is None: + return + user = await users.get(telegram_id=message.from_user.id) + if user is None: + return + bot_data = await bot.get_me() + await message.answer( + text=f"Your referal link https://t.me/{bot_data.username}?start={user.id}", + ) diff --git a/src/bot/handlers/start.py b/src/bot/handlers/start.py index 5e543d2..f9e4ede 100755 --- a/src/bot/handlers/start.py +++ b/src/bot/handlers/start.py @@ -1,5 +1,7 @@ +from typing import Optional + from aiogram import F, Router, types -from aiogram.filters import CommandStart +from aiogram.filters import CommandObject, CommandStart from database import users from filters.user_not_subscribed import UserNotSubscribedFilter from filters.user_subscribed import UserSubscribedFilter @@ -27,7 +29,21 @@ async def start_for_subsribed_user(message: types.Message): @start_router.message(UserNotSubscribedFilter(), CommandStart()) @start_router.message(UserNotSubscribedFilter(), F.text == "Back to main menu") -async def start_for_not_subsribed_user(message: types.Message): +async def start_for_not_subsribed_user( + message: types.Message, command: Optional[CommandObject] = None +): + if ( + command is not None + and command.args is not None + and command.args.isdigit() + and message.from_user is not None + ): + user = await users.get(telegram_id=message.from_user.id) + if user is not None and user.referrer_id == 0 and command.args.isdigit(): + if user.id != int(command.args): + await users.update_referrer_id( + referrer_id=int(command.args), to_database_id=user.id + ) await message.answer( text="Hello. Subscribe to the bot to get access to the closed functionality.", reply_markup=await reply_keyboards.make_subscribtion(), diff --git a/src/bot/keyboards/reply.py b/src/bot/keyboards/reply.py index 5095e70..bb50637 100755 --- a/src/bot/keyboards/reply.py +++ b/src/bot/keyboards/reply.py @@ -1,3 +1,5 @@ +from collections.abc import Iterable + from aiogram import types @@ -7,6 +9,7 @@ async def close_functionality() -> types.ReplyKeyboardMarkup: [types.KeyboardButton(text="Renew subscription")], [types.KeyboardButton(text="Show close functionality")], [types.KeyboardButton(text="Check subscription")], + [types.KeyboardButton(text="Referral link")], ], resize_keyboard=True, ) @@ -17,6 +20,7 @@ async def make_subscribtion() -> types.ReplyKeyboardMarkup: keyboard=[ [types.KeyboardButton(text="Make subscription")], [types.KeyboardButton(text="Check subscription")], + [types.KeyboardButton(text="Referral link")], ], resize_keyboard=True, ) @@ -46,14 +50,10 @@ async def back_to_main_menu() -> types.ReplyKeyboardMarkup: ) -async def subscription_termins() -> types.ReplyKeyboardMarkup: +async def subscription_termins(plans: Iterable[int]) -> types.ReplyKeyboardMarkup: return types.ReplyKeyboardMarkup( keyboard=[ - [ - types.KeyboardButton(text="1 month"), - types.KeyboardButton(text="3 months"), - types.KeyboardButton(text="6 months"), - ], + [types.KeyboardButton(text=f"{plan} month") for plan in plans], [types.KeyboardButton(text="Back to main menu")], ], resize_keyboard=True, diff --git a/src/bot/utils/subscription_checker.py b/src/bot/utils/subscription_checker.py index 426f3e5..b569ba7 100755 --- a/src/bot/utils/subscription_checker.py +++ b/src/bot/utils/subscription_checker.py @@ -1,7 +1,8 @@ import typing from datetime import datetime, timedelta -from data.config import NUMBER_DAYS_FROM_ONE_PAYMENT +from data.config import (NUMBER_DAYS_FROM_ONE_PAYMENT, REFERAL_REWARD, + SUBSCRIBE_AMOUNT_BY_PLANS) from database import transactions, users from keyboards import reply from utils import tronscan_service @@ -13,7 +14,9 @@ async def task(bot: "aiogram.Bot"): transactions_records = await transactions.get_new() for transaction in transactions_records: - if await tronscan_service.check_transaction_for_correct_data(transaction.txid): + if await tronscan_service.check_transaction_for_correct_data( + transaction.txid, SUBSCRIBE_AMOUNT_BY_PLANS[transaction.months] + ): await transactions.set_status(True, database_id=transaction.id) await users.update_subscription_date( @@ -28,3 +31,19 @@ async def task(bot: "aiogram.Bot"): text="Congratulations, you now have access to limited functionality.", reply_markup=await reply.close_functionality(), ) + + user = await users.get(telegram_id=transaction.owner_telegram_id) + if user is None: + continue + if user.referrer_id == 0: + continue + + referer = await users.get(database_id=user.referrer_id) + if referer is None: + continue + await users.increase_balance_by(REFERAL_REWARD, database_id=referer.id) + await bot.send_message( + chat_id=referer.telegram_id, + text=f"Congratulations, you have received a reward of {REFERAL_REWARD} points" + + " for subscribing using your referral link.", + ) diff --git a/src/bot/utils/tronscan_service.py b/src/bot/utils/tronscan_service.py index b1b8eb2..667e256 100755 --- a/src/bot/utils/tronscan_service.py +++ b/src/bot/utils/tronscan_service.py @@ -2,7 +2,7 @@ from typing import Dict from aiohttp import ClientSession -from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20, USDT_TRC20_WALLET_ADDRESS) +from data.config import USDT_TRC20_WALLET_ADDRESS def is_valid_transaction_hash(txid: str) -> bool: @@ -20,7 +20,9 @@ async def get_transaction_info(txid: str) -> Dict: return response -async def check_transaction_for_correct_data(txid: str) -> bool: +async def check_transaction_for_correct_data( + txid: str, subscription_amount: int +) -> bool: transaction = await get_transaction_info(txid) transaction_status = transaction.get("contractRet") if transaction_status is None: @@ -37,7 +39,7 @@ async def check_transaction_for_correct_data(txid: str) -> bool: if ( transaction_to_wallet == USDT_TRC20_WALLET_ADDRESS - and transaction_amount >= SUBSCRIBE_AMOUNT_IN_USDT_TRC20 + and transaction_amount >= subscription_amount and transaction_status == "SUCCESS" ): return True From 04a5e54cdc314ad41519e7f9aea893089032086a Mon Sep 17 00:00:00 2001 From: progeroffline Date: Tue, 23 Jan 2024 13:09:27 +0200 Subject: [PATCH 5/8] None --- src/bot/app.py | 15 ++++++++++----- src/bot/data/config.py | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/bot/app.py b/src/bot/app.py index 956a2a8..79f25ca 100755 --- a/src/bot/app.py +++ b/src/bot/app.py @@ -1,10 +1,15 @@ import asyncio -from database import \ - create_schema_if_not_exist as database_create_schema_if_not_exist -from handlers import (admin_router, channels_join_requests_router, - check_subscription_router, close_functionality_router, - payment_router, referral_router, start_router) +from database import create_schema_if_not_exist as database_create_schema_if_not_exist +from handlers import ( + admin_router, + channels_join_requests_router, + check_subscription_router, + close_functionality_router, + payment_router, + referral_router, + start_router, +) from loader import bot, dp, tasks_scheduler from middlewares import CreateUserMiddleware, UpdateLoggerMiddleware diff --git a/src/bot/data/config.py b/src/bot/data/config.py index 155a113..8e23466 100755 --- a/src/bot/data/config.py +++ b/src/bot/data/config.py @@ -31,7 +31,7 @@ SUBSCRIBE_END_NOTIFICATION_DAYS = [7, 3, 1] REFERAL_REWARD = 5 -ADMINS_ID_LIST = [535327818] +ADMINS_ID_LIST = [] private_channels = { "Channel 1": {"id": -100123456789, "invite_url": "https://t.me/+ABCDEFGHIJKL"}, } From dc047c1c0c7e9892b2c90f963a0df68a1b9e8094 Mon Sep 17 00:00:00 2001 From: progeroffline Date: Tue, 23 Jan 2024 13:16:54 +0200 Subject: [PATCH 6/8] Make balance for all users --- src/bot/app.py | 2 ++ src/bot/handlers/__init__.py | 1 + src/bot/handlers/balance.py | 15 +++++++++++++++ src/bot/keyboards/reply.py | 2 ++ src/bot/utils/subscription_checker.py | 12 ++++++++---- 5 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 src/bot/handlers/balance.py diff --git a/src/bot/app.py b/src/bot/app.py index 79f25ca..1d1e996 100755 --- a/src/bot/app.py +++ b/src/bot/app.py @@ -3,6 +3,7 @@ from database import create_schema_if_not_exist as database_create_schema_if_not_exist from handlers import ( admin_router, + balance_router, channels_join_requests_router, check_subscription_router, close_functionality_router, @@ -26,6 +27,7 @@ async def on_startup(): channels_join_requests_router, check_subscription_router, referral_router, + balance_router, ) await database_create_schema_if_not_exist() diff --git a/src/bot/handlers/__init__.py b/src/bot/handlers/__init__.py index 9eaf850..eb59401 100755 --- a/src/bot/handlers/__init__.py +++ b/src/bot/handlers/__init__.py @@ -1,4 +1,5 @@ from .admin import admin_router +from .balance import balance_router from .channels_join_requests import channels_join_requests_router from .check_subscription import check_subscription_router from .close_functionality import close_functionality_router diff --git a/src/bot/handlers/balance.py b/src/bot/handlers/balance.py new file mode 100644 index 0000000..80870c2 --- /dev/null +++ b/src/bot/handlers/balance.py @@ -0,0 +1,15 @@ +from aiogram import F, Router, types +from database import users + +balance_router = Router() + + +@balance_router.message(F.text == "Balance") +async def show_balance(message: types.Message): + if message.from_user is None: + return + user = await users.get(telegram_id=message.from_user.id) + if user is None: + return + + await message.answer(text=f"Your balance: {user.balance}") diff --git a/src/bot/keyboards/reply.py b/src/bot/keyboards/reply.py index bb50637..b630044 100755 --- a/src/bot/keyboards/reply.py +++ b/src/bot/keyboards/reply.py @@ -6,6 +6,7 @@ async def close_functionality() -> types.ReplyKeyboardMarkup: return types.ReplyKeyboardMarkup( keyboard=[ + [types.KeyboardButton(text="Balance")], [types.KeyboardButton(text="Renew subscription")], [types.KeyboardButton(text="Show close functionality")], [types.KeyboardButton(text="Check subscription")], @@ -18,6 +19,7 @@ async def close_functionality() -> types.ReplyKeyboardMarkup: async def make_subscribtion() -> types.ReplyKeyboardMarkup: return types.ReplyKeyboardMarkup( keyboard=[ + [types.KeyboardButton(text="Balance")], [types.KeyboardButton(text="Make subscription")], [types.KeyboardButton(text="Check subscription")], [types.KeyboardButton(text="Referral link")], diff --git a/src/bot/utils/subscription_checker.py b/src/bot/utils/subscription_checker.py index b569ba7..d070f78 100755 --- a/src/bot/utils/subscription_checker.py +++ b/src/bot/utils/subscription_checker.py @@ -1,8 +1,11 @@ import typing from datetime import datetime, timedelta -from data.config import (NUMBER_DAYS_FROM_ONE_PAYMENT, REFERAL_REWARD, - SUBSCRIBE_AMOUNT_BY_PLANS) +from data.config import ( + NUMBER_DAYS_FROM_ONE_PAYMENT, + REFERAL_REWARD, + SUBSCRIBE_AMOUNT_BY_PLANS, +) from database import transactions, users from keyboards import reply from utils import tronscan_service @@ -44,6 +47,7 @@ async def task(bot: "aiogram.Bot"): await users.increase_balance_by(REFERAL_REWARD, database_id=referer.id) await bot.send_message( chat_id=referer.telegram_id, - text=f"Congratulations, you have received a reward of {REFERAL_REWARD} points" - + " for subscribing using your referral link.", + text=f"Congratulations, you have received a reward of {REFERAL_REWARD} points " + + "for subscribing using your referral link.\n\n" + + f"Now your balance: {referer.balance + REFERAL_REWARD}", ) From de0d6a4f09be56d5f11e2a3d03e3d769c97211cb Mon Sep 17 00:00:00 2001 From: progeroffline Date: Tue, 23 Jan 2024 19:09:08 +0200 Subject: [PATCH 7/8] Add balance column to sql schema --- .gitignore | 0 README-ua.md | 0 README.md | 0 requirements.txt | 0 src/db/database_schema.sql | 5 ++++- 5 files changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 README-ua.md mode change 100644 => 100755 README.md mode change 100644 => 100755 requirements.txt mode change 100644 => 100755 src/db/database_schema.sql diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README-ua.md b/README-ua.md old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/src/db/database_schema.sql b/src/db/database_schema.sql old mode 100644 new mode 100755 index 64e1706..3a1a6b0 --- a/src/db/database_schema.sql +++ b/src/db/database_schema.sql @@ -4,7 +4,9 @@ CREATE TABLE IF NOT EXISTS "Users" ( "first_name" TEXT, "last_name" TEXT, "username" TEXT, - "days_sub_end" INTEGER, + "days_sub_end" TEXT, + "balance" INTEGER, + "referrer_id" INTEGER, PRIMARY KEY("id" AUTOINCREMENT) ); @@ -13,6 +15,7 @@ CREATE TABLE IF NOT EXISTS "Transactions" ( "txid" TEXT, "owner_telegram_id" INTEGER, "status" BOOLEAN, + "months" INTEGER, "created_at_timestamp" INTEGER, PRIMARY KEY("id" AUTOINCREMENT) ); From f6559b1590764afc62a75c79525ade80f6293feb Mon Sep 17 00:00:00 2001 From: progeroffline Date: Wed, 24 Jan 2024 15:58:47 +0200 Subject: [PATCH 8/8] Change inline keyboard layout --- src/bot/data/config.py | 2 ++ src/bot/keyboards/inline.py | 1 + src/bot/utils/decrease_subscription_days.py | 1 + 3 files changed, 4 insertions(+) diff --git a/src/bot/data/config.py b/src/bot/data/config.py index 8e23466..41ff9b2 100755 --- a/src/bot/data/config.py +++ b/src/bot/data/config.py @@ -34,6 +34,8 @@ ADMINS_ID_LIST = [] private_channels = { "Channel 1": {"id": -100123456789, "invite_url": "https://t.me/+ABCDEFGHIJKL"}, + "Channel 2": {"id": -100123456789, "invite_url": "https://t.me/+ABCDEFGHIJKL"}, + "Channel 3": {"id": -100123456789, "invite_url": "https://t.me/+ABCDEFGHIJKL"}, } """ diff --git a/src/bot/keyboards/inline.py b/src/bot/keyboards/inline.py index 2aa729a..b9d8956 100755 --- a/src/bot/keyboards/inline.py +++ b/src/bot/keyboards/inline.py @@ -13,5 +13,6 @@ async def channels() -> types.InlineKeyboardMarkup: url=private_channels[name]["invite_url"], ) ) + builder.adjust(1) return builder.as_markup(resize_keyboard=True) diff --git a/src/bot/utils/decrease_subscription_days.py b/src/bot/utils/decrease_subscription_days.py index d9e851b..0d63eeb 100755 --- a/src/bot/utils/decrease_subscription_days.py +++ b/src/bot/utils/decrease_subscription_days.py @@ -15,6 +15,7 @@ async def task(bot: "aiogram.Bot"): sub_end_date = datetime.strptime(user.days_sub_end, "%Y-%m-%d %H:%M:%S") days_left = (sub_end_date - datetime_now).days + 1 + print(days_left) if days_left in SUBSCRIBE_END_NOTIFICATION_DAYS: if days_left == 1: await bot.send_message(