mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-17 23:13:03 +08:00
Compare commits
5 Commits
f7167c13f4
...
59c06b3a19
| Author | SHA1 | Date | |
|---|---|---|---|
| 59c06b3a19 | |||
| b78fd2d1e4 | |||
| 2aace40a26 | |||
| df7ad92f7f | |||
| 910e3e3224 |
@@ -4,10 +4,6 @@ name: Build Test
|
||||
# It is triggered when a pull request is opened, synchronized, or reopened against the main branch.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -15,7 +11,6 @@ on:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
# Allow manual trigger for testing
|
||||
workflow_dispatch:
|
||||
|
||||
#
|
||||
@@ -49,6 +44,8 @@ jobs:
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: requirement.txt
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -125,7 +122,7 @@ jobs:
|
||||
" binaries=[],"
|
||||
" datas=["
|
||||
" ('models\\common.onnx', 'ddddocr'),"
|
||||
" ('src\\gui\\resources\\icons\\AutoLibrary_32x32.ico', 'gui\\resources\\icons'),"
|
||||
" ('src\\gui\\resources\\icons\\AutoLibrary_Logo_64.ico', 'gui\\resources\\icons'),"
|
||||
" ],"
|
||||
" hiddenimports=[],"
|
||||
" hookspath=[],"
|
||||
@@ -153,7 +150,7 @@ jobs:
|
||||
" target_arch=None,"
|
||||
" codesign_identity=None,"
|
||||
" entitlements_file=None,"
|
||||
" icon=['src\\gui\\resources\\icons\\AutoLibrary_32x32.ico'],"
|
||||
" icon=['src\\gui\\resources\\icons\\AutoLibrary_Logo_64.ico'],"
|
||||
")"
|
||||
""
|
||||
"coll = COLLECT("
|
||||
@@ -169,9 +166,11 @@ jobs:
|
||||
$specLines | Out-File -FilePath "Main.spec" -Encoding UTF8
|
||||
|
||||
Write-Host "✓ Main.spec (non-single file) generated successfully"
|
||||
Write-Host "`nGenerated Main.spec ============"
|
||||
Write-Host "`n========================================"
|
||||
Write-Host "Generated Main.spec"
|
||||
Write-Host "========================================"
|
||||
Get-Content "Main.spec" | Write-Host
|
||||
Write-Host "==================================`n"
|
||||
Write-Host "========================================`n"
|
||||
shell: pwsh
|
||||
|
||||
- name: Build with PyInstaller
|
||||
@@ -186,7 +185,7 @@ jobs:
|
||||
$distDir = "dist/AutoLibrary-$version"
|
||||
$zipName = "AutoLibrary.$tagName-windows-x86_64.zip"
|
||||
|
||||
echo "ZIP_NAME=$zipName" >> $env:GITHUB_OUTPUT
|
||||
"ZIP_NAME=$zipName" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
|
||||
Write-Host "Looking for distribution directory: $distDir"
|
||||
if (Test-Path $distDir) {
|
||||
@@ -212,10 +211,11 @@ jobs:
|
||||
run: |
|
||||
Write-Host "## Build Test Summary" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8
|
||||
Write-Host "" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "========================================" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "✓ Pull request build test completed successfully!" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Version: ${{ steps.get_version.outputs.VERSION }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Tag: ${{ steps.get_version.outputs.TAG_NAME }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Pull Request #${{ github.event.pull_request.number }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Branch: ${{ github.event.pull_request.head.ref }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Pull Request #${{ github.event.pull_request.number || 'N/A' }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Branch: ${{ github.event.pull_request.head.ref || github.ref }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Event: ${{ github.event_name }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
shell: pwsh
|
||||
|
||||
@@ -76,15 +76,17 @@ jobs:
|
||||
run: |
|
||||
$versionInfoFile = "src/gui/ALVersionInfo.py"
|
||||
Write-Host "Verifying $versionInfoFile content:"
|
||||
Write-Host "=================================="
|
||||
Write-Host "========================================"
|
||||
Get-Content $versionInfoFile | Write-Host
|
||||
Write-Host "=================================="
|
||||
Write-Host "========================================"
|
||||
shell: pwsh
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: requirement.txt
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -161,7 +163,7 @@ jobs:
|
||||
" binaries=[],"
|
||||
" datas=["
|
||||
" ('models\\common.onnx', 'ddddocr'),"
|
||||
" ('src\\gui\\resources\\icons\\AutoLibrary_32x32.ico', 'gui\\resources\\icons'),"
|
||||
" ('src\\gui\\resources\\icons\\AutoLibrary_Logo_64.ico', 'gui\\resources\\icons'),"
|
||||
" ],"
|
||||
" hiddenimports=[],"
|
||||
" hookspath=[],"
|
||||
@@ -189,7 +191,7 @@ jobs:
|
||||
" target_arch=None,"
|
||||
" codesign_identity=None,"
|
||||
" entitlements_file=None,"
|
||||
" icon=['src\\gui\\resources\\icons\\AutoLibrary_32x32.ico'],"
|
||||
" icon=['src\\gui\\resources\\icons\\AutoLibrary_Logo_64.ico'],"
|
||||
")"
|
||||
""
|
||||
"coll = COLLECT("
|
||||
@@ -205,9 +207,11 @@ jobs:
|
||||
$specLines | Out-File -FilePath "Main.spec" -Encoding UTF8
|
||||
|
||||
Write-Host "✓ Main.spec (non-single file) generated successfully"
|
||||
Write-Host "`nGenerated Main.spec ============"
|
||||
Write-Host "`n========================================"
|
||||
Write-Host "Generated Main.spec"
|
||||
Write-Host "========================================"
|
||||
Get-Content "Main.spec" | Write-Host
|
||||
Write-Host "==================================`n"
|
||||
Write-Host "========================================`n"
|
||||
shell: pwsh
|
||||
|
||||
- name: Build with PyInstaller
|
||||
@@ -222,7 +226,7 @@ jobs:
|
||||
$distDir = "dist/AutoLibrary-$version"
|
||||
$zipName = "AutoLibrary.$tagName-windows-x86_64.zip"
|
||||
|
||||
echo "ZIP_NAME=$zipName" >> $env:GITHUB_OUTPUT
|
||||
"ZIP_NAME=$zipName" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
|
||||
Write-Host "Looking for distribution directory: $distDir"
|
||||
if (Test-Path $distDir) {
|
||||
@@ -242,13 +246,14 @@ jobs:
|
||||
name: AutoLibrary.${{ steps.get_version.outputs.TAG_NAME }}-windows-x86_64
|
||||
path: |
|
||||
${{ steps.zip_release.outputs.ZIP_NAME }}
|
||||
retention-days: ${{ github.event_name != 'workflow_call' && 7 || 90 }}
|
||||
retention-days: ${{ inputs.is_test == 'true' && 7 || 90 }}
|
||||
|
||||
- name: Upload build summary
|
||||
if: ${{ github.event_name != 'workflow_call' }}
|
||||
if: ${{ inputs.is_test == 'true' }}
|
||||
run: |
|
||||
Write-Host "## Build Summary" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8
|
||||
Write-Host "" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "========================================" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "✓ Build test completed successfully!" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Version: ${{ steps.get_version.outputs.VERSION }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
Write-Host "- Tag: ${{ steps.get_version.outputs.TAG_NAME }}" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
|
||||
|
||||
@@ -83,7 +83,9 @@ jobs:
|
||||
|
||||
echo "✓ File replaced: $FILE_PATH"
|
||||
echo ""
|
||||
echo "Updated file content ==================="
|
||||
echo "========================================"
|
||||
echo "Updated file content"
|
||||
echo "========================================"
|
||||
cat "$FILE_PATH"
|
||||
echo "========================================"
|
||||
|
||||
@@ -151,3 +153,9 @@ jobs:
|
||||
COMMIT_SHA=$(git rev-parse --short HEAD)
|
||||
echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "✓ New commit SHA: $COMMIT_SHA"
|
||||
echo "## Commit Release Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "========================================" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Version: ${{ inputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Tag: ${{ inputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Commit SHA: $COMMIT_SHA" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -47,7 +47,7 @@ on:
|
||||
jobs:
|
||||
#
|
||||
# Start :
|
||||
# virtual job that indacates the start of the release process
|
||||
# virtual job that indicates the start of the release process
|
||||
#
|
||||
|
||||
start:
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
needs:
|
||||
- update-version
|
||||
- commit-release
|
||||
if: always() && needs.update-version.result == 'success' && needs.commit-release.result == 'success'
|
||||
if: always() && needs.update-version.result == 'success' && (needs.commit-release.result == 'success' || needs.commit-release.result == 'skipped')
|
||||
uses: ./.github/workflows/build.yml
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# End :
|
||||
# virtual job that indacates the end of the release process
|
||||
# virtual job that indicates the end of the release process
|
||||
#
|
||||
|
||||
end:
|
||||
@@ -227,7 +227,7 @@ jobs:
|
||||
- release
|
||||
- extract-version
|
||||
- commit-release
|
||||
if: ${{ needs.release.result == 'success' && needs.commit-release.result == 'success' }}
|
||||
if: ${{ needs.release.result == 'success' && (needs.commit-release.result == 'success' || needs.commit-release.result == 'skipped') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -267,9 +267,13 @@ jobs:
|
||||
git checkout ${MAIN_BRANCH}
|
||||
|
||||
# Show branch status before merge
|
||||
echo "=== Branch status before merge ==="
|
||||
echo "========================================"
|
||||
echo "Branch status before merge"
|
||||
echo "========================================"
|
||||
git log --oneline --graph --all -5
|
||||
echo "=== Diff between ${MAIN_BRANCH} and origin/${BRANCH_NAME} ==="
|
||||
echo "========================================"
|
||||
echo "Diff: ${MAIN_BRANCH} vs origin/${BRANCH_NAME}"
|
||||
echo "========================================"
|
||||
git diff ${MAIN_BRANCH} origin/${BRANCH_NAME} --stat || echo "No differences found"
|
||||
|
||||
# Force create a merge commit even if there are no changes
|
||||
@@ -279,7 +283,9 @@ jobs:
|
||||
-m "chore(release): merge ${BRANCH_NAME} to ${MAIN_BRANCH} [auto release commit]"
|
||||
|
||||
# Show merge result
|
||||
echo "=== Merge result ==="
|
||||
echo "========================================"
|
||||
echo "Merge result"
|
||||
echo "========================================"
|
||||
git log --oneline --graph -3
|
||||
|
||||
# Push to main
|
||||
@@ -310,6 +316,7 @@ jobs:
|
||||
|
||||
echo "## Release Cleanup Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "========================================" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✓ Release completed successfully!" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Actions Performed:" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -128,10 +128,15 @@ jobs:
|
||||
echo "Build version file location: $VER_INFO_BUILDFILE"
|
||||
echo "Commit version file location: $VER_INFO_COMMITFILE"
|
||||
echo ""
|
||||
echo "Build version ALVersionInfo.py content ="
|
||||
echo "========================================"
|
||||
echo "Build version ALVersionInfo.py"
|
||||
echo "========================================"
|
||||
cat "$VER_INFO_BUILDFILE"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Commit version ALVersionInfo.py content "
|
||||
echo "========================================"
|
||||
echo "Commit version ALVersionInfo.py"
|
||||
echo "========================================"
|
||||
cat "$VER_INFO_COMMITFILE"
|
||||
echo "========================================"
|
||||
|
||||
@@ -140,11 +145,16 @@ jobs:
|
||||
run: |
|
||||
if git diff --quiet src/gui/ALVersionInfo.py; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "! No changes detected in ALVersionInfo.py"
|
||||
echo "⚠ No changes detected in ALVersionInfo.py"
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "✓ ALVersionInfo.py has been modified"
|
||||
fi
|
||||
echo "## Update Version Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "========================================" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Version: ${{ steps.get_version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Tag: ${{ steps.get_version.outputs.TAG_NAME }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload modified ALVersionInfo.py ready for build
|
||||
if: steps.check_changes.outputs.has_changes == 'true'
|
||||
|
||||
@@ -7,16 +7,7 @@ This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from autoscript.ASEngine import ASEngine
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ASEngine",
|
||||
"createEngine",
|
||||
"createMockTargetData",
|
||||
"createAllVariablesTable",
|
||||
"createTargetVarDefs",
|
||||
]
|
||||
from .ASEngine import ASEngine
|
||||
|
||||
|
||||
_TARGET_VAR_DEFS = [
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Base module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- MsgBase: Base class for messages.
|
||||
"""
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Boot module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- AppInitializer: Application initializer class.
|
||||
"""
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
from gui.ALAutoScriptOrchDialog._dialog import ALAutoScriptOrchDialog
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
__all__ = ["ALAutoScriptOrchDialog"]
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from ._dialog import ALAutoScriptOrchDialog
|
||||
|
||||
+7
-17
@@ -1,19 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
GUI module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- ALMainWindow: Main window class.
|
||||
- ALAboutDialog: About dialog class.
|
||||
- ALConfigWidget: Configuration widget class.
|
||||
- ALSeatFrame: Seat frame class.
|
||||
- ALSeatMapView: Seat map view class.
|
||||
- ALSeatMapTable: Seat map table class.
|
||||
- ALSeatMapSelectDialog: Seat map select dialog class.
|
||||
- ALTimerTaskAddDialog: Timer task add dialog class.
|
||||
- ALAutoScriptOrchDialog: AutoScript orchestration dialog class.
|
||||
- ALTimerTaskHistoryDialog: Timer task history dialog class.
|
||||
- ALTimerTaskManageWidget: Timer task manage widget class.
|
||||
- ALUserTreeWidget: User tree widget class.
|
||||
- ALMainWorkers: Main workers class.
|
||||
- ALVersionInfo: Version info class.
|
||||
"""
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
GUI resources module for the AutoLibrary project.
|
||||
"""
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 178 KiB |
@@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Interfaces module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Defines abstract interfaces (Protocols) and shared type definitions
|
||||
used across layers to decouple consumers from concrete implementations.
|
||||
|
||||
Key components:
|
||||
- ConfigProvider: Abstract interface for configuration access.
|
||||
- ConfigType: Enumeration of configuration file types.
|
||||
- ConfigKey: Type-safe hierarchical key constants for config lookups.
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Managers module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- ConfigManager: Config manager for managing configuration files.
|
||||
- LogManager: Log manager for logging.
|
||||
- WebDriverManager: Web driver manager for managing web drivers.
|
||||
"""
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Config managers module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- ConfigManager: Config manager for managing configuration files.
|
||||
"""
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Driver managers module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- WebBrowserDetector: Web browser detector class.
|
||||
- WebDriverDownloader: Web driver downloader class.
|
||||
- WebDriverManager: Web driver manager class.
|
||||
"""
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Log managers module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- LogManager: Log manager for logging.
|
||||
"""
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
@@ -149,9 +149,6 @@ class AutoLib(MsgBase):
|
||||
except WebDriverException as e:
|
||||
self._showTrace(f"浏览器驱动初始化失败: {e}", self.TraceLevel.ERROR)
|
||||
return False
|
||||
except Exception as e:
|
||||
self._showTrace(f"浏览器驱动初始化失败: {e}", self.TraceLevel.ERROR)
|
||||
return False
|
||||
self._showTrace(f"浏览器驱动已初始化, 类型: {self.__driver_type}, 路径: {self.__driver_path}")
|
||||
return True
|
||||
|
||||
|
||||
+15
-23
@@ -85,9 +85,7 @@ class LoginPage:
|
||||
EC.presence_of_element_located(self.CAPTCHA_IMG)
|
||||
)
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return False
|
||||
except Exception:
|
||||
except TimeoutException:
|
||||
return False
|
||||
|
||||
def fillCredentials(
|
||||
@@ -104,17 +102,21 @@ class LoginPage:
|
||||
el.clear()
|
||||
el.send_keys(password)
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return False
|
||||
except Exception:
|
||||
except (NoSuchElementException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
def getCaptchaImageSrc(
|
||||
self,
|
||||
) -> str:
|
||||
) -> str | None:
|
||||
|
||||
captcha_el = self._driver.find_element(*self.CAPTCHA_IMG)
|
||||
return captcha_el.get_attribute("src")
|
||||
# return 'None' if captcha image element is not found.
|
||||
# But the 'get_attribute("src")' also return 'None' if there's no attribute with
|
||||
# that name, which is not what we want.
|
||||
try:
|
||||
captcha_el = self._driver.find_element(*self.CAPTCHA_IMG)
|
||||
return captcha_el.get_attribute("src")
|
||||
except NoSuchElementException:
|
||||
return None
|
||||
|
||||
def refreshCaptcha(
|
||||
self,
|
||||
@@ -123,10 +125,7 @@ class LoginPage:
|
||||
try:
|
||||
self._driver.find_element(*self.CAPTCHA_IMG).click()
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException,
|
||||
ElementNotInteractableException):
|
||||
return False
|
||||
except Exception:
|
||||
except (NoSuchElementException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
def fillCaptcha(
|
||||
@@ -139,9 +138,7 @@ class LoginPage:
|
||||
el.clear()
|
||||
el.send_keys(captcha_text)
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return False
|
||||
except Exception:
|
||||
except (NoSuchElementException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
def clickLogin(
|
||||
@@ -151,10 +148,7 @@ class LoginPage:
|
||||
try:
|
||||
self._driver.find_element(*self.LOGIN_BUTTON).click()
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException,
|
||||
ElementNotInteractableException):
|
||||
return False
|
||||
except Exception:
|
||||
except (NoSuchElementException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
def waitLoginSuccess(
|
||||
@@ -172,9 +166,7 @@ class LoginPage:
|
||||
EC.presence_of_element_located(self.SUCCESS_INDICATOR_CONTENT)
|
||||
)
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return False
|
||||
except Exception:
|
||||
except TimeoutException:
|
||||
return False
|
||||
|
||||
def stopPageLoad(
|
||||
|
||||
+40
-29
@@ -14,8 +14,10 @@ from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.common.exceptions import (
|
||||
ElementNotInteractableException,
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
WebDriverException,
|
||||
)
|
||||
|
||||
from pages.ReserveView import ReserveView
|
||||
@@ -38,6 +40,15 @@ class MainShell:
|
||||
|
||||
self._driver = driver
|
||||
|
||||
def _clickTab(
|
||||
self,
|
||||
locator: tuple,
|
||||
) -> None:
|
||||
|
||||
WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(locator)
|
||||
).click()
|
||||
|
||||
def gotoReserveView(
|
||||
self,
|
||||
) -> ReserveView:
|
||||
@@ -65,9 +76,7 @@ class MainShell:
|
||||
try:
|
||||
self._driver.find_element(*self.TAB_LOGOUT).click()
|
||||
return True
|
||||
except NoSuchElementException:
|
||||
return False
|
||||
except Exception:
|
||||
except (NoSuchElementException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
def waitCheckinButton(
|
||||
@@ -81,8 +90,6 @@ class MainShell:
|
||||
return True
|
||||
except TimeoutException:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def waitExtendButton(
|
||||
self,
|
||||
@@ -95,40 +102,50 @@ class MainShell:
|
||||
return True
|
||||
except TimeoutException:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def isCheckinButtonDisabled(
|
||||
self,
|
||||
) -> bool:
|
||||
|
||||
btn = self._driver.find_element(*self.BTN_CHECKIN)
|
||||
return "disabled" in btn.get_attribute("class")
|
||||
try:
|
||||
btn = self._driver.find_element(*self.BTN_CHECKIN)
|
||||
return "disabled" in btn.get_attribute("class")
|
||||
except NoSuchElementException:
|
||||
return True
|
||||
|
||||
def isExtendButtonDisabled(
|
||||
self,
|
||||
) -> bool:
|
||||
|
||||
btn = self._driver.find_element(*self.BTN_EXTEND)
|
||||
return "disabled" in btn.get_attribute("class")
|
||||
try:
|
||||
btn = self._driver.find_element(*self.BTN_EXTEND)
|
||||
return "disabled" in btn.get_attribute("class")
|
||||
except NoSuchElementException:
|
||||
return True
|
||||
|
||||
def clickCheckinButton(
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
btn = WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(self.BTN_CHECKIN)
|
||||
)
|
||||
btn.click()
|
||||
try:
|
||||
btn = WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(self.BTN_CHECKIN)
|
||||
)
|
||||
btn.click()
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return
|
||||
|
||||
def clickExtendButton(
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
btn = WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(self.BTN_EXTEND)
|
||||
)
|
||||
btn.click()
|
||||
try:
|
||||
btn = WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(self.BTN_EXTEND)
|
||||
)
|
||||
btn.click()
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return
|
||||
|
||||
def enableCheckinButtonByJS(
|
||||
self,
|
||||
@@ -154,13 +171,7 @@ class MainShell:
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
self._driver.refresh()
|
||||
|
||||
def _clickTab(
|
||||
self,
|
||||
locator: tuple,
|
||||
) -> None:
|
||||
|
||||
WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(locator)
|
||||
).click()
|
||||
try:
|
||||
self._driver.refresh()
|
||||
except (TimeoutException, WebDriverException):
|
||||
return
|
||||
|
||||
@@ -44,8 +44,6 @@ class RecordsView:
|
||||
return self._driver.find_elements(*self.RECORDS_LIST)
|
||||
except TimeoutException:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def getRecordTimeElement(
|
||||
self,
|
||||
@@ -71,8 +69,6 @@ class RecordsView:
|
||||
)
|
||||
except TimeoutException:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
try:
|
||||
more_btn = self._driver.find_element(*self.MORE_BTN)
|
||||
if more_btn.is_displayed() and more_btn.is_enabled():
|
||||
@@ -82,8 +78,6 @@ class RecordsView:
|
||||
return False
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def getRecordText(
|
||||
self,
|
||||
|
||||
+48
-55
@@ -53,6 +53,50 @@ class ReserveView:
|
||||
|
||||
self._driver = driver
|
||||
|
||||
def _clickOptionByJS(
|
||||
self,
|
||||
trigger_id: str,
|
||||
option_css: str,
|
||||
) -> bool:
|
||||
|
||||
script = f"""
|
||||
try {{
|
||||
var trigger = document.getElementById('{trigger_id}');
|
||||
if (trigger) {{
|
||||
trigger.click();
|
||||
var option = document.querySelector("{option_css}");
|
||||
if (option) {{
|
||||
option.click();
|
||||
return true;
|
||||
}}
|
||||
return false;
|
||||
}}
|
||||
return false;
|
||||
}} catch (e) {{
|
||||
return false;
|
||||
}}
|
||||
"""
|
||||
result = self._driver.execute_script(script)
|
||||
time.sleep(0.1)
|
||||
return result
|
||||
|
||||
def _clickOption(
|
||||
self,
|
||||
trigger: tuple,
|
||||
option: tuple,
|
||||
) -> bool:
|
||||
|
||||
try:
|
||||
WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(trigger)
|
||||
).click()
|
||||
WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(option)
|
||||
).click()
|
||||
return True
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
def selectDate(
|
||||
self,
|
||||
date_str: str,
|
||||
@@ -109,21 +153,15 @@ class ReserveView:
|
||||
).click()
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
try:
|
||||
WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable((By.ID, self.ROOM_BTN_FMT.format(room=room)))
|
||||
).click()
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
try:
|
||||
return SeatMapDialog(self._driver)
|
||||
except (TimeoutException):
|
||||
return None
|
||||
except Exception:
|
||||
except TimeoutException:
|
||||
return None
|
||||
|
||||
def submitReserve(
|
||||
@@ -137,57 +175,12 @@ class ReserveView:
|
||||
return True
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def refresh(
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
self._driver.refresh()
|
||||
|
||||
def _clickOptionByJS(
|
||||
self,
|
||||
trigger_id: str,
|
||||
option_css: str,
|
||||
) -> bool:
|
||||
|
||||
script = f"""
|
||||
try {{
|
||||
var trigger = document.getElementById('{trigger_id}');
|
||||
if (trigger) {{
|
||||
trigger.click();
|
||||
var option = document.querySelector("{option_css}");
|
||||
if (option) {{
|
||||
option.click();
|
||||
return true;
|
||||
}}
|
||||
return false;
|
||||
}}
|
||||
return false;
|
||||
}} catch (e) {{
|
||||
return false;
|
||||
}}
|
||||
"""
|
||||
result = self._driver.execute_script(script)
|
||||
time.sleep(0.1)
|
||||
return result
|
||||
|
||||
def _clickOption(
|
||||
self,
|
||||
trigger: tuple,
|
||||
option: tuple,
|
||||
) -> bool:
|
||||
|
||||
try:
|
||||
WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(trigger)
|
||||
).click()
|
||||
WebDriverWait(self._driver, 2).until(
|
||||
EC.element_to_be_clickable(option)
|
||||
).click()
|
||||
return True
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
self._driver.refresh()
|
||||
except TimeoutException:
|
||||
return
|
||||
|
||||
+10
-23
@@ -7,26 +7,13 @@ This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from pages.AutoLib import AutoLib
|
||||
from pages.LoginPage import LoginPage
|
||||
from pages.MainShell import MainShell
|
||||
from pages.ReserveView import ReserveView
|
||||
from pages.RecordsView import RecordsView
|
||||
from pages.components.SeatMapDialog import SeatMapDialog
|
||||
from pages.components.TimeSelectDialog import TimeSelectDialog
|
||||
from pages.components.ReserveResultDialog import ReserveResultDialog
|
||||
from pages.components.CheckinResultDialog import CheckinResultDialog
|
||||
from pages.components.RenewDialog import RenewDialog
|
||||
|
||||
__all__ = [
|
||||
"AutoLib",
|
||||
"LoginPage",
|
||||
"MainShell",
|
||||
"ReserveView",
|
||||
"RecordsView",
|
||||
"SeatMapDialog",
|
||||
"TimeSelectDialog",
|
||||
"ReserveResultDialog",
|
||||
"CheckinResultDialog",
|
||||
"RenewDialog",
|
||||
]
|
||||
from .AutoLib import AutoLib
|
||||
from .LoginPage import LoginPage
|
||||
from .MainShell import MainShell
|
||||
from .ReserveView import ReserveView
|
||||
from .RecordsView import RecordsView
|
||||
from .components.SeatMapDialog import SeatMapDialog
|
||||
from .components.TimeSelectDialog import TimeSelectDialog
|
||||
from .components.ReserveResultDialog import ReserveResultDialog
|
||||
from .components.CheckinResultDialog import CheckinResultDialog
|
||||
from .components.RenewDialog import RenewDialog
|
||||
|
||||
@@ -45,9 +45,8 @@ class CheckinResultDialog(Dialog):
|
||||
self._waitPresence(self.RESULT_MSG)
|
||||
el = self._find(*self.RESULT_MSG)
|
||||
return el.text
|
||||
except (TimeoutException, NoSuchElementException, StaleElementReferenceException):
|
||||
return ""
|
||||
except Exception:
|
||||
except (TimeoutException, NoSuchElementException,
|
||||
StaleElementReferenceException):
|
||||
return ""
|
||||
|
||||
def getDetails(
|
||||
@@ -59,8 +58,6 @@ class CheckinResultDialog(Dialog):
|
||||
return [el.text for el in elements if el.text.strip()]
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
return []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def clickOk(
|
||||
self,
|
||||
@@ -69,7 +66,5 @@ class CheckinResultDialog(Dialog):
|
||||
try:
|
||||
self._waitClickable(self.OK_BTN).click()
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException, ElementNotInteractableException):
|
||||
return False
|
||||
except Exception:
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
@@ -10,6 +10,7 @@ See the LICENSE file for details.
|
||||
from selenium.common.exceptions import (
|
||||
ElementNotInteractableException,
|
||||
NoSuchElementException,
|
||||
StaleElementReferenceException,
|
||||
TimeoutException,
|
||||
)
|
||||
from selenium.webdriver.common.by import By
|
||||
@@ -50,9 +51,7 @@ class RenewDialog(Dialog):
|
||||
self._waitVisible(self.ROOT)
|
||||
self._waitPresence(self.MESSAGE_HEAD)
|
||||
self._waitPresence(self.RESULT_MSG)
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return False
|
||||
except Exception:
|
||||
except TimeoutException:
|
||||
return False
|
||||
head_msg = self._find(*self.MESSAGE_HEAD).text.strip()
|
||||
if "警告" in head_msg:
|
||||
@@ -60,9 +59,7 @@ class RenewDialog(Dialog):
|
||||
try:
|
||||
self._waitAllPresence(self.TIME_OPTS)
|
||||
self._waitPresence(self.OK_BTN)
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return False
|
||||
except Exception:
|
||||
except TimeoutException:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -70,13 +67,19 @@ class RenewDialog(Dialog):
|
||||
self,
|
||||
) -> str:
|
||||
|
||||
return self._find(*self.MESSAGE_HEAD).text.strip()
|
||||
try:
|
||||
return self._find(*self.MESSAGE_HEAD).text.strip()
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
return ""
|
||||
|
||||
def getResultMessage(
|
||||
self,
|
||||
) -> str:
|
||||
|
||||
return self._find(*self.RESULT_MSG).text.strip()
|
||||
try:
|
||||
return self._find(*self.RESULT_MSG).text.strip()
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
return ""
|
||||
|
||||
def getTimeOptions(
|
||||
self,
|
||||
@@ -101,7 +104,10 @@ class RenewDialog(Dialog):
|
||||
prefer_earlier,
|
||||
)
|
||||
if result.selected_index >= 0:
|
||||
all_time_opts[result.selected_index].click()
|
||||
try:
|
||||
all_time_opts[result.selected_index].click()
|
||||
except (ElementNotInteractableException, StaleElementReferenceException):
|
||||
return TimeSelectionResult(free_times=result.free_times)
|
||||
return result
|
||||
|
||||
def getOkButton(
|
||||
@@ -117,8 +123,5 @@ class RenewDialog(Dialog):
|
||||
try:
|
||||
self._find(*self.OK_BTN).click()
|
||||
return True
|
||||
except (NoSuchElementException, TimeoutException,
|
||||
ElementNotInteractableException):
|
||||
return False
|
||||
except Exception:
|
||||
except (NoSuchElementException, ElementNotInteractableException):
|
||||
return False
|
||||
|
||||
@@ -45,8 +45,6 @@ class ReserveResultDialog(Dialog):
|
||||
return self._find(*self._titleLocator()).text
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
return ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def isSuccess(
|
||||
self,
|
||||
@@ -77,5 +75,3 @@ class ReserveResultDialog(Dialog):
|
||||
return [el.text for el in elements if el.text.strip()]
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
return []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
@@ -43,9 +43,7 @@ class SeatMapDialog(Dialog):
|
||||
|
||||
try:
|
||||
self._waitAllPresence(self.SEAT_ITEMS)
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return None
|
||||
except Exception:
|
||||
except TimeoutException:
|
||||
return None
|
||||
try:
|
||||
seat_el = self._find(By.ID, f"seat_{int(seat_id):03d}")
|
||||
@@ -58,8 +56,6 @@ class SeatMapDialog(Dialog):
|
||||
except (NoSuchElementException, ValueError, TimeoutException,
|
||||
ElementNotInteractableException, StaleElementReferenceException):
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
all_seats = self._findAll(*self.SEAT_ITEMS)
|
||||
seat_id_upper = seat_id.lstrip('0').upper()
|
||||
@@ -76,5 +72,3 @@ class SeatMapDialog(Dialog):
|
||||
except (NoSuchElementException, TimeoutException,
|
||||
ElementNotInteractableException, StaleElementReferenceException):
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -13,7 +13,8 @@ import logging
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
|
||||
from selenium.common.exceptions import (
|
||||
NoSuchElementException,
|
||||
ElementNotInteractableException,
|
||||
StaleElementReferenceException,
|
||||
TimeoutException,
|
||||
)
|
||||
from selenium.webdriver.common.by import By
|
||||
@@ -106,9 +107,7 @@ class TimeSelectDialog(Dialog):
|
||||
self._waitAllPresence(
|
||||
(By.CSS_SELECTOR, f"#{time_id} ul li a")
|
||||
)
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
return []
|
||||
except Exception:
|
||||
except TimeoutException:
|
||||
return []
|
||||
return self._findAll(
|
||||
By.CSS_SELECTOR,
|
||||
@@ -133,7 +132,10 @@ class TimeSelectDialog(Dialog):
|
||||
prefer_earlier,
|
||||
)
|
||||
if result.selected_index >= 0:
|
||||
all_time_opts[result.selected_index].click()
|
||||
try:
|
||||
all_time_opts[result.selected_index].click()
|
||||
except (ElementNotInteractableException, StaleElementReferenceException):
|
||||
return TimeSelectionResult(free_times=result.free_times)
|
||||
return result
|
||||
|
||||
def selectTimeRange(
|
||||
|
||||
@@ -7,16 +7,8 @@ This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from pages.components.SeatMapDialog import SeatMapDialog
|
||||
from pages.components.TimeSelectDialog import TimeSelectDialog
|
||||
from pages.components.ReserveResultDialog import ReserveResultDialog
|
||||
from pages.components.CheckinResultDialog import CheckinResultDialog
|
||||
from pages.components.RenewDialog import RenewDialog
|
||||
|
||||
__all__ = [
|
||||
"SeatMapDialog",
|
||||
"TimeSelectDialog",
|
||||
"ReserveResultDialog",
|
||||
"CheckinResultDialog",
|
||||
"RenewDialog",
|
||||
]
|
||||
from .SeatMapDialog import SeatMapDialog
|
||||
from .TimeSelectDialog import TimeSelectDialog
|
||||
from .ReserveResultDialog import ReserveResultDialog
|
||||
from .CheckinResultDialog import CheckinResultDialog
|
||||
from .RenewDialog import RenewDialog
|
||||
|
||||
@@ -10,6 +10,7 @@ See the LICENSE file for details.
|
||||
import queue
|
||||
|
||||
from selenium.common.exceptions import (
|
||||
ElementNotInteractableException,
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
)
|
||||
@@ -83,9 +84,6 @@ class CheckinFlow(MsgBase):
|
||||
dialog.clickOk()
|
||||
self._showTrace(f"用户 {username} 签到失败 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
self._showTrace("签到时发生未知错误 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
except Exception:
|
||||
except (TimeoutException, NoSuchElementException, ElementNotInteractableException):
|
||||
self._showTrace("签到时发生未知错误 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
|
||||
@@ -39,6 +39,28 @@ class RenewFlow(MsgBase):
|
||||
self._driver: WebDriver = driver
|
||||
self._shell: MainShell = shell
|
||||
|
||||
def _validateRenewTime(
|
||||
self,
|
||||
end_time: str,
|
||||
target_renew_mins: int,
|
||||
) -> bool:
|
||||
|
||||
if target_renew_mins > self.LIBRARY_CLOSE_MINS:
|
||||
actual_renew_duration = self.LIBRARY_CLOSE_MINS - timeStrToMins(end_time)
|
||||
if actual_renew_duration <= 0:
|
||||
self._showTrace(
|
||||
f"当前结束时间 {end_time} 已接近闭馆时间,无法续约 !", self.TraceLevel.ERROR
|
||||
)
|
||||
return False
|
||||
self._showTrace(
|
||||
f"续约时间已调整至闭馆时间 "
|
||||
f"{minsToTimeStr(self.LIBRARY_CLOSE_MINS)},"
|
||||
f"实际续约时长为 "
|
||||
f"{actual_renew_duration // 60} 小时 "
|
||||
f"{actual_renew_duration % 60} 分钟"
|
||||
)
|
||||
return True
|
||||
|
||||
def execute(
|
||||
self,
|
||||
username: str,
|
||||
@@ -106,37 +128,7 @@ class RenewFlow(MsgBase):
|
||||
self._showTrace(f"当前可供续约的时间有: {result.free_times}")
|
||||
self._shell.refresh()
|
||||
return False
|
||||
except (NoSuchElementException, TimeoutException) as e:
|
||||
except (NoSuchElementException, TimeoutException, ElementNotInteractableException) as e:
|
||||
self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
except (ElementNotInteractableException) as e:
|
||||
self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
except Exception as e:
|
||||
self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
|
||||
def _validateRenewTime(
|
||||
self,
|
||||
end_time: str,
|
||||
target_renew_mins: int,
|
||||
) -> bool:
|
||||
|
||||
if target_renew_mins > self.LIBRARY_CLOSE_MINS:
|
||||
actual_renew_duration = self.LIBRARY_CLOSE_MINS - timeStrToMins(end_time)
|
||||
if actual_renew_duration <= 0:
|
||||
self._showTrace(
|
||||
f"当前结束时间 {end_time} 已接近闭馆时间,无法续约 !", self.TraceLevel.ERROR
|
||||
)
|
||||
return False
|
||||
self._showTrace(
|
||||
f"续约时间已调整至闭馆时间 "
|
||||
f"{minsToTimeStr(self.LIBRARY_CLOSE_MINS)},"
|
||||
f"实际续约时长为 "
|
||||
f"{actual_renew_duration // 60} 小时 "
|
||||
f"{actual_renew_duration % 60} 分钟"
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -12,7 +12,6 @@ from dataclasses import dataclass
|
||||
|
||||
from selenium.common.exceptions import (
|
||||
ElementNotInteractableException,
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
)
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
@@ -70,10 +69,7 @@ class ReserveFlow(MsgBase):
|
||||
|
||||
try:
|
||||
view = self._shell.gotoReserveView()
|
||||
except (NoSuchElementException, TimeoutException) as e:
|
||||
self._showTrace(f"加载预约选座页面失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return False
|
||||
except Exception as e:
|
||||
except (TimeoutException, ElementNotInteractableException) as e:
|
||||
self._showTrace(f"加载预约选座页面失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return False
|
||||
if not view.selectDate(ctx.date):
|
||||
@@ -140,8 +136,6 @@ class ReserveFlow(MsgBase):
|
||||
self._showTrace("预约结果加载失败 !", self.TraceLevel.ERROR)
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
self._showTrace("预约提交失败 !", self.TraceLevel.ERROR)
|
||||
except Exception:
|
||||
self._showTrace("预约提交失败 !", self.TraceLevel.ERROR)
|
||||
if not submit_reserve and have_hover_on_page:
|
||||
view.refresh()
|
||||
if reserve_success:
|
||||
|
||||
@@ -7,12 +7,6 @@ This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from pages.flows.ReserveFlow import ReserveFlow
|
||||
from pages.flows.CheckinFlow import CheckinFlow
|
||||
from pages.flows.RenewFlow import RenewFlow
|
||||
|
||||
__all__ = [
|
||||
"ReserveFlow",
|
||||
"CheckinFlow",
|
||||
"RenewFlow",
|
||||
]
|
||||
from .ReserveFlow import ReserveFlow
|
||||
from .CheckinFlow import CheckinFlow
|
||||
from .RenewFlow import RenewFlow
|
||||
|
||||
@@ -9,11 +9,5 @@ See the LICENSE file for details.
|
||||
"""
|
||||
from pages.strategies.TimeSelectMaker import (
|
||||
minsToTimeStr,
|
||||
timeStrToMins
|
||||
timeStrToMins,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"minsToTimeStr",
|
||||
"timeStrToMins",
|
||||
]
|
||||
@@ -11,11 +11,6 @@ import base64
|
||||
import queue
|
||||
|
||||
import ddddocr
|
||||
from selenium.common.exceptions import (
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
)
|
||||
|
||||
from base.MsgBase import MsgBase
|
||||
from pages.LoginPage import LoginPage
|
||||
|
||||
@@ -38,6 +33,9 @@ class CaptchaSolver(MsgBase):
|
||||
|
||||
try:
|
||||
img_src = login_page.getCaptchaImageSrc()
|
||||
if img_src is None:
|
||||
self._showTrace("验证码图片元素定位时发生错误 !", self.TraceLevel.ERROR)
|
||||
return ""
|
||||
base64_str = img_src.split(',', 1)[1]
|
||||
captcha_img = base64.b64decode(base64_str)
|
||||
captcha_text = self._ocr.classification(captcha_img)
|
||||
@@ -45,15 +43,9 @@ class CaptchaSolver(MsgBase):
|
||||
self._showTrace(f"识别到验证码为 : '{captcha_text}'", 20, no_log=True)
|
||||
if len(captcha_text) != 4:
|
||||
self._showLog("识别到的验证码长度不等于 4 个字符 !", self.TraceLevel.WARNING)
|
||||
raise Exception("识别到的验证码长度不等于 4 个字符 !")
|
||||
return ""
|
||||
return captcha_text
|
||||
except (NoSuchElementException, TimeoutException) as e:
|
||||
self._showTrace(f"验证码识别失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return ""
|
||||
except (ValueError, OSError) as e:
|
||||
self._showTrace(f"验证码识别失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return ""
|
||||
except Exception as e:
|
||||
except ValueError as e:
|
||||
self._showTrace(f"验证码识别失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return ""
|
||||
|
||||
@@ -61,20 +53,13 @@ class CaptchaSolver(MsgBase):
|
||||
self,
|
||||
) -> str:
|
||||
|
||||
try:
|
||||
self._showMsg("请输入验证码:")
|
||||
captcha_text = self._waitMsg(timeout=15)
|
||||
self._showTrace(f"输入的验证码为 : '{captcha_text}'", 20, no_log=True)
|
||||
if len(captcha_text) != 4:
|
||||
self._showLog("输入的验证码长度不等于 4 个字符 !", self.TraceLevel.WARNING)
|
||||
raise Exception("输入的验证码长度不等于 4 个字符 !")
|
||||
return captcha_text
|
||||
except ValueError as e:
|
||||
self._showTrace(f"输入验证码失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return ""
|
||||
except Exception as e:
|
||||
self._showTrace(f"输入验证码失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
self._showMsg("请输入验证码:")
|
||||
captcha_text = self._waitMsg(timeout=15)
|
||||
self._showTrace(f"输入的验证码为 : '{captcha_text}'", 20, no_log=True)
|
||||
if len(captcha_text) != 4:
|
||||
self._showLog("输入的验证码长度不等于 4 个字符 !", self.TraceLevel.WARNING)
|
||||
return ""
|
||||
return captcha_text
|
||||
|
||||
def solveCaptcha(
|
||||
self,
|
||||
|
||||
@@ -112,20 +112,21 @@ class RecordChecker(MsgBase):
|
||||
try:
|
||||
time_element = records_view.getRecordTimeElement(reservation)
|
||||
info_elements = records_view.getRecordInfoElements(reservation)
|
||||
except (NoSuchElementException, TimeoutException, StaleElementReferenceException):
|
||||
except (NoSuchElementException, StaleElementReferenceException):
|
||||
return {
|
||||
"date": "",
|
||||
"time": {"begin": "", "end": ""},
|
||||
"info": {"location": "", "status": ""},
|
||||
}
|
||||
except Exception:
|
||||
try:
|
||||
time_data = self._decodeReserveTime(time_element)
|
||||
info_data = self._decodeReserveInfo(info_elements)
|
||||
except StaleElementReferenceException:
|
||||
return {
|
||||
"date": "",
|
||||
"time": {"begin": "", "end": ""},
|
||||
"info": {"location": "", "status": ""},
|
||||
}
|
||||
time_data = self._decodeReserveTime(time_element)
|
||||
info_data = self._decodeReserveInfo(info_elements)
|
||||
return {
|
||||
"date": time_data["date"],
|
||||
"time": time_data["time"],
|
||||
@@ -152,7 +153,10 @@ class RecordChecker(MsgBase):
|
||||
|
||||
records_view = shell.gotoRecordsView()
|
||||
for _ in range(max_check_times):
|
||||
reservations = records_view.loadRecords()
|
||||
try:
|
||||
reservations = records_view.loadRecords()
|
||||
except TimeoutException:
|
||||
reservations = None
|
||||
if reservations is None:
|
||||
return None
|
||||
for reservation in reservations[checked_count:]:
|
||||
|
||||
@@ -88,11 +88,22 @@ class ReserveChecker(MsgBase):
|
||||
) -> bool:
|
||||
|
||||
cur_time = time.strftime("%H:%M", time.localtime())
|
||||
cur_date = time.strftime("%Y-%m-%d", time.localtime())
|
||||
if reserve_info.get("begin_time") is None:
|
||||
reserve_info["begin_time"] = {}
|
||||
if "time" not in reserve_info["begin_time"]:
|
||||
reserve_info["begin_time"]["time"] = cur_time
|
||||
self._showTrace(f"开始时间未指定, 自动设置为当前时间: {cur_time}")
|
||||
elif reserve_info.get("date") == cur_date:
|
||||
begin_mins = timeStrToMins(reserve_info["begin_time"]["time"])
|
||||
cur_mins = timeStrToMins(cur_time)
|
||||
if begin_mins < cur_mins:
|
||||
self._showTrace(
|
||||
f"开始时间 {reserve_info['begin_time']['time']} 已过当前时间 {cur_time}, "
|
||||
f"自动调整为当前时间",
|
||||
self.TraceLevel.WARNING,
|
||||
)
|
||||
reserve_info["begin_time"]["time"] = cur_time
|
||||
if "max_diff" not in reserve_info["begin_time"]:
|
||||
reserve_info["begin_time"]["max_diff"] = 30
|
||||
self._showTrace("开始时间最大时间差未指定, 自动设置为 30 分钟")
|
||||
|
||||
@@ -7,12 +7,6 @@ This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from pages.services.CaptchaSolver import CaptchaSolver
|
||||
from pages.services.ReserveChecker import ReserveChecker
|
||||
from pages.services.RecordChecker import RecordChecker
|
||||
|
||||
__all__ = [
|
||||
"CaptchaSolver",
|
||||
"ReserveChecker",
|
||||
"RecordChecker",
|
||||
]
|
||||
from .CaptchaSolver import CaptchaSolver
|
||||
from .ReserveChecker import ReserveChecker
|
||||
from .RecordChecker import RecordChecker
|
||||
|
||||
@@ -7,7 +7,7 @@ This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
from pages.strategies.TimeSelectMaker import (
|
||||
from .TimeSelectMaker import (
|
||||
TimeSelectMaker,
|
||||
TimeDecisionMaker,
|
||||
TimeOptionReader,
|
||||
@@ -17,14 +17,3 @@ from pages.strategies.TimeSelectMaker import (
|
||||
TimeSelectionResult,
|
||||
TimeRangeResult,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"TimeSelectMaker",
|
||||
"TimeDecisionMaker",
|
||||
"TimeOptionReader",
|
||||
"ReserveTimeReader",
|
||||
"RenewTimeReader",
|
||||
"TimeOption",
|
||||
"TimeSelectionResult",
|
||||
"TimeRangeResult",
|
||||
]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Utils module for the AutoLibrary project.
|
||||
Copyright (c) 2026 KenanZhu.
|
||||
All rights reserved.
|
||||
|
||||
Here are the classes and modules in this package:
|
||||
- TimerUtils: Timer utils class for the AutoLibrary project.
|
||||
- JSONReader: JSON reader class for the AutoLibrary project.
|
||||
- JSONWriter: JSON writer class for the AutoLibrary project.
|
||||
This software is provided "as is", without any warranty of any kind.
|
||||
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||
See the LICENSE file for details.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user