"""Кнопки и тексты бота. Сообщения размечены HTML, данные пользователя экранируются.""" from html import escape from ..models import Point, Server # главное меню MAIN_MENU = 'главное меню' FAQ = 'ФАК' ADD_SERVER = 'добавить сервер' HELP = 'помощь' MANAGE = 'управление сервером' # общие CANCEL = 'отмена' DELETE = 'удалить' YES = 'Да' NO = 'Нет' # меню добавления сервера SERVER = 'сервер' MASK = 'маскировка' DOMAIN = 'домен' CLOUDFLARE = 'cloudflare' PROXY = 'прокси' INSTALL = 'установить' ENABLE = 'включить' DISABLE = 'выключить' CF_WARP = 'cf_warp' # меню сервера SERVER_MENU = 'меню сервера' RELEASE = 'Вернуть' REINSTALL = 'Переустановить' POINT_DOMAIN = 'Домен' POINT_PROXY = 'Прокси' POINT_CLOUDFLARE = 'Cloudflare' CLIENTS = 'Клиенты' USE_OLD = 'Использовать старый' MAIN_KEYBOARD = [[FAQ, ADD_SERVER, HELP]] MAIN_KEYBOARD_WITH_POINTS = [[FAQ, ADD_SERVER, HELP], [MANAGE]] DRAFT_KEYBOARD = [[DOMAIN, CLOUDFLARE, PROXY, MASK], [CANCEL, INSTALL, SERVER, HELP]] POINT_KEYBOARD = [[RELEASE, REINSTALL], [POINT_DOMAIN, POINT_PROXY, POINT_CLOUDFLARE], [CLIENTS], [MAIN_MENU]] CANCEL_KEYBOARD = [[CANCEL]] EMPTY = '-' GREETING = 'привет. выбери действие' FAQ_TEXT = '''Что это за бот? Бот настраивает ваш собственный VPN на ваших серверах: панель 3x-ui с подключениями vless + tls + reality и сервер маскировки перед ней. Что нужно от меня? Два арендованных сервера с Debian 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root каждого. По желанию - домен и прокси на выходе. Зачем два сервера? Клиенты подключаются к серверу маскировки, он пересылает трафик на панель. Адрес панели никто не видит. Что бот делает с сервером? Прописывает свой ssh ключ, меняет пароль root, обновляет систему, переносит ssh на случайный порт с входом только по ключу, включает фаервол и ставит нужные сервисы. Как забрать сервер назад? Меню сервера - "Вернуть". Бот уберет свои доступы, откатит ssh и фаервол, поставит новый пароль root и пришлет его. Данные на сервере останутся. Сервер переустановили у провайдера Меню сервера - "Переустановить" и новый пароль root.''' HELP_TEXT = '''Добавить сервер 1. "добавить сервер" 2. "сервер" - пришлите ip:пароль сервера для панели 3. "маскировка" - пришлите ip:пароль сервера маскировки 4. по желанию "домен", "cloudflare", "прокси" 5. "установить" - установка идет в фоне, по окончании придет уведомление Управлять сервером "управление сервером" и имя сервера из главного меню. "главное меню" или /start всегда возвращают в начало.''' CREDENTIALS_PROMPT = 'отправь данные для подключения к серверу\nip:root_password' CREDENTIALS_INVALID = 'Не похоже на ip:root_password. Попробуй еще раз.' ADDRESS_TAKEN = 'Сервер {address} уже подключен.' ADDRESS_DUPLICATE = 'Панель и маскировка должны быть разными серверами.' DOMAIN_PROMPT = 'Пришли доменное имя. Доменное имя лучше подходит для маскировки вашей точки доступа.' DOMAIN_INVALID = 'Не похоже на доменное имя. Попробуй еще раз.' CLOUDFLARE_PROMPT = 'Если вы спрятали домен за клаудом, то нужно включить опцию тут.' PROXY_PROMPT = 'Можно указать свои в формате protocol://username:password@host:port или включить cf_warp.' PROXY_INVALID = 'Не похоже на protocol://username:password@host:port. Попробуй еще раз.' DRAFT_INCOMPLETE = 'Нужны оба сервера: "сервер" и "маскировка".' CLOUDFLARE_WITHOUT_DOMAIN = 'Cloudflare работает только с доменом. Укажи домен или выключи cloudflare.' INSTALL_STARTED = 'Запускаю установку сервера. Отправлю уведомление когда все будет готово.' SELECT_PROMPT = 'Пришлите server_name' POINT_NOT_FOUND = 'Сервер не найден. Пришлите server_name из списка.' POINT_BUSY = 'Сервер сейчас {status}. Дождитесь окончания.' RELEASE_CONFIRM = '''Сервер будет освобожден из-под моего контроля, будет установлен случайный пароль рута, мои доступы будут убраны, данные на сервере сохранены. Будет отправлено уведомление с новым паролем. Продолжить?''' RELEASE_STARTED = 'Освобождаю серверы. Пришлю новые пароли, когда закончу.' REINSTALL_PROMPT = '''Будет произведена повторная установка всех компонентов. Вызывайте эту функцию, только после того, как в панели управления сервером в аккаунте хостинг провайдера вызовите переустановку сервера. Если уверены, то пришлите ip:новый_пароль переустановленного сервера. "Использовать старый" переустановит оба сервера с последними известными паролями.''' REINSTALL_UNKNOWN_ADDRESS = 'Сервер {address} не входит в эту точку.' REINSTALL_STARTED = 'Запускаю переустановку. Отправлю уведомление когда все будет готово.' NOT_IMPLEMENTED = 'Пока не реализовано.' FALLBACK = 'Не понял. Воспользуйся кнопками меню.' def value(text: str) -> str: return escape(text) if text else EMPTY def draft_card(draft: dict) -> str: return '\n'.join([ f"Сервер: {value(draft.get('server', {}).get('address', ''))}", f"Сервер маскировки: {value(draft.get('mask', {}).get('address', ''))}", f"Домен: {value(draft.get('domain', ''))}", f"Cloudflare: {'Да' if draft.get('cloudflare') else EMPTY}", f"Прокси: {value(draft.get('proxy', ''))}", ]) def points_list(points) -> str: return '\n'.join(f'{escape(point.name)}\t{point.get_status_display()}' for point in points) def point_card(point: Point) -> str: servers = {server.role: server for server in point.servers.all()} lines = [ f'Сервер: {escape(point.name)}', f'Состояние: {point.get_status_display()}', ] for role in Server.Role: server = servers.get(role) lines.append(f'{role.label.capitalize()}: {escape(server.address) if server else EMPTY}') lines += [ # TODO: онлайн/всего ключей из API панели f'Ключи: {EMPTY}', f'Домен: {value(point.domain)}', f"Cloudflare: {'Да' if point.cloudflare else EMPTY}", f'Прокси: {value(point.outbound_proxy)}', ] if point.error: lines.append(f'Ошибка: {escape(point.error)}') return '\n'.join(lines) def installed(point: Point) -> str: return f'сервер {escape(point.mask.address)} - проименован как {escape(point.name)}, готов к работе.' def install_failed(point: Point, error: Exception) -> str: return f'Не удалось настроить {escape(point.name)}:\n{escape(str(error))}' def released(name: str, passwords: dict[str, str], errors: dict[str, str]) -> str: lines = [f'Серверы {escape(name)} возвращены.' if not errors else f'{escape(name)}:'] lines += [f'{escape(address)} - пароль root {escape(password)}' for address, password in passwords.items()] lines += [f'{escape(address)} - не удалось вернуть: {escape(error)}' for address, error in errors.items()] return '\n'.join(lines)