up version and fix negative balance

This commit is contained in:
Vladless
2026-03-14 00:01:51 +03:00
parent 04ff996d14
commit eea908da36
6 changed files with 120 additions and 65 deletions
Executable → Regular
+93 -58
View File
@@ -67,6 +67,18 @@ def is_ascii_only(value: str) -> bool:
return all(ord(ch) < 128 for ch in value) return all(ord(ch) < 128 for ch in value)
def _parse_tag_version(tag_name: str) -> tuple[int, ...]:
"""Извлекает кортеж (major, minor, patch, ...) из тега для сортировки. v.5.1 -> (5, 1), v4 -> (4, 0)."""
s = tag_name.strip().lstrip("v.")
parts = []
for part in re.split(r"[.\s]+", s):
try:
parts.append(int(part))
except ValueError:
break
return tuple(parts) if parts else (0,)
def warn_english_only(): def warn_english_only():
"""Предупреждение о необходимости английской раскладки.""" """Предупреждение о необходимости английской раскладки."""
console.print("[red]Обнаружен ввод с неанглийской раскладкой.[/red]") console.print("[red]Обнаружен ввод с неанглийской раскладкой.[/red]")
@@ -508,8 +520,50 @@ def update_from_beta():
console.print("[green]Обновление с ветки dev завершено.[/green]") console.print("[green]Обновление с ветки dev завершено.[/green]")
def _do_update_to_tag(tag_name: str, update_buttons: bool, update_img: bool) -> None:
"""Общая логика обновления до указанного тега (релиз или произвольный тег)."""
subprocess.run(["rm", "-rf", TEMP_DIR])
subprocess.run(
["git", "clone", "--branch", tag_name, "--depth", "1", GITHUB_REPO, TEMP_DIR],
check=True,
)
console.print("[red]Начинается перезапись файлов бота![/red]")
subprocess.run(["sudo", "rm", "-rf", os.path.join(PROJECT_DIR, "venv")])
clean_project_dir_safe(update_buttons=update_buttons, update_img=update_img)
exclude_options = ""
if not update_img:
exclude_options += "--exclude=img "
if not update_buttons:
exclude_options += "--exclude=handlers/buttons.py "
exclude_options += "--exclude=modules "
rsync_cmd = ["rsync", "-a"] + exclude_options.split() + [f"{TEMP_DIR}/", f"{PROJECT_DIR}/"]
subprocess.run(rsync_cmd)
modules_path = os.path.join(PROJECT_DIR, "modules")
if not os.path.exists(modules_path):
console.print("[yellow]Папка modules отсутствует — создаю вручную...[/yellow]")
try:
os.makedirs(modules_path, exist_ok=True)
console.print("[green]Папка modules успешно создана.[/green]")
except Exception as e:
console.print(f"[red]❌ Не удалось создать папку modules: {e}[/red]")
if os.path.exists(os.path.join(TEMP_DIR, ".git")):
subprocess.run(["cp", "-r", os.path.join(TEMP_DIR, ".git"), PROJECT_DIR])
subprocess.run(["rm", "-rf", TEMP_DIR])
install_dependencies()
fix_permissions()
restart_service()
console.print(f"[green]Обновление до {tag_name} завершено.[/green]")
def update_from_release(): def update_from_release():
if not safe_confirm("[yellow]Подтвердите обновление Solobot до одного из последних релизов[/yellow]"): if not safe_confirm("[yellow]Подтвердите обновление Solobot до релиза или патча[/yellow]"):
return return
console.print("[red]ВНИМАНИЕ! Папка бота будет полностью перезаписана![/red]") console.print("[red]ВНИМАНИЕ! Папка бота будет полностью перезаписана![/red]")
@@ -525,65 +579,46 @@ def update_from_release():
install_rsync_if_needed() install_rsync_if_needed()
try: try:
response = requests.get("https://api.github.com/repos/Vladless/Solo_bot/releases", timeout=10) rel_resp = requests.get(
releases = response.json()[:3] "https://api.github.com/repos/Vladless/Solo_bot/releases",
tag_choices = [r["tag_name"] for r in releases] timeout=10,
if not tag_choices:
raise ValueError("Не удалось получить список релизов")
console.print("\n[bold green]Доступные релизы:[/bold green]")
for idx, tag in enumerate(tag_choices, 1):
console.print(f"[cyan]{idx}.[/cyan] {tag}")
selected = safe_prompt(
"[bold blue]Выберите номер релиза[/bold blue]",
choices=[str(i) for i in range(1, len(tag_choices) + 1)],
) )
tag_name = tag_choices[int(selected) - 1] releases = rel_resp.json() if rel_resp.status_code == 200 else []
release_tag_names = {r["tag_name"] for r in releases}
if not safe_confirm(f"[yellow]Подтвердите установку релиза {tag_name}[/yellow]"): tags_resp = requests.get(
"https://api.github.com/repos/Vladless/Solo_bot/tags",
params={"per_page": 50},
timeout=10,
)
if tags_resp.status_code != 200:
raise ValueError("Не удалось получить список тегов")
tags_data = tags_resp.json()
all_tag_names = [t["name"] for t in tags_data]
tag_names = [name for name in all_tag_names if _parse_tag_version(name)[0] >= 4]
tag_names.sort(key=_parse_tag_version)
if not tag_names:
raise ValueError("Нет доступных тегов (ожидаются версии начиная с 4)")
console.print("\n[bold green]Релизы и патчи:[/bold green]")
for idx, name in enumerate(tag_names, 1):
label = " [dim](релиз)[/dim]" if name in release_tag_names else " [dim](патч)[/dim]"
console.print(f"[cyan]{idx}.[/cyan] {name}{label}")
choices = [str(i) for i in range(1, len(tag_names) + 1)]
selected = safe_prompt(
"[bold blue]Выберите номер версии[/bold blue]",
choices=choices,
)
tag_name = tag_names[int(selected) - 1]
if not safe_confirm(f"[yellow]Установить {tag_name}?[/yellow]"):
return return
console.print(f"[cyan]Клонируем релиз {tag_name} во временную папку...[/cyan]") console.print(f"[cyan]Клонируем {tag_name} во временную папку...[/cyan]")
subprocess.run(["rm", "-rf", TEMP_DIR]) _do_update_to_tag(tag_name, update_buttons, update_img)
subprocess.run(
["git", "clone", "--branch", tag_name, GITHUB_REPO, TEMP_DIR],
check=True,
)
console.print("[red]Начинается перезапись файлов бота![/red]")
subprocess.run(["sudo", "rm", "-rf", os.path.join(PROJECT_DIR, "venv")])
clean_project_dir_safe(update_buttons=update_buttons, update_img=update_img)
exclude_options = ""
if not update_img:
exclude_options += "--exclude=img "
if not update_buttons:
exclude_options += "--exclude=handlers/buttons.py "
exclude_options += "--exclude=modules "
rsync_cmd = ["rsync", "-a"] + exclude_options.split() + [f"{TEMP_DIR}/", f"{PROJECT_DIR}/"]
subprocess.run(rsync_cmd)
modules_path = os.path.join(PROJECT_DIR, "modules")
if not os.path.exists(modules_path):
console.print("[yellow]Папка modules отсутствует — создаю вручную...[/yellow]")
try:
os.makedirs(modules_path, exist_ok=True)
console.print("[green]Папка modules успешно создана.[/green]")
except Exception as e:
console.print(f"[red]❌ Не удалось создать папку modules: {e}[/red]")
if os.path.exists(os.path.join(TEMP_DIR, ".git")):
subprocess.run(["cp", "-r", os.path.join(TEMP_DIR, ".git"), PROJECT_DIR])
subprocess.run(["rm", "-rf", TEMP_DIR])
install_dependencies()
fix_permissions()
restart_service()
console.print(f"[green]Обновление до релиза {tag_name} завершено.[/green]")
except Exception as e: except Exception as e:
console.print(f"[red]❌ Ошибка при обновлении: {e}[/red]") console.print(f"[red]❌ Ошибка при обновлении: {e}[/red]")
@@ -599,7 +634,7 @@ def show_update_menu():
table.add_column("", justify="center", style="cyan", no_wrap=True) table.add_column("", justify="center", style="cyan", no_wrap=True)
table.add_column("Источник", style="white") table.add_column("Источник", style="white")
table.add_row("1", "Обновить до BETA") table.add_row("1", "Обновить до BETA")
table.add_row("2", "Обновить/откатить до релиза") table.add_row("2", "Обновить до релиза (релизы и патчи)")
table.add_row("3", "Назад в меню") table.add_row("3", "Назад в меню")
console.print(table) console.print(table)
@@ -612,7 +647,7 @@ def show_update_menu():
def show_menu(): def show_menu():
table = Table(title="Solobot CLI v0.3.9", title_style="bold magenta", header_style="bold blue") table = Table(title="Solobot CLI v0.4.0", title_style="bold magenta", header_style="bold blue")
table.add_column("", justify="center", style="cyan", no_wrap=True) table.add_column("", justify="center", style="cyan", no_wrap=True)
table.add_column("Операция", style="white") table.add_column("Операция", style="white")
table.add_row("1", "Запустить бота (systemd)") table.add_row("1", "Запустить бота (systemd)")
+11 -3
View File
@@ -451,6 +451,7 @@ async def create_key(
selected_device_limit: int | None = None, selected_device_limit: int | None = None,
selected_traffic_gb: int | None = None, selected_traffic_gb: int | None = None,
selected_price_rub: int | None = None, selected_price_rub: int | None = None,
skip_balance_charge: bool | None = None,
): ):
from_user = message_or_query.from_user if isinstance(message_or_query, CallbackQuery | Message) else None from_user = message_or_query.from_user if isinstance(message_or_query, CallbackQuery | Message) else None
if from_user: if from_user:
@@ -466,9 +467,12 @@ async def create_key(
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION)) use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
if state and any( if state and (
value is not None skip_balance_charge is not None
for value in (selected_duration_days, selected_device_limit, selected_traffic_gb, selected_price_rub) or any(
value is not None
for value in (selected_duration_days, selected_device_limit, selected_traffic_gb, selected_price_rub)
)
): ):
state_data = await state.get_data() state_data = await state.get_data()
if selected_duration_days is not None: if selected_duration_days is not None:
@@ -479,6 +483,8 @@ async def create_key(
state_data["config_selected_traffic_gb"] = selected_traffic_gb state_data["config_selected_traffic_gb"] = selected_traffic_gb
if selected_price_rub is not None: if selected_price_rub is not None:
state_data["config_selected_price_rub"] = selected_price_rub state_data["config_selected_price_rub"] = selected_price_rub
if skip_balance_charge is not None:
state_data["skip_balance_charge"] = skip_balance_charge
await state.set_data(state_data) await state.set_data(state_data)
if use_country_selection: if use_country_selection:
@@ -493,6 +499,7 @@ async def create_key(
selected_device_limit=selected_device_limit, selected_device_limit=selected_device_limit,
selected_traffic_gb=selected_traffic_gb, selected_traffic_gb=selected_traffic_gb,
selected_price_rub=selected_price_rub, selected_price_rub=selected_price_rub,
skip_balance_charge=skip_balance_charge,
) )
else: else:
await key_cluster_mode( await key_cluster_mode(
@@ -505,4 +512,5 @@ async def create_key(
selected_device_limit=selected_device_limit, selected_device_limit=selected_device_limit,
selected_traffic_gb=selected_traffic_gb, selected_traffic_gb=selected_traffic_gb,
selected_price_rub=selected_price_rub, selected_price_rub=selected_price_rub,
skip_balance_charge=skip_balance_charge,
) )
+3 -1
View File
@@ -69,6 +69,7 @@ async def key_cluster_mode(
selected_device_limit: int | None = None, selected_device_limit: int | None = None,
selected_traffic_gb: int | None = None, selected_traffic_gb: int | None = None,
selected_price_rub: int | None = None, selected_price_rub: int | None = None,
skip_balance_charge: bool | None = None,
): ):
target_message = None target_message = None
safe_to_edit = False safe_to_edit = False
@@ -93,6 +94,7 @@ async def key_cluster_mode(
try: try:
data = await state.get_data() if state else {} data = await state.get_data() if state else {}
is_trial = data.get("is_trial", False) is_trial = data.get("is_trial", False)
skip_balance_charge = bool(skip_balance_charge)
if selected_device_limit is None: if selected_device_limit is None:
selected_device_limit = data.get("config_selected_device_limit") or data.get("selected_device_limit") selected_device_limit = data.get("config_selected_device_limit") or data.get("selected_device_limit")
@@ -182,7 +184,7 @@ async def key_cluster_mode(
if trial_status in [0, -1]: if trial_status in [0, -1]:
await update_trial(session, tg_id, 1) await update_trial(session, tg_id, 1)
if price_to_charge: if price_to_charge and not skip_balance_charge:
await update_balance(session, tg_id, -int(price_to_charge)) await update_balance(session, tg_id, -int(price_to_charge))
except Exception as e: except Exception as e:
+12 -2
View File
@@ -89,6 +89,7 @@ async def key_country_mode(
selected_device_limit: int | None = None, selected_device_limit: int | None = None,
selected_traffic_gb: int | None = None, selected_traffic_gb: int | None = None,
selected_price_rub: int | None = None, selected_price_rub: int | None = None,
skip_balance_charge: bool | None = None,
): ):
target_message = None target_message = None
safe_to_edit = False safe_to_edit = False
@@ -96,7 +97,10 @@ async def key_country_mode(
if state and plan: if state and plan:
await state.update_data(tariff_id=plan) await state.update_data(tariff_id=plan)
if state and any(value is not None for value in (selected_device_limit, selected_traffic_gb, selected_price_rub)): if state and (
skip_balance_charge is not None
or any(value is not None for value in (selected_device_limit, selected_traffic_gb, selected_price_rub))
):
data = await state.get_data() data = await state.get_data()
if selected_device_limit is not None: if selected_device_limit is not None:
data["config_selected_device_limit"] = selected_device_limit data["config_selected_device_limit"] = selected_device_limit
@@ -104,6 +108,8 @@ async def key_country_mode(
data["config_selected_traffic_gb"] = selected_traffic_gb data["config_selected_traffic_gb"] = selected_traffic_gb
if selected_price_rub is not None: if selected_price_rub is not None:
data["config_selected_price_rub"] = selected_price_rub data["config_selected_price_rub"] = selected_price_rub
if skip_balance_charge is not None:
data["skip_balance_charge"] = skip_balance_charge
await state.set_data(data) await state.set_data(data)
if isinstance(message_or_query, CallbackQuery) and message_or_query.message: if isinstance(message_or_query, CallbackQuery) and message_or_query.message:
@@ -499,6 +505,7 @@ async def finalize_key_creation(
data = await state.get_data() if state else {} data = await state.get_data() if state else {}
is_trial = data.get("is_trial", False) is_trial = data.get("is_trial", False)
skip_balance_charge = bool(data.get("skip_balance_charge", False))
selected_traffic_gb = data.get("config_selected_traffic_gb") selected_traffic_gb = data.get("config_selected_traffic_gb")
if selected_traffic_gb is None: if selected_traffic_gb is None:
@@ -723,9 +730,12 @@ async def finalize_key_creation(
trial_status = await get_trial(session, tg_id) trial_status = await get_trial(session, tg_id)
if trial_status in [0, -1]: if trial_status in [0, -1]:
await update_trial(session, tg_id, 1) await update_trial(session, tg_id, 1)
if not is_trial and price_to_charge: if not is_trial and price_to_charge and not skip_balance_charge:
await update_balance(session, tg_id, -int(price_to_charge)) await update_balance(session, tg_id, -int(price_to_charge))
if state:
await state.update_data(skip_balance_charge=False)
await session.commit() await session.commit()
except Exception as e: except Exception as e:
+1 -1
View File
@@ -92,4 +92,4 @@ def get_git_commit_number() -> str:
def get_version() -> str: def get_version() -> str:
return f"v.5.1.1 {get_git_commit_number()}" return f"v.5.1.2 {get_git_commit_number()}"