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
index 0f91ca9..4219551
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
aiofiles==23.1.0
-aiogram==2.24
+aiogram==3.1.1
aiohttp==3.8.5
aiosignal==1.3.1
aiosqlite==0.19.0
diff --git a/src/bot/app.py b/src/bot/app.py
old mode 100644
new mode 100755
index 7d3c886..1d1e996
--- a/src/bot/app.py
+++ b/src/bot/app.py
@@ -1,20 +1,40 @@
-# -*- coding: utf-8 -*-
+import asyncio
-from aiogram import Dispatcher, executor
-from database import \
- create_schema_if_not_exist as database_create_schema_if_not_exist
-from loader import dp, tasks_scheduler
+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,
+ payment_router,
+ referral_router,
+ start_router,
+)
+from loader import bot, dp, tasks_scheduler
+from middlewares import CreateUserMiddleware, UpdateLoggerMiddleware
-async def on_startup(dp: Dispatcher):
- import filters
- import handlers
- import middlewares
-
+async def on_startup():
+ 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,
+ referral_router,
+ balance_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/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..41ff9b2
--- 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,53 @@
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'
-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 = []
private_channels = {
- 'Channel 1': {
- 'id': -100123456789,
- 'invite_url': 'https://t.me/+ABCDEFGHIJKL'
- },
+ "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"},
}
+
+"""
+ Use HTML to format text
+
+ bold, bold
+ italic, italic
+ underline, underline
+ strikethrough, strikethrough, strikethrough
+ spoiler, 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..78d6a7c --- 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,19 @@ class User: first_name: Optional[str] last_name: Optional[str] username: Optional[str] - days_sub_end: int - + days_sub_end: str + balance: int + referrer_id: int + @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 +30,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..7b6fc57 --- 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,127 @@ 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'), %s, %s) ; - """ % ( - 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, + 0, + 0, + ) + ) 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] + + +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/filters/__init__.py b/src/bot/filters/__init__.py old mode 100644 new mode 100755 index f6143cd..47d30ab --- a/src/bot/filters/__init__.py +++ b/src/bot/filters/__init__.py @@ -1,10 +1,3 @@ -# -*- coding: utf-8 -*- - -from loader import dp - +from .is_admin import IsAdminFilter 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/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 6a99419..f8114b4 --- a/src/bot/filters/user_not_subscribed.py +++ b/src/bot/filters/user_not_subscribed.py @@ -1,14 +1,21 @@ -# -*- coding: utf-8 -*- +from datetime import datetime 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): - async def check(self, message: types.Message): + 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 2797e85..4f4ff1a --- a/src/bot/filters/user_subscribed.py +++ b/src/bot/filters/user_subscribed.py @@ -1,14 +1,21 @@ -# -*- coding: utf-8 -*- +from datetime import datetime 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): - async def check(self, message: types.Message): + 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 8fbee22..eb59401 --- a/src/bot/handlers/__init__.py +++ b/src/bot/handlers/__init__.py @@ -1,6 +1,8 @@ -# -*- coding: utf-8 -*- - -from .channels_join_requests import * -from .close_functionality import * -from .payment import * -from .start import * +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 +from .payment import payment_router +from .referral import referral_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/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/handlers/channels_join_requests.py b/src/bot/handlers/channels_join_requests.py
old mode 100644
new mode 100755
index 04c0bd4..421f1c8
--- a/src/bot/handlers/channels_join_requests.py
+++ b/src/bot/handlers/channels_join_requests.py
@@ -1,11 +1,17 @@
-from aiogram import types
+from datetime import datetime
+
+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
- 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 a6689dc..24f9548
--- a/src/bot/handlers/close_functionality.py
+++ b/src/bot/handlers/close_functionality.py
@@ -1,12 +1,16 @@
-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.',
+ 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 2665c7f..6301aa5
--- a/src/bot/handlers/payment.py
+++ b/src/bot/handlers/payment.py
@@ -1,48 +1,69 @@
-# -*- coding: utf-8 -*-
-
-from aiogram import types
-from aiogram.dispatcher import FSMContext
-from data.config import (SUBSCRIBE_AMOUNT_IN_USDT_TRC20,
- USDT_TRC20_WALLET_ADDRESS)
+from aiogram import F, Router, types
+from aiogram.fsm.context import FSMContext
+from data.config import SUBSCRIBE_AMOUNT_BY_PLANS, 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(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(
+ SUBSCRIBE_AMOUNT_BY_PLANS.keys()
+ ),
+ )
+
+
+@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_BY_PLANS[termin]} USDT TRC20
.\n"
+ "After submitting, click the Confirm button.",
reply_markup=await reply_keyboards.confirm_transfer(),
)
-
-
-@dp.message_handler(UserNotSubscribedFilter(), text='Confirm transfer')
-async def confirm_transfer(message: types.Message):
- await GetTxidFromUser.state.set()
+
+
+@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(),
)
-
-@dp.message_handler(UserNotSubscribedFilter(), state=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 or message.from_user is None:
+ return
+
if transaction is None and tronscan_service.is_valid_transaction_hash(message.text):
- await state.finish()
- await transactions.create(message.text, message.from_user.id)
+ data = await state.get_data()
+ await transactions.create(
+ message.text,
+ message.from_user.id,
+ data["subscription_termin"],
+ )
+ await state.clear()
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/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
old mode 100644
new mode 100755
index 16a3c59..f9e4ede
--- a/src/bot/handlers/start.py
+++ b/src/bot/handlers/start.py
@@ -1,28 +1,50 @@
-# -*- coding: utf-8 -*-
+from typing import Optional
-from aiogram import types
+from aiogram import F, Router, types
+from aiogram.filters import CommandObject, 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())
+@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(),
)
-
-@dp.message_handler(UserNotSubscribedFilter(), commands=['start'], state="*")
-async def start_for_not_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, 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.',
+ 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 ea0e3f3..b9d8956
--- a/src/bot/keyboards/inline.py
+++ b/src/bot/keyboards/inline.py
@@ -1,16 +1,18 @@
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'],
+ url=private_channels[name]["invite_url"],
)
)
+ builder.adjust(1)
- return keyboard
+ return builder.as_markup(resize_keyboard=True)
diff --git a/src/bot/keyboards/reply.py b/src/bot/keyboards/reply.py
old mode 100644
new mode 100755
index 7b4a8ac..b630044
--- a/src/bot/keyboards/reply.py
+++ b/src/bot/keyboards/reply.py
@@ -1,35 +1,62 @@
-# -*- coding: utf-8 -*-
+from collections.abc import Iterable
from aiogram import types
-async def close_functionality():
- return types.ReplyKeyboardMarkup(resize_keyboard=True).add(
- types.KeyboardButton(
- text='Show close functionality',
- )
+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")],
+ [types.KeyboardButton(text="Referral link")],
+ ],
+ resize_keyboard=True,
)
-async def make_subscribtion():
- return types.ReplyKeyboardMarkup(resize_keyboard=True).add(
- types.KeyboardButton(
- text='Make subscription',
- )
+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")],
+ ],
+ resize_keyboard=True,
)
-async def confirm_transfer():
- return types.ReplyKeyboardMarkup(resize_keyboard=True).add(
- types.KeyboardButton(
- text='Confirm transfer',
- )
+async def confirm_transfer() -> types.ReplyKeyboardMarkup:
+ return types.ReplyKeyboardMarkup(
+ keyboard=[
+ [types.KeyboardButton(text="Confirm transfer")],
+ [types.KeyboardButton(text="Back to main menu")],
+ ],
+ resize_keyboard=True,
)
-async def check_transaction():
- return types.ReplyKeyboardMarkup(resize_keyboard=True).add(
- types.KeyboardButton(
- text='Check transaction',
- )
+async def check_transaction() -> types.ReplyKeyboardMarkup:
+ return types.ReplyKeyboardMarkup(
+ keyboard=[[types.KeyboardButton(text="Check transaction")]],
+ resize_keyboard=True,
+ )
+
+
+async def back_to_main_menu() -> types.ReplyKeyboardMarkup:
+ return types.ReplyKeyboardMarkup(
+ keyboard=[[types.KeyboardButton(text="Back to main menu")]],
+ resize_keyboard=True,
+ )
+
+
+async def subscription_termins(plans: Iterable[int]) -> types.ReplyKeyboardMarkup:
+ return types.ReplyKeyboardMarkup(
+ keyboard=[
+ [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/loader.py b/src/bot/loader.py
old mode 100644
new mode 100755
index ffe4c71..7b0fff1
--- a/src/bot/loader.py
+++ b/src/bot/loader.py
@@ -1,22 +1,28 @@
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 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=types.ParseMode.HTML)
+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 55bdfb6..21f5344
--- a/src/bot/middlewares/__init__.py
+++ b/src/bot/middlewares/__init__.py
@@ -1,10 +1,2 @@
-# -*- 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
old mode 100644
new mode 100755
index 1303b99..d78898f
--- a/src/bot/middlewares/create_user_middleware.py
+++ b/src/bot/middlewares/create_user_middleware.py
@@ -1,26 +1,25 @@
-# -*- coding: utf-8 -*-
+from collections.abc import Awaitable, Callable
+from typing import Any, Dict
-from aiogram import types
-from aiogram.dispatcher.middlewares import BaseMiddleware
+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):
- 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,
- )
-
- 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)
+ async def __call__(
+ self,
+ handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
+ message: Message,
+ data: Dict[str, Any],
+ ) -> Any:
+ 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)
diff --git a/src/bot/middlewares/logger_middleware.py b/src/bot/middlewares/logger_middleware.py
old mode 100644
new mode 100755
index 383d5e3..0cca5f2
--- a/src/bot/middlewares/logger_middleware.py
+++ b/src/bot/middlewares/logger_middleware.py
@@ -1,10 +1,18 @@
-# -*- coding: utf-8 -*-
+from collections.abc import Awaitable, Callable
+from typing import Any, Dict
-from aiogram import types
-from aiogram.dispatcher.middlewares import BaseMiddleware
+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
old mode 100644
new mode 100755
index fb1355c..07232f2
--- a/src/bot/statesgroup.py
+++ b/src/bot/statesgroup.py
@@ -1,6 +1,4 @@
-# -*- coding: utf-8 -*-
-
-from aiogram.dispatcher.filters.state import State, StatesGroup
+from aiogram.fsm.state import State, StatesGroup
class GetTxidFromUser(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..0d63eeb
--- a/src/bot/utils/decrease_subscription_days.py
+++ b/src/bot/utils/decrease_subscription_days.py
@@ -1,23 +1,31 @@
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
+ print(days_left)
+ 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..d070f78
--- a/src/bot/utils/subscription_checker.py
+++ b/src/bot/utils/subscription_checker.py
@@ -1,24 +1,53 @@
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
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, SUBSCRIBE_AMOUNT_BY_PLANS[transaction.months]
+ ):
+ 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=transaction.owner_telegram_id,
+ 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=record.owner_telegram_id,
- text='Congratulations, you now have access to limited functionality.'
+ chat_id=referer.telegram_id,
+ 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}
",
)
-
diff --git a/src/bot/utils/tronscan_service.py b/src/bot/utils/tronscan_service.py
old mode 100644
new mode 100755
index 9162769..667e256
--- 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 USDT_TRC20_WALLET_ADDRESS
def is_valid_transaction_hash(txid: str) -> bool:
@@ -14,32 +11,37 @@ 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()
return response
-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
+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:
+ 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 >= subscription_amount
+ and transaction_status == "SUCCESS"
+ ):
+ return True
+ else:
return False
-
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)
);