Compare commits

...

51 Commits
devel ... main

Author SHA1 Message Date
korotkov 78c23227e4 ewq
BOT_open_sesam/pipeline/head This commit looks good Details
2026-04-01 14:49:27 +05:00
korotkov 7b6d298d68 delete id-rsa
BOT_open_sesam/pipeline/head This commit looks good Details
2026-04-01 14:32:43 +05:00
korotkov 1b9649fbc0 q
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 14:27:48 +05:00
korotkov 193390ecb8 q
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 14:23:12 +05:00
korotkov 55ebed483f q
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 14:15:31 +05:00
korotkov 00846331f3 q
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 14:14:09 +05:00
korotkov 0adc48209d q
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 14:09:47 +05:00
korotkov d7403bb266 q
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 13:58:53 +05:00
korotkov 2b43532a8f qa
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 13:37:15 +05:00
korotkov 54afe573c1 zxc
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 13:28:53 +05:00
korotkov b240bc1bad q
BOT_open_sesam/pipeline/head This commit looks good Details
2026-04-01 13:08:56 +05:00
korotkov be996f942b qwe
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 13:00:14 +05:00
korotkov dda0e8ebb7 q
BOT_open_sesam/pipeline/head This commit looks good Details
2026-04-01 12:44:31 +05:00
korotkov dd78a4a55b qq
BOT_open_sesam/pipeline/head This commit looks good Details
2026-04-01 11:39:35 +05:00
korotkov f61e8e1588 qwe
BOT_open_sesam/pipeline/head This commit looks good Details
2026-04-01 11:38:28 +05:00
korotkov 0168036fab qaz
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 11:35:42 +05:00
korotkov 24fdc1014c qwe
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 11:34:39 +05:00
korotkov 5f59c4df92 qw
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 11:26:27 +05:00
korotkov 63ab836f1a q
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 11:20:59 +05:00
korotkov d57a88321e add sock5
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-04-01 11:11:11 +05:00
dl 5b0eafe695 Docker automate in Jenkinsfile (fix 8)
TEST_JD/pipeline/head There was a failure building this commit Details
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:50:00 +05:00
dl 35dca573c9 Docker automate in Jenkinsfile (fix 7)
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:44:53 +05:00
dl dc021f6802 Docker automate in Jenkinsfile (fix 6)
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:43:05 +05:00
dl 8f8b5d18d1 Docker automate in Jenkinsfile (fix 5)
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:34:20 +05:00
dl 7ad9b91a0a Docker automate in Jenkinsfile (fix 4)
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:26:33 +05:00
dl 469ab42324 Docker automate in Jenkinsfile (fix 3)
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:21:50 +05:00
dl 3547f307b1 Docker automate in Jenkinsfile (fix 2)
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:20:25 +05:00
dl 8edd79be72 Docker automate in Jenkinsfile (fix 1)
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:13:28 +05:00
dl e4f596dd82 Docker automate in Jenkinsfile
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2026-02-04 12:06:58 +05:00
korotkov b2363215b8 q
BOT_open_sesam/pipeline/head This commit looks good Details
2026-02-03 16:04:02 +05:00
korotkov 0c060c79cb change .env env variable
BOT_open_sesam/pipeline/head This commit looks good Details
2026-02-03 15:59:00 +05:00
dl 0871c0e251 Python img janged in docker
BOT_open_sesam/pipeline/head This commit looks good Details
TEST_JD/pipeline/head There was a failure building this commit Details
2025-05-30 17:17:29 +05:00
dl acc3c9916c .env return
BOT_open_sesam/pipeline/head This commit looks good Details
2025-05-30 15:49:58 +05:00
dl 18629ddffe ...
BOT_open_sesam/pipeline/head This commit looks good Details
2025-05-30 15:33:25 +05:00
dl a9f3766d9f ...
BOT_open_sesam/pipeline/head This commit looks good Details
2025-05-30 11:29:40 +05:00
dl ae11fb7588 ...
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2025-05-30 11:14:19 +05:00
dl 6ac9f59716 ... 2025-05-30 11:11:51 +05:00
dl c49502dff3 New Jenkins Pipline 2025-05-30 10:53:23 +05:00
dl 14ed4ad11f Docker .env should be right one
BOT_open_sesam/pipeline/head There was a failure building this commit Details
#4
2025-05-30 10:42:42 +05:00
dl d263966ded docker .env
BOT_open_sesam/pipeline/head There was a failure building this commit Details
#3
2025-05-30 10:38:30 +05:00
dl d159c0f0e4 docker .env
BOT_open_sesam/pipeline/head There was a failure building this commit Details
Попытка создать .env #2
2025-05-30 10:35:51 +05:00
dl dc5c35906f dockerfile change
BOT_open_sesam/pipeline/head There was a failure building this commit Details
Попытка нормально создать .env #1
2025-05-30 10:28:42 +05:00
dl 73c2f1f440 DEBUG
BOT_open_sesam/pipeline/head This commit looks good Details
Пытаемся поймать передаваемый номер в логах
2025-05-30 10:17:50 +05:00
dl 93e5303ee6 init_config.py was deleted
BOT_open_sesam/pipeline/head This commit looks good Details
2025-05-29 17:58:36 +05:00
dl 65e95b8eb2 requests modul was removed
BOT_open_sesam/pipeline/head This commit looks good Details
Удален модуль requests, обновлен README
2025-05-29 17:54:23 +05:00
dl 3b5ac43c30 JSON was remove
BOT_open_sesam/pipeline/head This commit looks good Details
Теперь вся информация получается по запросам к API, локально хранится
только BOT_TOKEN в файле .env
2025-05-29 17:10:51 +05:00
dl e319ea2e73 Human readable locks name
BOT_open_sesam/pipeline/head This commit looks good Details
- Теперь названия замков (дверей) на кнопках, отображаемых
пользователям, человекочитаемы, все соотношения заполняются в
bot_config.json
- Поправлен docker-compose.yml, размер логов ограничен тремя файлами по
10M каждый
2025-05-29 13:28:34 +05:00
dl 8582bdca9e API requests instead of local json
BOT_open_sesam/pipeline/head This commit looks good Details
2025-05-29 11:29:48 +05:00
dl a869a07eba Jenkins file was patched #2
BOT_open_sesam/pipeline/head This commit looks good Details
2025-05-16 16:52:03 +05:00
dl bc99718123 Jenkins file was patched
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2025-05-16 16:48:51 +05:00
dl 83aac7cd51 Jenkins file was added for test branch
BOT_open_sesam/pipeline/head There was a failure building this commit Details
2025-05-16 16:44:00 +05:00
17 changed files with 364 additions and 208 deletions

117
Jenkinsfile vendored 100644
View File

@ -0,0 +1,117 @@
pipeline {
environment {
REGISTRY_URL = "https://proxy.docker.dataekb.ru/local_cache"
REGISTRY = "proxy.docker.dataekb.ru/local_cache"
BOT_IMAGE_NAME = "bot_open_sesam"
TUNNEL_IMAGE_NAME = "tunnel_open_sesam"
BOT_IMAGE_TAG = "latest"
TUNNEL_IMAGE_TAG = "latest"
IMAGE_TAG = "${env.BUILD_NUMBER}"
}
agent { label 'agent_smith'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds() // Prevent cleanup conflicts
timeout(time: 30, unit: 'MINUTES')
}
stages {
stage ('build bot image and push') {
steps {
script {
docker.withRegistry("${REGISTRY_URL}", 'jenkins_harbor') {
def BotImage = docker.build(
"${REGISTRY}/${BOT_IMAGE_NAME}:${env.BUILD_NUMBER}",
"--label build_number=${IMAGE_TAG} " +
"--label git_commit=${env.GIT_COMMIT} " +
"."
)
BotImage.push()
}
}
}
}
stage ('build tunnel image and push') {
steps {
dir('tunnel'){
script {
docker.withRegistry("${REGISTRY_URL}", 'jenkins_harbor') {
def TunnelImage = docker.build(
"${REGISTRY}/${TUNNEL_IMAGE_NAME}:${env.BUILD_NUMBER}",
"--label build_number=${IMAGE_TAG} " +
"--label git_commit=${env.GIT_COMMIT} " +
"."
)
TunnelImage.push()
}
}
}
}
}
stage ('clear after build and push') {
steps {
script {
sh "docker image prune --filter label=stage=builder"
}
}
}
stage("Deploy") {
steps {
script {
sh '''
export BOT_IMAGE_TAG=${IMAGE_TAG}
export TUNNEL_IMAGE_TAG=${IMAGE_TAG}
# Pull new images explicitly
docker-compose pull bot_open_sesam tunnel_open_sesam
# Deploy with specific project name
docker-compose up -d --remove-orphans
'''
}
}
}
}
post {
success {
echo "Оба образа собраны и задеплоены успешно"
}
failure {
echo "Ошибка сборки!"
}
}
}
// Old_version
// pipeline {
// agent { label 'agent_smith' }
//
// stages {
// stage('Stop and Remove Existing Container') {
// steps {
// sh '''
// docker stop open_sesam || true
// docker rm open_sesam || true
// docker-compose down || true
// '''
// }
// }
//
// stage('Build and Run Container') {
// steps {
// sh '''
// docker-compose up --build -d
// '''
// }
// }
// }
// }

View File

@ -1,22 +1,14 @@
# Usage # USAGE
Создать файл .env в корневой дирректории проекта, объявить и присвоить значения переменным: Для развертывания бота необходимо внести свой токен в файл
.env, запись должна иметь вид: BOT_TOKEN=your_token.
TOKEN= Для того чтобы пользователь мог пользоваться ботом, ему
LOCK_IP= необходимо поделиться номером телефона нажав на соответствующую
CARD_ID= кнопку (номер не будет виден другим пользователям, используется
AUTH_API= отправляется единожды для сверки с данными в БД).
, где # TODO
TOKEN - токен телеграм для взаимодействия с ботом
LOCK_IP - ip адресс замка
CARD_ID - уникальный номер ключ-карты
AUTH_API - уникальный набор символов, для взаимодействия с API замка
Дополнительные подробности можно найти в instruction_http_api_v5.pdf и исходном коде программы (см. main.py)
## TODO
- [x] Написать базовый функционал - [x] Написать базовый функционал
~~- [ ] Создать соотношение типа "id_пользователя: досутп_к_замку:"~~ ~~- [ ] Создать соотношение типа "id_пользователя: досутп_к_замку:"~~
@ -24,4 +16,8 @@ AUTH_API - уникальный набор символов, для взаимо
- [x] Удалить модуль getenv - [x] Удалить модуль getenv
- [x] Создать и парсить json с информацией о пользователях и их номерах - [x] Создать и парсить json с информацией о пользователях и их номерах
~~- [ ] Проверять, является ли пользователь администратором, если является, выводить дополнительную кнопку, предлагающую добавить номер в БД~~ ~~- [ ] Проверять, является ли пользователь администратором, если является, выводить дополнительную кнопку, предлагающую добавить номер в БД~~
- [ ] Сделать логирование о том, что кто-то открыл дверь в конкретное время - [x] Изменить json хранящийся локально на запросы к API
- [x] Изменить названия комнта в локальном json
- [x] Ограничить логи внутри докера
- [x] Избавиться от json файла, переместить токен в .env файл, информацию от замков получать через запрос по API
- [x] Избавиться от модуля requests

60
auth.py
View File

@ -1,37 +1,47 @@
from config import config
import re import re
import aiohttp
ALLOWED_PHONE_NUMBERS = list(config.get("users", {}).keys())
AUTHORIZED_USERS = {} AUTHORIZED_USERS = {}
def check_user_auth(phone: str) -> bool:
return phone in ALLOWED_PHONE_NUMBERS
def normalize_phone(phone: str) -> str: def normalize_phone(phone: str) -> str:
phone = phone.strip() digits = re.sub(r"\D", "", phone)
phone = re.sub(r"[^\d+]", "", phone) if len(digits) > 10:
if not phone.startswith("+"): digits = digits[-10:]
phone = "+" + phone return digits
return phone
def authorize_user(user_id: int, phone: str) -> bool: async def authorize_user(user_id: int, phone: str) -> bool:
normalized_phone = normalize_phone(phone) normalized = normalize_phone(phone)
if normalized_phone in ALLOWED_PHONE_NUMBERS: api_url = "https://papi.dataekb.ru/check_access"
AUTHORIZED_USERS[user_id] = normalized_phone print("DEBUG: phone (var normalized) is ", normalized)
# if check_user_auth(phone): async with aiohttp.ClientSession() as session:
# AUTHORIZED_USERS[user_id] = phone try:
print(f"{user_id} авторизован с номером: {normalized_phone}") async with session.get(api_url, params={"tel": normalized}) as response:
return True if response.status != 200:
else: print(f"Ошибка запроса к API: статус {response.status}")
print(
f"Пользователь {user_id} пытался авторизоваться с номером {normalized_phone}"
)
return False return False
data = await response.json()
except Exception as e:
print(f"Исключение при запросе к API: {str(e)}")
return False
if data.get("response") != "1":
print(f"Доступ запрещён для номера: {normalized}")
return False
zone_str = data["data"].get("zone", "")
zones = [zone.strip() for zone in zone_str.split(";") if zone.strip()]
card_code = data["data"].get("card-code", "")
if not zones or not card_code:
print("Некорректный ответ API: отсутствуют зоны или код карты.")
return False
AUTHORIZED_USERS[user_id] = {"tel": normalized, "card": card_code, "zones": zones}
print(
f"{user_id} авторизован с номером: {normalized}, карта: {card_code}, зоны: {zones}"
)
return True
def is_user_auth(user_id: int) -> bool: def is_user_auth(user_id: int) -> bool:

View File

@ -1,23 +0,0 @@
{
"bot_token": "",
"locks": {
"Room418": {
"ip": "10.9.1.26",
"auth_api": "73B15D12"
},
"Floor4": {
"ip": "10.9.1.27",
"auth_api": "F901C40A"
}
},
"users": {
"+79000959392": {
"access_card": "000D001195DD",
"lock_id": ["Room418", "Floor4"]
},
"+79221716513": {
"access_card": "",
"lock_id": ["Room418", "Floor4"]
}
}
}

View File

@ -1,11 +1,20 @@
import json def load_env(path=".env") -> dict:
env = {}
CONFIG_PATH = "bot_config.json" try:
def load_config(path: str = CONFIG_PATH) -> dict:
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
return json.load(f) for line in f:
line = line.strip()
# Пропускаем пустые строки и строки-комментарии
if not line or line.startswith("#"):
continue
if "=" in line:
key, value = line.split("=", 1)
env[key.strip()] = value.strip()
except FileNotFoundError:
raise Exception(
f"Файл {path} не найден. Проверьте наличие файла .env в корне проекта."
)
return env
config = load_config() config = load_env()

View File

@ -1,10 +1,52 @@
version: '3' version: '3'
services: services:
open_sesam: bot_open_sesam:
container_name: open_sesam container_name: bot_open_sesam
build: . image: proxy.docker.dataekb.ru/local_cache/bot_open_sesam:${BOT_IMAGE_TAG:-latest}
stdin_open: true stdin_open: true
tty: true tty: true
volumes: env_file:
- ./.env:/bot/open_sesam/.env - ./.env
restart: always restart: always
networks:
- bot_open_sesam_network
depends_on:
tunnel_open_sesam:
condition: service_healthy
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
tunnel_open_sesam:
image: proxy.docker.dataekb.ru/local_cache/tunnel_open_sesam:${BOT_IMAGE_TAG:-latest}
container_name: tunnel_open_sesam
env_file:
- ./.env
# environment:
# - SSH_HOST=91.194.84.91
# - SSH_PORT=22
# - SSH_USER=root
volumes:
# SSH ключ
- .ssh/id_rsa:/root/.ssh/id_rsa:ro
networks:
- bot_open_sesam_network
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "1080"]
interval: 10s
timeout: 5s
retries: 5
networks:
bot_open_sesam_network:
driver: bridge
ipam:
config:
- subnet: 172.30.10.0/29
gateway: 172.30.10.1

View File

@ -1,4 +1,4 @@
FROM python:3.13.3-slim FROM python:3.11-slim-bookworm
WORKDIR /bot/open_sesam WORKDIR /bot/open_sesam

View File

@ -2,35 +2,30 @@ from aiogram import Dispatcher, F
from aiogram.types import Message from aiogram.types import Message
from keyboard import get_locks_keyboard, get_contact_keyboard from keyboard import get_locks_keyboard, get_contact_keyboard
from auth import authorize_user, normalize_phone from auth import authorize_user, AUTHORIZED_USERS
from config import config
def register_contact_handler(dp: Dispatcher): def register_contact_handler(dp: Dispatcher):
@dp.message(F.contact) @dp.message(F.contact)
async def contact_handler(msg: Message): async def contact_handler(msg: Message):
user_id = msg.from_user.id user_id = msg.from_user.id
if msg.contact is None: if msg.contact is None:
await msg.answer("Ошибка: номер телефона не получен") await msg.answer("Ошибка: номер телефона не получен")
return return
phone = normalize_phone(msg.contact.phone_number) phone = msg.contact.phone_number
if not await authorize_user(user_id, phone):
if not authorize_user(user_id, phone): print("DEBUG: phone in contact_handler is ", phone)
await msg.answer("Доступ запрещен, номер не идентифицирован") await msg.answer("Доступ запрещён, номер не идентифицирован")
return return
user_conf = config.get("users", {}).get(phone) user_data = AUTHORIZED_USERS.get(user_id)
print(f"***user_conf для {phone}: {user_conf}***") if not user_data:
if not user_conf: await msg.answer("Ошибка авторизации.")
await msg.answer("Пользователь не опознан")
return return
allowed_locks = user_conf.get("lock_id", []) allowed_zones = user_data["zones"]
print(f"***allowed_locks = {allowed_locks}***") reply_markup = await get_locks_keyboard(allowed_zones)
reply_markup = get_locks_keyboard(allowed_locks)
await msg.answer( await msg.answer(
"Номер подтвержден. Выберите дверь для открытия", reply_markup=reply_markup "Номер подтверждён. Выберите дверь для открытия", reply_markup=reply_markup
) )

View File

@ -1,72 +1,44 @@
import asyncio import aiohttp
import requests
from aiogram import Dispatcher from aiogram import Dispatcher
from aiogram.types import Message from aiogram.types import Message
from auth import AUTHORIZED_USERS, is_user_auth from auth import AUTHORIZED_USERS, is_user_auth
from config import config from locks_api import get_lock_by_label
def register_open_door_handler(dp: Dispatcher): def register_open_door_handler(dp: Dispatcher):
@dp.message() @dp.message()
async def open_door_handler(msg: Message): async def open_door_handler(msg: Message):
print(
f"DEBUG: Получено сообщение от пользователя {msg.from_user.id}: '{msg.text}'"
)
user_id = msg.from_user.id user_id = msg.from_user.id
if not is_user_auth(user_id): if not is_user_auth(user_id):
await msg.answer( await msg.answer("Доступ запрещён. Предоставьте номер телефона.")
"Доступ запрщен. Необходимо предоставить свой номер телефона."
)
else:
print("OK")
phone = AUTHORIZED_USERS.get(user_id)
print(AUTHORIZED_USERS)
print(f"***user_id={user_id},phone={phone}***")
user_conf = config.get("users", {}).get(phone)
print(f"***phone={phone}, user_conf={user_conf}***")
# allowed_locks = user_conf.get("locks", [])
# print(f"***allowed_locks={allowed_locks}***")
# if msg.text not in allowed_locks:
# print("**********************")
# print(f"***{allowed_locks}***")
# print(f"***{msg.text}***")
# print("**********************")
# return
lock_conf = config.get("locks", {}).get(msg.text)
if not lock_conf:
await msg.answer("Информации по замку не найдено")
return return
url = f"http://{lock_conf['ip']}/cgi-bin/ext" user_data = AUTHORIZED_USERS.get(user_id)
auth_info = ("ext", lock_conf["auth_api"]) if not user_data:
payload = f"CARD={user_conf['access_card']}&DIR=0" await msg.answer("Ошибка авторизации.")
return
lock_info = await get_lock_by_label(msg.text)
if not lock_info:
await msg.answer("Информация по замку не найдена.")
return
url = f"http://{lock_info['ip']}/cgi-bin/ext"
auth_info = aiohttp.BasicAuth(login="ext", password=lock_info["code"])
payload = f"CARD={user_data['card']}&DIR=0"
headers = {"Content-Type": "application/x-www-form-urlencoded"} headers = {"Content-Type": "application/x-www-form-urlencoded"}
try: try:
print( async with aiohttp.ClientSession() as session:
f"***DEBUG: Отправляю запрос к {url} c payload: {payload} и auth: {auth_info}" async with session.post(
) url, auth=auth_info, data=payload, headers=headers, timeout=5
response = await asyncio.to_thread( ) as response:
requests.post, if response.status == 200:
url,
auth=auth_info,
data=payload,
headers=headers,
timeout=5,
)
print(
f"DEBUG: URL: {url}, status: {response.status_code}, response: {response.text}"
)
if response.status_code == 200:
await msg.answer("Открыто") await msg.answer("Открыто")
else: else:
await msg.answer( await msg.answer(
f"Ошибка при открытии замка. Код ошибки: {response.status_code}" f"Ошибка при открытии замка. Код ошибки: {response.status}"
) )
except Exception as e: except Exception as e:
await msg.answer(f"Исключение: {str(e)}") await msg.answer(f"Исключение: {str(e)}")

View File

@ -4,7 +4,6 @@ from aiogram.filters import CommandStart
from keyboard import get_contact_keyboard, get_locks_keyboard from keyboard import get_contact_keyboard, get_locks_keyboard
from auth import is_user_auth, AUTHORIZED_USERS from auth import is_user_auth, AUTHORIZED_USERS
from config import config
def register_start_handler(dp: Dispatcher): def register_start_handler(dp: Dispatcher):
@ -12,25 +11,16 @@ def register_start_handler(dp: Dispatcher):
async def command_start_handler(msg: Message): async def command_start_handler(msg: Message):
user_id = msg.from_user.id user_id = msg.from_user.id
if is_user_auth(user_id): if is_user_auth(user_id):
phone = AUTHORIZED_USERS.get(user_id) user_data = AUTHORIZED_USERS.get(user_id)
if not phone: if not user_data:
await msg.answer("Номер не найден") await msg.answer("Ошибка авторизации.")
return return
allowed_zones = user_data["zones"]
user_conf = config.get("user", {}).get(phone) reply_markup = await get_locks_keyboard(allowed_zones)
if not user_conf: await msg.answer("Авторизация прошла успешно", reply_markup=reply_markup)
await msg.answer("Пользователь не найден в конфигурации")
return
allowed_locks = user_conf.get("locks_id", [])
reply_markup = get_locks_keyboard(allowed_locks)
await msg.answer(
"Авторизация прошла успешно",
reply_markup=reply_markup,
)
else: else:
reply_markup = get_contact_keyboard() reply_markup = get_contact_keyboard()
await msg.answer( await msg.answer(
"Для пользования ботом, предоставьте номер телефона", "Для пользования ботом предоставьте номер телефона",
reply_markup=reply_markup, reply_markup=reply_markup,
) )

View File

@ -1,30 +0,0 @@
import os
from dotenv import load_dotenv
ENV_FILE = ".env"
def check_env_file():
return os.path.exists(ENV_FILE)
def create_env_file():
print("Файл .env отсутствует и будет создан автоматически.")
token = input("Введите TOKEN: ")
lock_ip = input("Введите LOCK_IP: ")
card_id = input("Введите CARD_ID: ")
auth_api = input("Введите AUTH_API: ")
with open(ENV_FILE, "w") as f:
f.write(f"TOKEN={token}\n")
f.write(f"LOCK_IP={lock_ip}\n")
f.write(f"CARD_ID={card_id}\n")
f.write(f"AUTH_API={auth_api}\n")
print("Файл .env создан. Происходит запуск приложения")
def load_env():
load_dotenv()

View File

@ -1,12 +1,15 @@
from aiogram.utils.keyboard import ReplyKeyboardBuilder from aiogram.utils.keyboard import ReplyKeyboardBuilder
from aiogram.types import KeyboardButton from aiogram.types import KeyboardButton
from locks_api import fetch_locks
def get_locks_keyboard(allowed_locks: list): async def get_locks_keyboard(allowed_locks: list):
print(f"DEBUG: allowed_locks = {allowed_locks}")
kb = ReplyKeyboardBuilder() kb = ReplyKeyboardBuilder()
for lock in allowed_locks: locks = await fetch_locks()
kb.button(text=lock) for zone in allowed_locks:
lock_info = locks.get(zone)
btn_text = lock_info["name"] if lock_info and "name" in lock_info else zone
kb.button(text=btn_text)
return kb.as_markup(resize_keyboard=True) return kb.as_markup(resize_keyboard=True)

35
locks_api.py 100644
View File

@ -0,0 +1,35 @@
import aiohttp
import json
LOCKS_API_URL = "https://papi.dataekb.ru/get_pacs"
async def fetch_locks() -> dict:
async with aiohttp.ClientSession() as session:
try:
async with session.get(LOCKS_API_URL) as response:
if response.status != 200:
print(f"Ошибка при получении данных замков: {response.status}")
return {}
text = await response.text()
except Exception as e:
print(f"Исключение при запросе данных замков: {str(e)}")
return {}
try:
data = json.loads(text)
if not data or not isinstance(data, list):
print("Неверный формат данных о замках, ожидался список.")
return {}
locks_dict = data[0]
return locks_dict
except Exception as e:
print(f"Ошибка при разборе данных замков: {str(e)}")
return {}
async def get_lock_by_label(label: str) -> dict:
locks = await fetch_locks()
for lock_id, details in locks.items():
if label == lock_id or details.get("name") == label:
return details
return {}

15
main.py
View File

@ -1,23 +1,32 @@
import os
import asyncio import asyncio
import logging import logging
import sys import sys
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.enums import ParseMode from aiogram.enums import ParseMode
from aiogram.client.default import DefaultBotProperties from aiogram.client.default import DefaultBotProperties
from aiogram.client.session.aiohttp import AiohttpSession
from handlers import register_all_handlers from handlers import register_all_handlers
from config import config from config import config
# BOT_TOKEN = config["BOT_TOKEN"]
BOT_TOKEN = config["bot_token"] BOT_TOKEN=os.environ.get('BOT_TOKEN')
PROXY_URL = os.environ.get('PROXY_URL')
dp = Dispatcher() dp = Dispatcher()
register_all_handlers(dp) register_all_handlers(dp)
session = AiohttpSession(proxy=PROXY_URL)
async def main() -> None: async def main() -> None:
bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) bot = Bot(
token=BOT_TOKEN
, session=session
, default=DefaultBotProperties(parse_mode=ParseMode.HTML)
)
await dp.start_polling(bot) await dp.start_polling(bot)

View File

@ -14,8 +14,8 @@ multidict==6.4.3
propcache==0.3.1 propcache==0.3.1
pydantic==2.11.3 pydantic==2.11.3
pydantic_core==2.33.1 pydantic_core==2.33.1
requests==2.32.3
typing-inspection==0.4.0 typing-inspection==0.4.0
typing_extensions==4.13.2 typing_extensions==4.13.2
urllib3==2.4.0 urllib3==2.4.0
yarl==1.20.0 yarl==1.20.0
aiohttp-socks

15
tunnel/Dockerfile 100644
View File

@ -0,0 +1,15 @@
# tunnel/Dockerfile
FROM alpine:3.19
RUN apk add --no-cache \
autossh \
openssh-client
# Директория для SSH ключей
RUN mkdir -p /root/.ssh && \
chmod 700 /root/.ssh
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -0,0 +1,16 @@
#!/bin/sh
ssh-keyscan -p ${SSH_PORT:-22} ${SSH_HOST} >> /root/.ssh/known_hosts 2>/dev/null
exec autossh \
-M 0 \
-N \
-D 0.0.0.0:1080 \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-o "StrictHostKeyChecking=no" \
-o "ConnectTimeout=10" \
-p ${SSH_PORT:-22} \
-i /root/.ssh/id_rsa \
${SSH_USER}@${SSH_HOST}