summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bot.py52
-rw-r--r--config_sample.py1
-rw-r--r--enums.py13
-rw-r--r--keyboards.py23
-rw-r--r--logger_config.py27
-rw-r--r--requirements.txt11
-rw-r--r--spec.org49
-rw-r--r--utils.py18
-rw-r--r--views.py71
9 files changed, 265 insertions, 0 deletions
diff --git a/bot.py b/bot.py
new file mode 100644
index 0000000..dfb65b4
--- /dev/null
+++ b/bot.py
@@ -0,0 +1,52 @@
+from telegram.ext import Updater, CommandHandler, ConversationHandler, MessageHandler, Filters
+from telegram import Update, User, Bot # Typing
+
+from config import TG_TOKEN
+
+from enums import UtilStates, MainStates
+from views import UtilViews, MainStatesView
+from keyboards import MenuKeyboard, BACK
+
+from logger_config import logger
+
+
+def error(bot, update, error):
+ logger.warning('Update "%s" caused error "%s"', update, error)
+
+
+def main():
+ conversation = ConversationHandler(
+ entry_points=[
+ CommandHandler('start', UtilViews.start),
+ ],
+
+ states={
+ UtilStates.WAIT_FOR_USERNAME: [MessageHandler(Filters.text, UtilViews.wait_for_username)],
+ MainStates.MAIN_MENU: [
+ MessageHandler(Filters.regex(MenuKeyboard.NEW_CLIENT), MainStatesView.new_client_name),
+ MessageHandler(Filters.regex(MenuKeyboard.LIST_CLIENTS), MainStatesView.list_clients),
+ ],
+ MainStates.ENTERING_NAME: [
+ MessageHandler(Filters.regex(BACK), MainStatesView.main_menu),
+ MessageHandler(Filters.text, MainStatesView.new_client),
+ ],
+ },
+
+ fallbacks=[
+ MessageHandler(Filters.text, UtilViews.fallback),
+ ]
+ )
+
+
+ updater = Updater(TG_TOKEN)
+
+ dp = updater.dispatcher
+ dp.add_error_handler(error)
+ dp.add_handler(conversation)
+
+ updater.start_polling()
+ updater.idle()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/config_sample.py b/config_sample.py
new file mode 100644
index 0000000..90d0772
--- /dev/null
+++ b/config_sample.py
@@ -0,0 +1 @@
+TG_TOKEN = ""
diff --git a/enums.py b/enums.py
new file mode 100644
index 0000000..be70024
--- /dev/null
+++ b/enums.py
@@ -0,0 +1,13 @@
+class UtilStates:
+ (
+ WAIT_FOR_USERNAME,
+ *_
+ ) = range(100)
+
+
+class MainStates:
+ (
+ MAIN_MENU,
+ ENTERING_NAME,
+ *_
+ ) = range(100)
diff --git a/keyboards.py b/keyboards.py
new file mode 100644
index 0000000..4f73c55
--- /dev/null
+++ b/keyboards.py
@@ -0,0 +1,23 @@
+from telegram import ReplyKeyboardMarkup
+from abc import ABC
+
+
+BACK = "↩️ Назад"
+
+
+class Keyboard(ABC):
+ @classmethod
+ def get_keyboard(cls, telegram_id=None):
+ pass
+
+
+class MenuKeyboard(Keyboard):
+ NEW_CLIENT = "📝 Добавить новый клиент"
+ LIST_CLIENTS = "📚 Вывести список клиентов"
+
+ @classmethod
+ def get_keyboard(cls, telegram_id=None):
+ return ReplyKeyboardMarkup([
+ [cls.NEW_CLIENT],
+ [cls.LIST_CLIENTS],
+ ])
diff --git a/logger_config.py b/logger_config.py
new file mode 100644
index 0000000..1dbea4c
--- /dev/null
+++ b/logger_config.py
@@ -0,0 +1,27 @@
+import logging
+import os
+
+try:
+ os.mkdir("logs")
+except FileExistsError:
+ pass
+
+fh = logging.FileHandler("logs/log.log")
+fh.setLevel(logging.DEBUG)
+
+ch = logging.StreamHandler()
+ch.setLevel(logging.DEBUG)
+
+log_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+fh.setFormatter(log_format)
+ch.setFormatter(log_format)
+
+logging.basicConfig(
+ level=logging.DEBUG, handlers=[fh, ch],
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+)
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+logger.addHandler(fh)
+logger.addHandler(ch)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ba803e2
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,11 @@
+APScheduler==3.6.3
+backports.zoneinfo==0.2.1
+cachetools==4.2.2
+certifi==2022.6.15
+python-telegram-bot==13.13
+pytz==2022.1
+pytz-deprecation-shim==0.1.0.post0
+six==1.16.0
+tornado==6.1
+tzdata==2022.1
+tzlocal==4.2
diff --git a/spec.org b/spec.org
new file mode 100644
index 0000000..eea2014
--- /dev/null
+++ b/spec.org
@@ -0,0 +1,49 @@
+#+TITLE: Bot architecture
+
+* База данных
+
+** Таблицы
+
+*** users
+| field | type | foreign |
+|-----------+----------+---------|
+| id | int PK | no |
+| tg_id | int | no |
+| tg_handle | varchar | no |
+| joined_at | datetime | no |
+
+*** clients
+| field | type | foreign |
+|------------+----------+----------|
+| id | int PK | no |
+| user_id | int | users.id |
+| ip | int | no |
+| pubkey | varchar | no |
+| created_at | datetime | no |
+
+*** user_perms
+| field | type | foreign |
+|-----------+--------+----------|
+| user_id | int PK | users.id |
+| is_admin | bool | no |
+| is_client | bool | no |
+
+
+** Представления
+
+*** ClientsView
+| field | type | foreign |
+|--------+---------+---------|
+| ip | int | no |
+| pubkey | varchar | no |
+WHERE: user.is_client = True
+
+* Бот
+
+** MAIN_MENU
+- Создать новый клиент -> CREATE
+- Список созданных клиентов
+
+** NAME_CLIENT
+- [Ввод] Название клиента
+- Назад -> MAIN_MENU
diff --git a/utils.py b/utils.py
new file mode 100644
index 0000000..00960d0
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,18 @@
+import socket
+import struct
+
+
+def ip2int(addr):
+ return struct.unpack("!I", socket.inet_aton(addr))[0]
+
+
+def int2ip(addr):
+ return socket.inet_ntoa(struct.pack("!I", addr))
+
+
+a = ip2int('10.13.37.254')
+b = a + 1
+while (b & 0xFF) in (0x00, 0xFF):
+ b += 1
+c = int2ip(b)
+print(a, b, c)
diff --git a/views.py b/views.py
new file mode 100644
index 0000000..692af49
--- /dev/null
+++ b/views.py
@@ -0,0 +1,71 @@
+from telegram import Update, User, Bot # Typing
+from telegram.ext import CallbackContext # Typing
+from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove
+
+from enums import UtilStates, MainStates
+from keyboards import MenuKeyboard, BACK
+
+class UtilViews:
+ @staticmethod
+ def start(update: Update, context: CallbackContext):
+ update.message.reply_text(
+ "Welcome to The Club.",
+ reply_markup=ReplyKeyboardRemove()
+ )
+
+ user: User = update.message.from_user
+ if user.username is None:
+ return UtilViews.wait_for_username(update, context)
+ else:
+ return MainStatesView.main_menu(update, context)
+
+
+ @staticmethod
+ def wait_for_username(update: Update, context: CallbackContext):
+ user: User = update.message.from_user
+ if user.username is None:
+ update.message.reply_text(
+ "Этим ботом можно пользоваться только с указанным именем пользователя."
+ "Укажи его и возвращайся как только это сделаешь.",
+ reply_markup=ReplyKeyboardMarkup([['Я указал имя пользователя']])
+ )
+ return UtilStates.WAIT_FOR_USERNAME
+
+ else:
+ return MainStatesView.main_menu(update, context)
+
+ @staticmethod
+ def fallback(update: Update, context: CallbackContext):
+ update.message.reply_text("Команда не распознана")
+
+
+class MainStatesView:
+ @staticmethod
+ def main_menu(update: Update, context: CallbackContext):
+ update.message.reply_text(
+ "Выбери следующее действие...",
+ reply_markup=MenuKeyboard.get_keyboard()
+ )
+
+ return MainStates.MAIN_MENU
+
+ @staticmethod
+ def new_client_name(update: Update, context: CallbackContext):
+ update.message.reply_text(
+ "Введи имя для клиента (чтобы ты мог отличить клиентов в списке).",
+ reply_markup=ReplyKeyboardMarkup([[BACK]])
+ )
+ return MainStates.ENTERING_NAME
+
+ @staticmethod
+ def new_client(update: Update, context: CallbackContext):
+ client_name = update.message.text.strip()
+ update.message.reply_text(f"Клиент |{client_name}| успешно добавлен")
+ return MainStatesView.main_menu(update, context)
+
+
+ @staticmethod
+ def list_clients(update: Update, context: CallbackContext):
+ update.message.reply_text("Список клиентов:")
+ return MainStatesView.main_menu(update, context)
+