Edit on GitHub

src.handlers.wg_service

Действия с конфигурациями WireGuard

  1"""Действия с конфигурациями WireGuard"""
  2
  3import logging
  4import os
  5from contextlib import suppress
  6from typing import Union
  7
  8from aiogram import Bot, F, Router
  9from aiogram.exceptions import TelegramBadRequest
 10from aiogram.filters.command import Command
 11from aiogram.types import CallbackQuery, FSInputFile, Message
 12from pytils.numeral import get_plural
 13
 14import text
 15from core import exceptions as exc
 16from core.config import settings
 17from core.err import bot_except
 18from core.metric import async_speed_metric
 19from db import utils
 20from db.models import FreezeSteps, UserActivity, UserData, WgConfig
 21from handlers.utils import find_config, find_user
 22from kb import get_config_keyboard, remove_config, static_pay_button, why_freezed_button
 23from wg.utils import WgServerTools
 24
 25logger = logging.getLogger()
 26router = Router()
 27router.message.filter(F.chat.type == "private")
 28
 29
 30@router.message(Command("me"))
 31@router.message(Command("config"))
 32@router.message(F.text.lower().in_(text.me))
 33@router.callback_query(F.data == "user_configurations")
 34@async_speed_metric
 35@bot_except
 36async def post_user_data(trigger: Union[Message, CallbackQuery]):
 37    """Отправляет данные о конфигурациях пользователя.
 38
 39    Args:
 40        trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
 41
 42    Если у пользователя есть конфигурации, отправляет их список. Если конфигурации заморожены, отправляет сообщение об этом.
 43    Также информирует пользователя о максимальном количестве конфигураций для его тарифа.
 44    """
 45    user_data: UserData = await find_user(trigger, configs=True)
 46    if not user_data:
 47        return
 48    else:
 49        create_cfg_btn, create_output_cfg_btn = get_config_keyboard()
 50
 51        if user_data.configs:
 52            await getattr(trigger, "message", trigger).answer("Ваши конфигурации:")
 53
 54            user_data.configs.sort(key=lambda conf: conf.id)
 55            for i, config in enumerate(user_data.configs, 1):
 56                if config.user_private_key:
 57                    config_text = f"({i}/{settings.acceptable_config[user_data.stage]}) - Name: {config.name} | id: {config.user_private_key[:4]}"
 58
 59                    if config.freeze != FreezeSteps.no:
 60                        config_text = f'🥶<b>FREEZED</b>❄️\n<span class="tg-spoiler">{config_text}</span>'
 61
 62                        await getattr(trigger, "message", trigger).answer(
 63                            config_text, reply_markup=why_freezed_button
 64                        )
 65                    else:
 66                        await getattr(trigger, "message", trigger).answer(
 67                            config_text, reply_markup=create_output_cfg_btn
 68                        )
 69
 70        if user_data.active == UserActivity.active:
 71            cfg_number = settings.acceptable_config[user_data.stage] - len(
 72                user_data.configs
 73            )
 74
 75            if cfg_number <= 0:
 76                await getattr(trigger, "message", trigger).answer(
 77                    "Достигнуто максимальное количество конфигураций для данного тарифа.",
 78                    reply_markup=static_pay_button,
 79                )
 80            else:
 81                await getattr(trigger, "message", trigger).answer(
 82                    f"Вы можете создать еще {get_plural(cfg_number, 'конфигурацию, конфигурации, конфигураций')}",
 83                    reply_markup=create_cfg_btn,
 84                )
 85        elif user_data.active == UserActivity.inactive:
 86            await getattr(trigger, "message", trigger).answer(
 87                text.UNPAY, reply_markup=static_pay_button
 88            )
 89
 90
 91@router.message(Command("create"))
 92@router.callback_query(F.data == "create_configuration")
 93@async_speed_metric
 94@bot_except
 95async def create_config_data(trigger: Union[Message, CallbackQuery], bot: Bot):
 96    """Создает новую конфигурацию для пользователя.
 97
 98    Args:
 99        trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
100        bot (Bot): Экземпляр бота для выполнения действий.
101
102    Проверяет, может ли пользователь создать новую конфигурацию, и создает ее, если это возможно.
103    В случае ошибок отправляет соответствующие сообщения.
104    """
105    try:
106        user_data: UserData = await find_user(trigger, configs=True)
107        if not user_data:
108            return
109        elif user_data.active != UserActivity.active:
110            raise exc.PayError
111
112        elif len(user_data.configs) < settings.acceptable_config[user_data.stage]:
113            wg = WgServerTools()
114            conf = await wg.move_user(move="add", user_id=trigger.from_user.id)
115            config: WgConfig = await utils.add_wg_config(
116                conf, user_id=trigger.from_user.id
117            )
118
119        else:
120            raise exc.StagePayError
121
122    except exc.DatabaseError:
123        await trigger.answer(text=text.DB_ERROR, show_alert=True)
124    except exc.WireguardError:
125        await trigger.answer(text=text.WG_ERROR, show_alert=True)
126    except exc.StagePayError:
127        await getattr(trigger, "message", trigger).answer(
128            "Достигнуто максимальное количество конфигураций для данного тарифа.",
129            reply_markup=static_pay_button,
130        )
131        await bot.delete_message(
132            trigger.from_user.id, getattr(trigger, "message", trigger).message_id
133        )
134    except exc.PayError:
135        await getattr(trigger, "message", trigger).answer(
136            text.UNPAY, reply_markup=static_pay_button
137        )
138    else:
139        create_cfg_btn, create_output_cfg_btn = get_config_keyboard()
140
141        await trigger.answer(text="Конфигурация успешно создана", show_alert=True)
142        await getattr(trigger, "message", trigger).answer(
143            f"Конфигурация: {config.name} | id: {config.user_private_key[:4]}",
144            reply_markup=create_output_cfg_btn,
145        )
146
147        with suppress(TelegramBadRequest):
148            cfg_number = get_plural(
149                settings.acceptable_config[user_data.stage]
150                - len(user_data.configs)
151                - 1,
152                "конфигурацию, конфигурации, конфигураций",
153            )
154            await getattr(trigger, "message", trigger).edit_text(
155                f"Вы можете создать еще {cfg_number}", reply_markup=create_cfg_btn
156            )
157
158        if cfg_number.startswith("0"):
159            await bot.delete_message(
160                trigger.from_user.id, getattr(trigger, "message", trigger).message_id
161            )
162
163
164@router.callback_query(F.data == "remove_config_confirm")
165@bot_except
166async def remove_config_data_confirm(callback: CallbackQuery):
167    """Подтверждение удаления конфигурации.
168
169    Args:
170        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
171    """
172
173    with suppress(TelegramBadRequest):
174        await callback.message.edit_text(
175            "После удаления конфигурации вы больше не сможете подключаться по ней к VPN-серверу. "
176            "Для восстановления подключения вам необходимо будет создать новую конфигурацию и заменить ей старую в vpn-приложении"
177            f"\n\n? Вы уверены, что хотите удалить конфигурацию {callback.message.text}",
178            reply_markup=remove_config(),
179        )
180
181
182@router.callback_query(F.data == "remove_config_cancel")
183@bot_except
184async def remove_config_data_cancel(callback: CallbackQuery):
185    """Отмена удаления конфигурации.
186
187    Args:
188        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
189    """
190    _, create_output_cfg_btn = get_config_keyboard()
191
192    *_, old_cfg_message = callback.message.text.split("удалить конфигурацию ")
193
194    with suppress(TelegramBadRequest):
195        await callback.message.edit_text(
196            old_cfg_message, reply_markup=create_output_cfg_btn
197        )
198
199
200@router.callback_query(F.data == "remove_config")
201@async_speed_metric
202@bot_except
203async def remove_config_data(callback: CallbackQuery, bot: Bot):
204    """Удаляет конфигурацию с WG сервера, а затем из БД.
205
206    Args:
207        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
208    """
209
210    try:
211        user_config: WgConfig = await find_config(callback)
212        if not user_config:
213            return
214
215        wg = WgServerTools()
216        await wg.move_user(move="del", server_pubkey=user_config.server_public_key)
217
218        await utils.delete_wg_config(user_config)
219
220    except exc.DatabaseError:
221        await callback.answer(text=text.DB_ERROR, show_alert=True)
222    except exc.WireguardError:
223        await callback.answer(text=text.WG_ERROR, show_alert=True)
224    else:
225        await callback.answer(text="Конфигурация успешно удалена", show_alert=True)
226    finally:
227        await bot.delete_message(callback.from_user.id, callback.message.message_id)
228
229
230@router.callback_query(F.data == "create_conf_text")
231@async_speed_metric
232@bot_except
233async def get_config_text(callback: CallbackQuery, bot: Bot):
234    """Отправляет текст конфигурации пользователю.
235
236    Args:
237        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
238
239    Отправляет текст конфигурации, связанный с пользователем, в формате, удобном для чтения.
240    """
241    user_config: WgConfig = await find_config(callback)
242    if not user_config:
243        await bot.delete_message(callback.from_user.id, callback.message.message_id)
244        return
245
246    config = text.get_config_data(user_config)
247
248    await callback.message.answer("Конфигурация " + callback.message.text)
249    await callback.message.answer(f"<pre>{config}</pre>")
250
251
252@router.callback_query(F.data == "create_conf_file")
253@async_speed_metric
254@bot_except
255async def get_config_file(callback: CallbackQuery, bot: Bot):
256    """Отправляет файл конфигурации пользователю.
257
258    Args:
259        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
260
261    Создает файл конфигурации и отправляет его пользователю в виде документа.
262    """
263    user_config: WgConfig = await find_config(callback)
264    if not user_config:
265        await bot.delete_message(callback.from_user.id, callback.message.message_id)
266        return
267
268    config = text.get_config_data(user_config)
269    config_file = await text.create_config_file(config)
270
271    await callback.message.answer("Конфигурация " + callback.message.text)
272    await callback.message.answer_document(FSInputFile(config_file, "myVpn.conf"))
273    await callback.message.answer(
274        "‼️ Если у вас возникает ошибка: <b>Неправильное имя</b> "
275        "Проверьте имя файла: в нем не должно быть пробелов или спецсимволов (подчеркиваний, скобок ...), "
276        "длина имени файла должна быть не более 10 символов"
277    )
278    os.remove(config_file)
279
280
281@router.callback_query(F.data == "create_conf_qr")
282@async_speed_metric
283@bot_except
284async def get_config_qr(callback: CallbackQuery, bot: Bot):
285    """Отправляет QR-код конфигурации пользователю.
286
287    Args:
288        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
289
290    Создает QR-код конфигурации и отправляет его пользователю в виде изображения.
291    """
292    user_config: WgConfig = await find_config(callback)
293    if not user_config:
294        await bot.delete_message(callback.from_user.id, callback.message.message_id)
295        return
296
297    config = text.get_config_data(user_config)
298    config_qr = text.create_config_qr(config)
299
300    await callback.message.answer("Конфигурация " + callback.message.text)
301    await callback.message.answer_photo(
302        FSInputFile(config_qr, f"{user_config.name}_wg.conf")
303    )
304    os.remove(config_qr)
logger = <RootLogger root (DEBUG)>
router = <Router '0x7f23dac8e180'>
@router.message(Command('me'))
@router.message(Command('config'))
@router.message(F.text.lower().in_(text.me))
@router.callback_query(F.data == 'user_configurations')
@async_speed_metric
@bot_except
async def post_user_data( trigger: Union[aiogram.types.message.Message, aiogram.types.callback_query.CallbackQuery]):
31@router.message(Command("me"))
32@router.message(Command("config"))
33@router.message(F.text.lower().in_(text.me))
34@router.callback_query(F.data == "user_configurations")
35@async_speed_metric
36@bot_except
37async def post_user_data(trigger: Union[Message, CallbackQuery]):
38    """Отправляет данные о конфигурациях пользователя.
39
40    Args:
41        trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
42
43    Если у пользователя есть конфигурации, отправляет их список. Если конфигурации заморожены, отправляет сообщение об этом.
44    Также информирует пользователя о максимальном количестве конфигураций для его тарифа.
45    """
46    user_data: UserData = await find_user(trigger, configs=True)
47    if not user_data:
48        return
49    else:
50        create_cfg_btn, create_output_cfg_btn = get_config_keyboard()
51
52        if user_data.configs:
53            await getattr(trigger, "message", trigger).answer("Ваши конфигурации:")
54
55            user_data.configs.sort(key=lambda conf: conf.id)
56            for i, config in enumerate(user_data.configs, 1):
57                if config.user_private_key:
58                    config_text = f"({i}/{settings.acceptable_config[user_data.stage]}) - Name: {config.name} | id: {config.user_private_key[:4]}"
59
60                    if config.freeze != FreezeSteps.no:
61                        config_text = f'🥶<b>FREEZED</b>❄️\n<span class="tg-spoiler">{config_text}</span>'
62
63                        await getattr(trigger, "message", trigger).answer(
64                            config_text, reply_markup=why_freezed_button
65                        )
66                    else:
67                        await getattr(trigger, "message", trigger).answer(
68                            config_text, reply_markup=create_output_cfg_btn
69                        )
70
71        if user_data.active == UserActivity.active:
72            cfg_number = settings.acceptable_config[user_data.stage] - len(
73                user_data.configs
74            )
75
76            if cfg_number <= 0:
77                await getattr(trigger, "message", trigger).answer(
78                    "Достигнуто максимальное количество конфигураций для данного тарифа.",
79                    reply_markup=static_pay_button,
80                )
81            else:
82                await getattr(trigger, "message", trigger).answer(
83                    f"Вы можете создать еще {get_plural(cfg_number, 'конфигурацию, конфигурации, конфигураций')}",
84                    reply_markup=create_cfg_btn,
85                )
86        elif user_data.active == UserActivity.inactive:
87            await getattr(trigger, "message", trigger).answer(
88                text.UNPAY, reply_markup=static_pay_button
89            )

Отправляет данные о конфигурациях пользователя.

Arguments:
  • trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.

Если у пользователя есть конфигурации, отправляет их список. Если конфигурации заморожены, отправляет сообщение об этом. Также информирует пользователя о максимальном количестве конфигураций для его тарифа.

@router.message(Command('create'))
@router.callback_query(F.data == 'create_configuration')
@async_speed_metric
@bot_except
async def create_config_data( trigger: Union[aiogram.types.message.Message, aiogram.types.callback_query.CallbackQuery], bot: aiogram.client.bot.Bot):
 92@router.message(Command("create"))
 93@router.callback_query(F.data == "create_configuration")
 94@async_speed_metric
 95@bot_except
 96async def create_config_data(trigger: Union[Message, CallbackQuery], bot: Bot):
 97    """Создает новую конфигурацию для пользователя.
 98
 99    Args:
100        trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
101        bot (Bot): Экземпляр бота для выполнения действий.
102
103    Проверяет, может ли пользователь создать новую конфигурацию, и создает ее, если это возможно.
104    В случае ошибок отправляет соответствующие сообщения.
105    """
106    try:
107        user_data: UserData = await find_user(trigger, configs=True)
108        if not user_data:
109            return
110        elif user_data.active != UserActivity.active:
111            raise exc.PayError
112
113        elif len(user_data.configs) < settings.acceptable_config[user_data.stage]:
114            wg = WgServerTools()
115            conf = await wg.move_user(move="add", user_id=trigger.from_user.id)
116            config: WgConfig = await utils.add_wg_config(
117                conf, user_id=trigger.from_user.id
118            )
119
120        else:
121            raise exc.StagePayError
122
123    except exc.DatabaseError:
124        await trigger.answer(text=text.DB_ERROR, show_alert=True)
125    except exc.WireguardError:
126        await trigger.answer(text=text.WG_ERROR, show_alert=True)
127    except exc.StagePayError:
128        await getattr(trigger, "message", trigger).answer(
129            "Достигнуто максимальное количество конфигураций для данного тарифа.",
130            reply_markup=static_pay_button,
131        )
132        await bot.delete_message(
133            trigger.from_user.id, getattr(trigger, "message", trigger).message_id
134        )
135    except exc.PayError:
136        await getattr(trigger, "message", trigger).answer(
137            text.UNPAY, reply_markup=static_pay_button
138        )
139    else:
140        create_cfg_btn, create_output_cfg_btn = get_config_keyboard()
141
142        await trigger.answer(text="Конфигурация успешно создана", show_alert=True)
143        await getattr(trigger, "message", trigger).answer(
144            f"Конфигурация: {config.name} | id: {config.user_private_key[:4]}",
145            reply_markup=create_output_cfg_btn,
146        )
147
148        with suppress(TelegramBadRequest):
149            cfg_number = get_plural(
150                settings.acceptable_config[user_data.stage]
151                - len(user_data.configs)
152                - 1,
153                "конфигурацию, конфигурации, конфигураций",
154            )
155            await getattr(trigger, "message", trigger).edit_text(
156                f"Вы можете создать еще {cfg_number}", reply_markup=create_cfg_btn
157            )
158
159        if cfg_number.startswith("0"):
160            await bot.delete_message(
161                trigger.from_user.id, getattr(trigger, "message", trigger).message_id
162            )

Создает новую конфигурацию для пользователя.

Arguments:
  • trigger (Union[Message, CallbackQuery]): Сообщение или событие обратного вызова, инициировавшее команду.
  • bot (Bot): Экземпляр бота для выполнения действий.

Проверяет, может ли пользователь создать новую конфигурацию, и создает ее, если это возможно. В случае ошибок отправляет соответствующие сообщения.

@router.callback_query(F.data == 'remove_config_confirm')
@bot_except
async def remove_config_data_confirm(callback: aiogram.types.callback_query.CallbackQuery):
165@router.callback_query(F.data == "remove_config_confirm")
166@bot_except
167async def remove_config_data_confirm(callback: CallbackQuery):
168    """Подтверждение удаления конфигурации.
169
170    Args:
171        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
172    """
173
174    with suppress(TelegramBadRequest):
175        await callback.message.edit_text(
176            "После удаления конфигурации вы больше не сможете подключаться по ней к VPN-серверу. "
177            "Для восстановления подключения вам необходимо будет создать новую конфигурацию и заменить ей старую в vpn-приложении"
178            f"\n\n? Вы уверены, что хотите удалить конфигурацию {callback.message.text}",
179            reply_markup=remove_config(),
180        )

Подтверждение удаления конфигурации.

Arguments:
  • callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
@router.callback_query(F.data == 'remove_config_cancel')
@bot_except
async def remove_config_data_cancel(callback: aiogram.types.callback_query.CallbackQuery):
183@router.callback_query(F.data == "remove_config_cancel")
184@bot_except
185async def remove_config_data_cancel(callback: CallbackQuery):
186    """Отмена удаления конфигурации.
187
188    Args:
189        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
190    """
191    _, create_output_cfg_btn = get_config_keyboard()
192
193    *_, old_cfg_message = callback.message.text.split("удалить конфигурацию ")
194
195    with suppress(TelegramBadRequest):
196        await callback.message.edit_text(
197            old_cfg_message, reply_markup=create_output_cfg_btn
198        )

Отмена удаления конфигурации.

Arguments:
  • callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
@router.callback_query(F.data == 'remove_config')
@async_speed_metric
@bot_except
async def remove_config_data( callback: aiogram.types.callback_query.CallbackQuery, bot: aiogram.client.bot.Bot):
201@router.callback_query(F.data == "remove_config")
202@async_speed_metric
203@bot_except
204async def remove_config_data(callback: CallbackQuery, bot: Bot):
205    """Удаляет конфигурацию с WG сервера, а затем из БД.
206
207    Args:
208        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
209    """
210
211    try:
212        user_config: WgConfig = await find_config(callback)
213        if not user_config:
214            return
215
216        wg = WgServerTools()
217        await wg.move_user(move="del", server_pubkey=user_config.server_public_key)
218
219        await utils.delete_wg_config(user_config)
220
221    except exc.DatabaseError:
222        await callback.answer(text=text.DB_ERROR, show_alert=True)
223    except exc.WireguardError:
224        await callback.answer(text=text.WG_ERROR, show_alert=True)
225    else:
226        await callback.answer(text="Конфигурация успешно удалена", show_alert=True)
227    finally:
228        await bot.delete_message(callback.from_user.id, callback.message.message_id)

Удаляет конфигурацию с WG сервера, а затем из БД.

Arguments:
  • callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
@router.callback_query(F.data == 'create_conf_text')
@async_speed_metric
@bot_except
async def get_config_text( callback: aiogram.types.callback_query.CallbackQuery, bot: aiogram.client.bot.Bot):
231@router.callback_query(F.data == "create_conf_text")
232@async_speed_metric
233@bot_except
234async def get_config_text(callback: CallbackQuery, bot: Bot):
235    """Отправляет текст конфигурации пользователю.
236
237    Args:
238        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
239
240    Отправляет текст конфигурации, связанный с пользователем, в формате, удобном для чтения.
241    """
242    user_config: WgConfig = await find_config(callback)
243    if not user_config:
244        await bot.delete_message(callback.from_user.id, callback.message.message_id)
245        return
246
247    config = text.get_config_data(user_config)
248
249    await callback.message.answer("Конфигурация " + callback.message.text)
250    await callback.message.answer(f"<pre>{config}</pre>")

Отправляет текст конфигурации пользователю.

Arguments:
  • callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.

Отправляет текст конфигурации, связанный с пользователем, в формате, удобном для чтения.

@router.callback_query(F.data == 'create_conf_file')
@async_speed_metric
@bot_except
async def get_config_file( callback: aiogram.types.callback_query.CallbackQuery, bot: aiogram.client.bot.Bot):
253@router.callback_query(F.data == "create_conf_file")
254@async_speed_metric
255@bot_except
256async def get_config_file(callback: CallbackQuery, bot: Bot):
257    """Отправляет файл конфигурации пользователю.
258
259    Args:
260        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
261
262    Создает файл конфигурации и отправляет его пользователю в виде документа.
263    """
264    user_config: WgConfig = await find_config(callback)
265    if not user_config:
266        await bot.delete_message(callback.from_user.id, callback.message.message_id)
267        return
268
269    config = text.get_config_data(user_config)
270    config_file = await text.create_config_file(config)
271
272    await callback.message.answer("Конфигурация " + callback.message.text)
273    await callback.message.answer_document(FSInputFile(config_file, "myVpn.conf"))
274    await callback.message.answer(
275        "‼️ Если у вас возникает ошибка: <b>Неправильное имя</b> "
276        "Проверьте имя файла: в нем не должно быть пробелов или спецсимволов (подчеркиваний, скобок ...), "
277        "длина имени файла должна быть не более 10 символов"
278    )
279    os.remove(config_file)

Отправляет файл конфигурации пользователю.

Arguments:
  • callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.

Создает файл конфигурации и отправляет его пользователю в виде документа.

@router.callback_query(F.data == 'create_conf_qr')
@async_speed_metric
@bot_except
async def get_config_qr( callback: aiogram.types.callback_query.CallbackQuery, bot: aiogram.client.bot.Bot):
282@router.callback_query(F.data == "create_conf_qr")
283@async_speed_metric
284@bot_except
285async def get_config_qr(callback: CallbackQuery, bot: Bot):
286    """Отправляет QR-код конфигурации пользователю.
287
288    Args:
289        callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.
290
291    Создает QR-код конфигурации и отправляет его пользователю в виде изображения.
292    """
293    user_config: WgConfig = await find_config(callback)
294    if not user_config:
295        await bot.delete_message(callback.from_user.id, callback.message.message_id)
296        return
297
298    config = text.get_config_data(user_config)
299    config_qr = text.create_config_qr(config)
300
301    await callback.message.answer("Конфигурация " + callback.message.text)
302    await callback.message.answer_photo(
303        FSInputFile(config_qr, f"{user_config.name}_wg.conf")
304    )
305    os.remove(config_qr)

Отправляет QR-код конфигурации пользователю.

Arguments:
  • callback (CallbackQuery): Событие обратного вызова, инициировавшее команду.

Создает QR-код конфигурации и отправляет его пользователю в виде изображения.