Compare commits

..

9 Commits

Author SHA1 Message Date
17a1e4bb36 Feat: change deployment
All checks were successful
ci/woodpecker/push/pipeline Pipeline was successful
2025-01-22 13:25:45 +03:00
0faff426a7 Feat: update woodpecker
All checks were successful
ci/woodpecker/push/pipeline Pipeline was successful
2025-01-22 12:59:35 +03:00
970a0e6fd4 Hotfix: pg_isready healthcheck
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-20 15:27:31 +03:00
c13eb62770 Feat: url correcter util
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-20 13:44:54 +03:00
0fa538d079 Feat: get users command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-17 14:24:41 +03:00
fed548e9e8 Feat: admin help command 2025-01-17 14:10:02 +03:00
3ad4c2ea3e First hotfix
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2025-01-17 14:04:35 +03:00
e3c7f96693 Merge branch 'hotfix-start-command' 2025-01-17 13:52:33 +03:00
e82c392fc8 Hotfix: user deleting tg_id 2025-01-17 13:51:57 +03:00
8 changed files with 66 additions and 8 deletions

View File

@@ -5,5 +5,6 @@ POSTGRES_PASSWORD=postgres
POSTGRES_DB=postgres POSTGRES_DB=postgres
POSTGRES_HOST=postgres POSTGRES_HOST=postgres
POSTGRES_PORT=5432 POSTGRES_PORT=5432
DOMAIN_NAME=localhost
COMPOSE_PROJECT_NAME=nwxraybot COMPOSE_PROJECT_NAME=nwxraybot

View File

@@ -1,6 +1,5 @@
when: when:
- branch: [master, deploy] - branch: [master, deploy]
event: [push, manual]
steps: steps:
- name: build - name: build
image: docker:latest image: docker:latest
@@ -24,6 +23,7 @@ steps:
environment: environment:
ENV_FILE: ENV_FILE:
from_secret: ENV_FILE from_secret: ENV_FILE
depends_on: [build] depends_on: build
when: when:
- branch: deploy
- event: manual - event: manual

View File

@@ -24,7 +24,7 @@ services:
ports: ports:
- "127.0.0.1:${POSTGRES_PORT}:5432" - "127.0.0.1:${POSTGRES_PORT}:5432"
healthcheck: healthcheck:
test: ["CMD", "pg_isready"] test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 10 retries: 10

View File

@@ -1,3 +1,4 @@
from nwxraybot.bot import NwXrayBot from nwxraybot.bot import NwXrayBot
from nwxraybot.config import Settings from nwxraybot.config import Settings
from nwxraybot.utils import get_code, get_subscription_info from nwxraybot.utils import (get_code, get_correct_user_url,
get_subscription_info)

View File

@@ -10,6 +10,7 @@ class Settings(BaseSettings):
postgres_db: str = Field('db', env="POSTGRES_DB") postgres_db: str = Field('db', env="POSTGRES_DB")
postgres_host: str = Field('localhost', env="POSTGRES_HOST") postgres_host: str = Field('localhost', env="POSTGRES_HOST")
postgres_port: int = Field(5432, env="POSTGRES_PORT") postgres_port: int = Field(5432, env="POSTGRES_PORT")
domain_name: str = Field('localhost', env="DOMAIN_NAME")
@property @property
def postgres_url(self) -> str: def postgres_url(self) -> str:

View File

@@ -16,6 +16,7 @@ from nwxraybot.fsm import BroadcastStates
from nwxraybot.meta import Handler from nwxraybot.meta import Handler
from nwxraybot.middlewares import AdminMiddleware from nwxraybot.middlewares import AdminMiddleware
from nwxraybot.models import User from nwxraybot.models import User
from nwxraybot.utils import get_correct_user_url
class AdminHandler(Handler): class AdminHandler(Handler):
@@ -24,6 +25,17 @@ class AdminHandler(Handler):
self.router.message.middleware(AdminMiddleware()) self.router.message.middleware(AdminMiddleware())
help_text = """Список команда администратора:
`/adduser name url [01.01.1970 00:00]` - добавить пользователя
`/updateuser name 01.01.1970 00:00` - обновить информацию о пользователе
`/broadcast` - рассылка (пошагово)
`/get_users` - список пользователей
"""
@self.router.message(Command('ahelp'))
async def help(message: Message):
await message.reply(help_text, parse_mode=ParseMode.MARKDOWN)
@self.router.message(Command('adduser')) @self.router.message(Command('adduser'))
async def add_user(message: Message): async def add_user(message: Message):
mask = r"^(?P<name>[a-zA-Z0-9]+)\s(?P<url>vless://[^\s]+)($|\s(?P<date>[0-9]{2}\.[0-9]{2}\.[0-9]{4})\s(?P<time>[0-9]{2}\:[0-9]{2})$)" mask = r"^(?P<name>[a-zA-Z0-9]+)\s(?P<url>vless://[^\s]+)($|\s(?P<date>[0-9]{2}\.[0-9]{2}\.[0-9]{4})\s(?P<time>[0-9]{2}\:[0-9]{2})$)"
@@ -33,13 +45,15 @@ class AdminHandler(Handler):
await message.reply('Вы ввели команду в неверном формате. Вводите в формате:\n``` /adduser name vless://.... 01.01.1970 00:00```', parse_mode=ParseMode.MARKDOWN) await message.reply('Вы ввели команду в неверном формате. Вводите в формате:\n``` /adduser name vless://.... 01.01.1970 00:00```', parse_mode=ParseMode.MARKDOWN)
return return
user_dict = match.groupdict() user_dict = match.groupdict()
url = user_dict['url']
url = get_correct_user_url(url)
date = None date = None
if user_dict['date']: if user_dict['date']:
date = datetime.strptime(f"{user_dict['date']} { date = datetime.strptime(f"{user_dict['date']} {
user_dict['time']}", "%d.%m.%Y %H:%M") user_dict['time']}", "%d.%m.%Y %H:%M")
code = get_code() code = get_code()
new_user = User( new_user = User(
name=user_dict['name'], url=user_dict['url'], time=date, code=code) name=user_dict['name'], url=url, time=date, code=code)
new_user.save() new_user.save()
await message.answer(f'Пользователь создан. Вот его ссылка для доступа:\n`https://t.me/nwproxybot?start={code}`', parse_mode=ParseMode.MARKDOWN) await message.answer(f'Пользователь создан. Вот его ссылка для доступа:\n`https://t.me/nwproxybot?start={code}`', parse_mode=ParseMode.MARKDOWN)
@@ -59,6 +73,24 @@ class AdminHandler(Handler):
query.execute() query.execute()
await message.answer('Информация о пользователе обновлена.') await message.answer('Информация о пользователе обновлена.')
@self.router.message(Command('get_users'))
async def get_users(message: Message):
def get_user_info(user: User) -> str:
date_str = "" if user.time is None else f'До: {
user.time.strftime("%d.%m.%Y %H:%M")} МСК\n'
return f"Информация о пользователе `{user.name}`:\n{date_str}Ссылка: `{user.url}`\ncode: `{user.code if user.code else 'None'}`\n\n"
users = User.select()
res = ""
for user in users:
res += get_user_info(user)
try:
await message.answer(res, parse_mode=ParseMode.MARKDOWN)
except Exception as e:
await message.answer(f"Error while getting users: {e}")
logging.error(f"Error while getting user {
user.telegram_id}: {e}")
@self.router.message(Command('broadcast')) @self.router.message(Command('broadcast'))
async def start_broadcast(message: Message, state: FSMContext): async def start_broadcast(message: Message, state: FSMContext):
await message.answer('Отправьте для рассылки') await message.answer('Отправьте для рассылки')

View File

@@ -30,14 +30,20 @@ class HelloHandler(Handler):
user: Optional[User] = None user: Optional[User] = None
if len(data) == 2: if len(data) == 2:
code = data[1] code = data[1]
query = User.update(telegram_id=None).where( user = User.select().where(
User.telegram_id == message.from_user.id) User.telegram_id == message.from_user.id).first()
query.execute() if user is not None:
await message.answer(f"Приветствуем в боте NwXray! Здесь вы сможете получить информацию о своем подключении к NwXray.\n\n{get_subscription_info(message.from_user.id)}",
reply_markup=self.__non_admin_main_menu(), parse_mode=ParseMode.MARKDOWN)
return
user = User.select().where( user = User.select().where(
User.code == code).first() User.code == code).first()
if user is None: if user is None:
await message.answer('Пользователь не найден, обратитесь к администратору за ссылкой!') await message.answer('Пользователь не найден, обратитесь к администратору за ссылкой!')
return return
query = User.update(telegram_id=None).where(
User.telegram_id == message.from_user.id)
query.execute()
user.telegram_id = message.from_user.id user.telegram_id = message.from_user.id
user.code = '' user.code = ''
user.save() user.save()

View File

@@ -2,9 +2,13 @@ import logging
from datetime import datetime from datetime import datetime
from secrets import token_urlsafe from secrets import token_urlsafe
from typing import Optional from typing import Optional
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
from nwxraybot import Settings
from nwxraybot.models import User from nwxraybot.models import User
config = Settings()
def get_subscription_info(telegram_id: str) -> str: def get_subscription_info(telegram_id: str) -> str:
user: User = User.select().where(User.telegram_id == telegram_id).first() user: User = User.select().where(User.telegram_id == telegram_id).first()
@@ -20,3 +24,16 @@ def get_subscription_info(telegram_id: str) -> str:
def get_code(length: int = 10) -> str: def get_code(length: int = 10) -> str:
return token_urlsafe(length)[:length] return token_urlsafe(length)[:length]
def get_correct_user_url(url: str) -> str:
parsed_url = urlparse(url)
query = parse_qs(parsed_url.query)
query['fp'] = 'chrome'
query['alpn'] = 'h2,h3'
query['packetEncoding'] = 'xudp'
query['security'] = 'tls'
new_query = urlencode(query, doseq=True)
new_url = urlunparse((parsed_url.scheme, parsed_url.netloc.replace("127.0.0.1:1234", f"{
config.domain_name}:443"), parsed_url.path, parsed_url.params, new_query, parsed_url.fragment))
return new_url