chore: сделал
This commit is contained in:
1 parent
602199d510
commit
45e7a1d22f
25 files changed
+1164
-113
No files matched your search
+140
-4
@@ -4,7 +4,7 @@ import re
|
||||
import serverus
|
||||
from Telegram.interfaces import Interface, command, register, state, text
|
||||
|
||||
from .. import services, tasks
|
||||
from .. import fields, services, tasks
|
||||
from ..models import Credentials, Point, Server
|
||||
from . import texts
|
||||
|
||||
@@ -18,6 +18,11 @@ SELECT = 'select'
|
||||
POINT = 'point'
|
||||
POINT_RELEASE = 'point.release'
|
||||
POINT_REINSTALL = 'point.reinstall'
|
||||
POINT_DOMAIN_EDIT = 'point.domain'
|
||||
POINT_PROXY_EDIT = 'point.proxy'
|
||||
POINT_KEYS = 'point.keys'
|
||||
POINT_KEY_ADD = 'point.keys.add'
|
||||
POINT_KEY_REVOKE = 'point.keys.revoke'
|
||||
|
||||
DRAFT_CREDENTIALS = {DRAFT_SERVER: 'server', DRAFT_MASK: 'mask'}
|
||||
DOMAIN_RE = re.compile(r'^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$')
|
||||
@@ -149,6 +154,8 @@ class MyPointInterface(Interface):
|
||||
|
||||
@state(DRAFT_PROXY)
|
||||
def set_proxy(self, ctx):
|
||||
# в прокси может быть пароль
|
||||
ctx.delete_message()
|
||||
proxy = ctx.text.strip()
|
||||
if not is_proxy_url(proxy):
|
||||
return ctx.reply(texts.PROXY_INVALID, parse_mode=HTML)
|
||||
@@ -274,15 +281,144 @@ class MyPointInterface(Interface):
|
||||
@text(texts.NO, state=POINT_RELEASE)
|
||||
@text(texts.CANCEL, state=POINT_RELEASE)
|
||||
@text(texts.CANCEL, state=POINT_REINSTALL)
|
||||
@text(texts.CANCEL, state=POINT_DOMAIN_EDIT)
|
||||
@text(texts.CANCEL, state=POINT_PROXY_EDIT)
|
||||
def back_to_point(self, ctx):
|
||||
point = self.current_point(ctx)
|
||||
if point is None:
|
||||
return self.main_menu(ctx)
|
||||
self.show_point(ctx, point)
|
||||
|
||||
# домен и прокси готовой точки: применяются в фоне, панель тем временем работает с прежними настройками
|
||||
|
||||
@text(texts.POINT_DOMAIN, state=POINT)
|
||||
def ask_point_domain(self, ctx):
|
||||
point = self.ready_point(ctx)
|
||||
if point:
|
||||
keyboard = texts.CANCEL_KEYBOARD if point.mask else [[texts.CANCEL, texts.DELETE]]
|
||||
self.ask(ctx, POINT_DOMAIN_EDIT, texts.domain_prompt(point), keyboard)
|
||||
|
||||
@state(POINT_DOMAIN_EDIT)
|
||||
def change_domain(self, ctx):
|
||||
domain = ctx.text.strip().lower().rstrip('.')
|
||||
if not is_domain(domain):
|
||||
return ctx.reply(texts.DOMAIN_INVALID)
|
||||
point = self.ready_point(ctx)
|
||||
if point:
|
||||
self.start_change(ctx, point, tasks.set_domain, domain)
|
||||
|
||||
@text(texts.DELETE, state=POINT_DOMAIN_EDIT)
|
||||
def delete_point_domain(self, ctx):
|
||||
point = self.ready_point(ctx)
|
||||
if point is None:
|
||||
return
|
||||
if point.mask:
|
||||
return ctx.reply(texts.DOMAIN_REQUIRED_BY_MASK)
|
||||
if not point.domain:
|
||||
return self.show_point(ctx, point)
|
||||
self.start_change(ctx, point, tasks.set_domain, '')
|
||||
|
||||
@text(texts.POINT_PROXY, state=POINT)
|
||||
def ask_point_proxy(self, ctx):
|
||||
point = self.ready_point(ctx)
|
||||
if point:
|
||||
self.ask(ctx, POINT_PROXY_EDIT, texts.proxy_prompt(point), [[texts.CF_WARP, texts.CANCEL, texts.DELETE]])
|
||||
|
||||
@state(POINT_PROXY_EDIT)
|
||||
def change_proxy(self, ctx):
|
||||
# в прокси может быть пароль
|
||||
ctx.delete_message()
|
||||
proxy = ctx.text.strip()
|
||||
if not is_proxy_url(proxy):
|
||||
return ctx.reply(texts.PROXY_INVALID, parse_mode=HTML)
|
||||
self.start_proxy_change(ctx, proxy)
|
||||
|
||||
@text(texts.CF_WARP, state=POINT_PROXY_EDIT)
|
||||
def change_proxy_to_warp(self, ctx):
|
||||
self.start_proxy_change(ctx, Point.CF_WARP)
|
||||
|
||||
@text(texts.DELETE, state=POINT_PROXY_EDIT)
|
||||
def delete_point_proxy(self, ctx):
|
||||
self.start_proxy_change(ctx, '')
|
||||
|
||||
def start_proxy_change(self, ctx, proxy: str):
|
||||
point = self.ready_point(ctx)
|
||||
if point:
|
||||
self.start_change(ctx, point, tasks.set_outbound_proxy, fields.encrypt(proxy) if proxy else '')
|
||||
|
||||
def start_change(self, ctx, point: Point, actor, value: str):
|
||||
point.set_status(Point.Status.UPDATING)
|
||||
actor.send(point.pk, ctx.chat.pk, value)
|
||||
ctx.reset()
|
||||
ctx.reply(texts.CHANGE_STARTED, keyboard=texts.MAIN_KEYBOARD_WITH_POINTS)
|
||||
|
||||
# ключи
|
||||
|
||||
def ready_point(self, ctx) -> Point | None:
|
||||
"""Текущая точка, если на ее панели можно выпускать ключи. Иначе отвечает пользователю и возвращает None."""
|
||||
point = self.idle_point(ctx)
|
||||
if point is not None and (point.status != Point.Status.READY or not point.panel_inbound_id):
|
||||
ctx.reply(texts.POINT_NOT_READY)
|
||||
return None
|
||||
return point
|
||||
|
||||
@text(texts.CLIENTS, state=POINT)
|
||||
def not_implemented(self, ctx):
|
||||
# TODO: настройки домена, прокси и клиенты через API панели 3x-ui
|
||||
ctx.reply(texts.NOT_IMPLEMENTED)
|
||||
@text(texts.CLIENTS, state=POINT_KEYS)
|
||||
@text(texts.CANCEL, state=POINT_KEY_ADD)
|
||||
@text(texts.CANCEL, state=POINT_KEY_REVOKE)
|
||||
def show_keys(self, ctx):
|
||||
point = self.ready_point(ctx)
|
||||
if point is None:
|
||||
return
|
||||
ctx.state = POINT_KEYS
|
||||
keys = [(key, services.key_traffic(key)) for key in point.keys.all()]
|
||||
ctx.reply(texts.keys_list(point, keys), keyboard=texts.KEYS_KEYBOARD, parse_mode=HTML)
|
||||
|
||||
@state(POINT_KEYS)
|
||||
def show_key(self, ctx):
|
||||
point = self.ready_point(ctx)
|
||||
if point is None:
|
||||
return
|
||||
key = point.keys.find_by_name(ctx.text.strip().lower())
|
||||
if key is None:
|
||||
return ctx.reply(texts.KEY_NOT_FOUND)
|
||||
ctx.reply(texts.key_card(key, services.key_traffic(key)), parse_mode=HTML)
|
||||
|
||||
@text(texts.ADD_KEY, state=POINT_KEYS)
|
||||
def ask_key_name(self, ctx):
|
||||
if self.ready_point(ctx):
|
||||
self.ask(ctx, POINT_KEY_ADD, texts.KEY_NAME_PROMPT)
|
||||
|
||||
@state(POINT_KEY_ADD)
|
||||
def add_key(self, ctx):
|
||||
point = self.ready_point(ctx)
|
||||
if point is None:
|
||||
return
|
||||
try:
|
||||
key = services.issue_key(point, ctx.text.strip().lower())
|
||||
except ValueError as error:
|
||||
return ctx.reply(str(error))
|
||||
except serverus.XuiApiError as error:
|
||||
return ctx.reply(texts.PANEL_ERROR.format(error=error))
|
||||
ctx.state = POINT_KEYS
|
||||
ctx.reply(texts.key_card(key, None), keyboard=texts.KEYS_KEYBOARD, parse_mode=HTML)
|
||||
|
||||
@text(texts.REVOKE_KEY, state=POINT_KEYS)
|
||||
def ask_revoke_key(self, ctx):
|
||||
if self.ready_point(ctx):
|
||||
self.ask(ctx, POINT_KEY_REVOKE, texts.KEY_REVOKE_PROMPT)
|
||||
|
||||
@state(POINT_KEY_REVOKE)
|
||||
def revoke_key(self, ctx):
|
||||
point = self.ready_point(ctx)
|
||||
if point is None:
|
||||
return
|
||||
key = point.keys.find_by_name(ctx.text.strip().lower())
|
||||
if key is None:
|
||||
return ctx.reply(texts.KEY_NOT_FOUND)
|
||||
try:
|
||||
services.revoke_key(key)
|
||||
except serverus.XuiApiError as error:
|
||||
return ctx.reply(texts.PANEL_ERROR.format(error=error))
|
||||
ctx.reply(texts.KEY_REVOKED.format(name=key.name))
|
||||
self.show_keys(ctx)
|
||||
Reference in new issue
Block a user