5 Commits

Author SHA1 Message Date
97c52c5a51 ci(tests): run full suite in Desktop CI
Some checks failed
Desktop CI / tests (push) Successful in 13s
Desktop Release / release (push) Failing after 1m53s
- switch unittest to discover test_*.py

- include all current test modules in py_compile
2026-02-15 21:02:35 +03:00
862c2c8899 ci(release): run full test suite in workflow
Some checks are pending
Desktop CI / tests (push) Successful in 12s
Desktop Release / release (push) Has started running
- execute unittest discovery for all test_*.py files

- include all current test modules in py_compile check
2026-02-15 21:02:04 +03:00
1013a1ce38 ci(release): fix tag existence check for remote
Some checks are pending
Desktop CI / tests (push) Successful in 12s
Desktop Release / release (push) Has started running
- detect tag by non-empty ls-remote output instead of exit code
2026-02-15 21:00:36 +03:00
f15e71996b ci(release): skip duplicate Gitea release creation
All checks were successful
Desktop CI / tests (push) Successful in 12s
Desktop Release / release (push) Successful in 14s
- check tag existence on origin via ls-remote

- stop workflow when release for tag already exists
2026-02-15 20:57:01 +03:00
34272d01c8 merge: release 1.7.1
Some checks failed
Desktop CI / tests (push) Successful in 13s
Desktop Release / release (push) Failing after 1m48s
Reviewed-on: #1
2026-02-15 20:51:41 +03:00
5 changed files with 56 additions and 176 deletions

View File

@@ -28,8 +28,8 @@ jobs:
- name: Validate syntax - name: Validate syntax
run: | run: |
python -m py_compile app_version.py main.py build.py tests/test_auth_relogin_smoke.py python -m py_compile app_version.py main.py build.py tests/test_auth_relogin_smoke.py tests/test_auto_update_service.py tests/test_chat_actions.py tests/test_token_store.py
- name: Run tests - name: Run tests
run: | run: |
python -m unittest tests/test_auth_relogin_smoke.py python -m unittest discover -s tests -p "test_*.py" -v

View File

@@ -1,148 +0,0 @@
name: Desktop Dev Pre-release
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
prerelease:
runs-on: windows
steps:
- name: Checkout
uses: https://git.daemonlord.ru/actions/checkout@v4
with:
fetch-depth: 0
tags: true
- name: Ensure Python 3.13
shell: powershell
run: |
if (Get-Command python -ErrorAction SilentlyContinue) {
python --version
} elseif (Get-Command py -ErrorAction SilentlyContinue) {
$pyExe = py -3.13 -c "import sys; print(sys.executable)"
if (-not $pyExe) {
throw "Python 3.13 launcher is available, but interpreter was not found."
}
Split-Path $pyExe | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
python --version
} else {
throw "Python is not installed on runner. Install Python 3.13 and restart runner service."
}
- name: Install dependencies
shell: powershell
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt pyinstaller
- name: Extract prerelease metadata
id: meta
shell: powershell
run: |
$version = (python -c "from app_version import APP_VERSION; print(APP_VERSION)").Trim()
$commit = (git rev-parse --short HEAD).Trim()
$tag = "v$version-$commit"
$archive = "AnabasisManager-$version-$commit"
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "version=$version`n", $utf8NoBom)
[System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "commit=$commit`n", $utf8NoBom)
[System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "tag=$tag`n", $utf8NoBom)
[System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "archive=$archive`n", $utf8NoBom)
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "CONTINUE=true`n", $utf8NoBom)
Write-Host "Detected tag: $tag"
- name: Stop if prerelease already exists
if: env.CONTINUE == 'true'
shell: powershell
run: |
$tag = "${{ steps.meta.outputs.tag }}"
$apiUrl = "https://git.daemonlord.ru/api/v1/repos/${{ gitea.repository }}/releases?page=1&limit=100"
$headers = @{ Authorization = "token ${{ secrets.API_TOKEN }}" }
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
try {
$response = Invoke-RestMethod -Uri $apiUrl -Headers $headers -Method Get
$found = $false
foreach ($release in $response) {
if ($release.tag_name -eq $tag) {
$found = $true
break
}
}
if ($found) {
Write-Host "Pre-release $tag already exists, stopping job."
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "CONTINUE=false`n", $utf8NoBom)
} else {
Write-Host "Pre-release $tag not found, continuing workflow..."
}
} catch {
Write-Host "Failed to query releases list, continuing workflow..."
}
- name: Run tests
if: env.CONTINUE == 'true'
shell: powershell
run: |
python -m py_compile app_version.py main.py build.py updater_gui.py tests/test_auth_relogin_smoke.py tests/test_auto_update_service.py tests/test_chat_actions.py tests/test_token_store.py
python -m unittest discover -s tests -p "test_*.py" -v
- name: Build release zip
if: env.CONTINUE == 'true'
shell: powershell
run: |
python build.py
- name: Prepare prerelease artifacts
if: env.CONTINUE == 'true'
shell: powershell
run: |
$version = "${{ steps.meta.outputs.version }}"
$archiveBase = "${{ steps.meta.outputs.archive }}"
$srcZip = "dist/AnabasisManager-$version.zip"
$dstZip = "dist/$archiveBase.zip"
if (-not (Test-Path $srcZip)) {
throw "Archive not found: $srcZip"
}
Copy-Item -Path $srcZip -Destination $dstZip -Force
$hash = (Get-FileHash -Path $dstZip -Algorithm SHA256).Hash.ToLower()
"$hash $archiveBase.zip" | Set-Content -Path "dist/$archiveBase.zip.sha256" -Encoding UTF8
- name: Configure git identity
if: env.CONTINUE == 'true'
shell: powershell
run: |
git config user.name "gitea-actions"
git config user.email "gitea-actions@daemonlord.ru"
- name: Create git tag
if: env.CONTINUE == 'true'
shell: powershell
run: |
$tag = "${{ steps.meta.outputs.tag }}"
$tagLine = (git ls-remote --tags origin "refs/tags/$tag" | Select-Object -First 1)
if ([string]::IsNullOrWhiteSpace($tagLine)) {
git tag "$tag"
git push origin "$tag"
} else {
Write-Host "Tag $tag already exists on origin, skipping tag push."
}
- name: Create Gitea Pre-release
if: env.CONTINUE == 'true'
uses: https://git.daemonlord.ru/actions/gitea-release-action@v1
with:
server_url: https://git.daemonlord.ru
repository: ${{ gitea.repository }}
token: ${{ secrets.API_TOKEN }}
tag_name: ${{ steps.meta.outputs.tag }}
name: Anabasis Manager ${{ steps.meta.outputs.version }} (dev ${{ steps.meta.outputs.commit }})
prerelease: true
body: |
Development pre-release for commit ${{ steps.meta.outputs.commit }}
Version base: ${{ steps.meta.outputs.version }}
files: |
dist/${{ steps.meta.outputs.archive }}.zip
dist/${{ steps.meta.outputs.archive }}.zip.sha256

View File

@@ -53,9 +53,8 @@ jobs:
run: | run: |
$version = "${{ steps.extract_version.outputs.version }}" $version = "${{ steps.extract_version.outputs.version }}"
$tag = "v$version" $tag = "v$version"
git show-ref --tags --quiet --verify "refs/tags/$tag" $tagLine = (git ls-remote --tags origin "refs/tags/$tag" | Select-Object -First 1)
$tagExists = ($LASTEXITCODE -eq 0) $tagExists = -not [string]::IsNullOrWhiteSpace($tagLine)
$global:LASTEXITCODE = 0
$utf8NoBom = New-Object System.Text.UTF8Encoding($false) $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
if ($tagExists) { if ($tagExists) {
Write-Host "Version $tag already released, stopping job." Write-Host "Version $tag already released, stopping job."
@@ -66,12 +65,31 @@ jobs:
} }
exit 0 exit 0
- name: Stop if release already exists
if: env.CONTINUE == 'true'
shell: powershell
run: |
$version = "${{ steps.extract_version.outputs.version }}"
$tag = "v$version"
$apiUrl = "https://git.daemonlord.ru/api/v1/repos/${{ gitea.repository }}/releases/tags/$tag"
$headers = @{ Authorization = "token ${{ secrets.API_TOKEN }}" }
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
try {
$response = Invoke-WebRequest -Uri $apiUrl -Headers $headers -Method Get -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Host "Release $tag already exists, stopping job."
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "CONTINUE=false`n", $utf8NoBom)
}
} catch {
Write-Host "Release $tag not found, continuing workflow..."
}
- name: Run tests - name: Run tests
if: env.CONTINUE == 'true' if: env.CONTINUE == 'true'
shell: powershell shell: powershell
run: | run: |
python -m py_compile app_version.py main.py build.py tests/test_auth_relogin_smoke.py python -m py_compile app_version.py main.py build.py tests/test_auth_relogin_smoke.py tests/test_auto_update_service.py tests/test_chat_actions.py tests/test_token_store.py
python -m unittest tests/test_auth_relogin_smoke.py python -m unittest discover -s tests -p "test_*.py" -v
- name: Build release zip - name: Build release zip
if: env.CONTINUE == 'true' if: env.CONTINUE == 'true'

View File

@@ -1 +1 @@
APP_VERSION = "2.0.0" APP_VERSION = "1.7.1"

50
main.py
View File

@@ -21,7 +21,7 @@ from ui.main_window import instructions_text
from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit, from PySide6.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
QPushButton, QVBoxLayout, QWidget, QMessageBox, QPushButton, QVBoxLayout, QWidget, QMessageBox,
QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout, QTextBrowser, QScrollArea, QCheckBox, QHBoxLayout,
QSizePolicy, QTabWidget, QDialog, QDialogButtonBox, QSizePolicy, QTabWidget,
QProgressBar) QProgressBar)
from PySide6.QtCore import Qt, QUrl, QDateTime, QTimer from PySide6.QtCore import Qt, QUrl, QDateTime, QTimer
from PySide6.QtGui import QIcon, QAction, QDesktopServices from PySide6.QtGui import QIcon, QAction, QDesktopServices
@@ -103,6 +103,14 @@ class VkChatManager(QMainWindow):
layout.setSpacing(5) layout.setSpacing(5)
self.instructions = QTextBrowser() self.instructions = QTextBrowser()
self.instructions.setPlainText(
"Инструкция:\n"
"1. Авторизуйтесь через VK.\n"
"2. Выберите чаты.\n"
"3. Вставьте ссылку на пользователя в поле ниже. ID определится автоматически.\n"
"4. Для массовых операций, нажмите кнопку 'Список' и вставьте ссылки в окне.\n"
"5. Нажмите 'ИСКЛЮЧИТЬ' или 'ПРИГЛАСИТЬ'."
)
self.instructions.setFixedHeight(120) self.instructions.setFixedHeight(120)
self.instructions.setPlainText(instructions_text()) self.instructions.setPlainText(instructions_text())
layout.addWidget(self.instructions) layout.addWidget(self.instructions)
@@ -244,33 +252,35 @@ class VkChatManager(QMainWindow):
self.about_action = about_action self.about_action = about_action
def show_about_dialog(self): def show_about_dialog(self):
dialog = QDialog(self) message_box = QMessageBox(self)
dialog.setWindowTitle("О приложении") message_box.setWindowTitle("О приложении")
dialog.setMinimumWidth(460) message_box.setIcon(QMessageBox.Information)
message_box.setTextFormat(Qt.RichText)
repo_url = self.update_repository_url repo_url = self.update_repository_url
if repo_url: if repo_url:
repo_html = f'<a href="{repo_url}">{repo_url}</a>' repo_html = f'<a href="{repo_url}">{repo_url}</a>'
else: else:
repo_html = "не указан" repo_html = "не указан"
content = QLabel(
f"<b>Anabasis Chat Manager</b><br>" message_box.setText(
f"Версия: {APP_VERSION}<br><br>" (
"Инструмент для массового управления пользователями в чатах VK.<br>" f"<b>Anabasis Chat Manager</b><br>"
"Поддерживается проверка обновлений и автообновление Windows-сборки.<br><br>" f"Версия: {APP_VERSION}<br><br>"
f"Репозиторий: {repo_html}" "Инструмент для массового управления пользователями в чатах VK.<br>"
"Поддерживается проверка обновлений и автообновление Windows-сборки.<br><br>"
f"Репозиторий: {repo_html}"
)
) )
content.setTextFormat(Qt.RichText)
content.setTextInteractionFlags(Qt.TextBrowserInteraction)
content.setOpenExternalLinks(True)
content.setWordWrap(True)
button_box = QDialogButtonBox(QDialogButtonBox.Ok, parent=dialog) # QMessageBox не имеет setOpenExternalLinks, настраиваем его внутренний QLabel.
button_box.accepted.connect(dialog.accept) for label in message_box.findChildren(QLabel):
if "href=" in label.text():
label.setTextInteractionFlags(Qt.TextBrowserInteraction)
label.setOpenExternalLinks(True)
break
layout = QVBoxLayout(dialog) message_box.exec()
layout.addWidget(content)
layout.addWidget(button_box)
dialog.exec()
def create_chat_tab(self): def create_chat_tab(self):
# This implementation correctly creates a scrollable area for chat lists. # This implementation correctly creates a scrollable area for chat lists.