1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 07:23:03 +08:00

Compare commits

...

19 Commits

Author SHA1 Message Date
github-actions[bot] dd48c8a01c chore(release): v1.0.1 [auto release commit] 2026-01-02 16:39:02 +00:00
KenanZhu 924db3bdcc ci(workflows): 新增基于 Github Actions 的 CI/CD 工作流控制 2026-01-03 00:35:16 +08:00
KenanZhu 1e5452d411 refactor(ALAboutDialog): 更改关于对话框的显示内容
主要包括版本号,提交信息,构建时间等。为 CI/CD 流程添加相关信息占位。
2026-01-03 00:33:01 +08:00
KenanZhu 1b378e5aaa fix(LibLogin): 修复优化验证码处理逻辑,避免无效请求。并完善手动输入验证码功能。 2026-01-02 17:37:17 +08:00
KenanZhu e069efb2ea fix(ALConfigWidget): 修复用户配置列表中,选中用户项时禁用该用户所在用户组时,该用户项未同步禁用状态仍保持被选中的问题 2026-01-02 00:44:24 +08:00
KenanZhu 407d25570a fix(ALMainWorkers): 修复 AutoLibWorker 中基础检查未通过时,运行线程错误返回导致结束信号未发送的问题 2026-01-02 00:30:37 +08:00
KenanZhu bfcb65f56a fix(gui.ALMainWindow): 修改了 setControlButtons 方法,防止按钮状态的意外更改 2025-12-31 10:15:57 +08:00
KenanZhu cde1e966e7 chore(gui.batchs): 将编译脚本的错误命名 complie_*.bat/sh 修改为 compile_*.bat/sh 2025-12-27 23:12:37 +08:00
KenanZhu 8c4f463889 docs(readme): 修改一些文档的不通顺不准确描述,新增捐助链接 2025-12-27 21:57:38 +08:00
KenanZhu 39867cc20c docs(readme): 修改文档的歧义和其它不准确描述 2025-12-27 15:43:25 +08:00
KenanZhu 149910d628 chore(release): v1.0.0 2025-12-22 15:24:31 +08:00
KenanZhu 2a7ed099bf docs(readme.md): 更新 readme 文档 2025-12-22 15:23:56 +08:00
KenanZhu 473f32ca29 chore(batchs): 新增界面资源和应用资源的编译脚本 2025-12-22 15:23:47 +08:00
KenanZhu 580052f1e3 chore(icons): 添加多种图标格式,将当前的图标尺寸从 1024x1024 调整为 32x32 2025-12-22 11:55:33 +08:00
KenanZhu 6abf530307 optimize(ALTimerTaskWidget, ALConfigWidget): 优化定时器和用户设置的任务列表排序 2025-12-22 11:53:45 +08:00
KenanZhu 577c651ef8 feat(ALMainWindow): 引入对新增定时器任务状态 - 执行失败的处理支持 (#18ae949)
同时,为了统一消息处理,我们将 ALMainWorkers 中的原信号
槽处理的消息逻辑更改为使用继承的 MsgBase 类的 showTrace 方法
2025-12-13 14:27:46 +08:00
KenanZhu 18ae949900 feat(ALTimerTaskWidget): 新增定时器任务状态 - 执行失败 2025-12-13 14:22:28 +08:00
KenanZhu ca9059d1db refactor(AutoLib): 初始化 AutoLib 时,发生错误则抛出异常 2025-12-13 14:21:26 +08:00
KenanZhu ad4deae0c6 fix(ALMainWindow): 修复停止时的按钮状态重置问题
函数更改于(#9255eec)
2025-12-13 14:15:28 +08:00
26 changed files with 980 additions and 121 deletions
+236
View File
@@ -0,0 +1,236 @@
name: Build and Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
update-version-info:
uses: ./.github/workflows/update-version-info.yml
permissions:
contents: write
with:
tag_name: ${{ github.ref_name }}
ref: ${{ github.ref }}
commit-and-move-tag:
needs: update-version-info
if: ${{ needs.update-version-info.outputs.has_changes == 'true' }}
uses: ./.github/workflows/commit-and-move-tag.yml
permissions:
contents: write
with:
tag_name: ${{ needs.update-version-info.outputs.tag_name }}
version: ${{ needs.update-version-info.outputs.version }}
file_path: src/gui/ALVersionInfo.py
build-and-release:
runs-on: windows-latest
needs: [update-version-info, commit-and-move-tag]
if: always() && needs.update-version-info.result == 'success'
permissions:
contents: write
steps:
- name: Checkout code with updated version info
uses: actions/checkout@v4
with:
ref: main
- name: Get version info from previous job
id: get_tag
run: |
$tagName = "${{ needs.update-version-info.outputs.tag_name }}"
$version = "${{ needs.update-version-info.outputs.version }}"
echo "TAG_NAME=$tagName" >> $env:GITHUB_OUTPUT
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
Write-Host "✓ Tag: $tagName"
Write-Host "✓ Version: $version"
shell: pwsh
- name: Verify ALVersionInfo.py was updated
run: |
$versionInfoFile = "src/gui/ALVersionInfo.py"
Write-Host "Verifying $versionInfoFile content:"
Write-Host "================================"
Get-Content $versionInfoFile | Write-Host
Write-Host "================================"
shell: pwsh
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirement.txt
- name: Fix ddddocr compatibility and copy model files
run: |
$ddddocrPath = python -c "import ddddocr, os; print(os.path.dirname(ddddocr.__file__))"
Write-Host "ddddocr package location: $ddddocrPath"
$initFile = Join-Path $ddddocrPath "__init__.py"
if (Test-Path $initFile) {
Write-Host "Fixing ddddocr compatibility in: $initFile"
(Get-Content $initFile) -replace 'Image\.ANTIALIAS', 'Image.Resampling.LANCZOS' | Set-Content $initFile
Write-Host "✓ Fixed: Image.ANTIALIAS -> Image.Resampling.LANCZOS"
} else {
Write-Error "✗ ddddocr __init__.py not found"
exit 1
}
if (-not (Test-Path "model")) {
New-Item -ItemType Directory -Path "model" | Out-Null
Write-Host "✓ Created model directory"
}
$onnxSource = Join-Path $ddddocrPath "common.onnx"
$onnxDest = "model/common.onnx"
if (Test-Path $onnxSource) {
Copy-Item $onnxSource $onnxDest -Force
Write-Host "✓ Copied ONNX model from: $onnxSource"
Write-Host "✓ ONNX model copied to: $onnxDest"
} else {
Write-Error "✗ ONNX model not found in ddddocr package: $onnxSource"
exit 1
}
if (Test-Path $onnxDest) {
$fileSize = (Get-Item $onnxDest).Length / 1MB
Write-Host "✓ Model file verified: $onnxDest (Size: $([math]::Round($fileSize, 2)) MB)"
} else {
Write-Error "✗ Failed to copy model file"
exit 1
}
shell: pwsh
- name: Compile Qt UI files
run: |
cd src/gui/batchs
./compile_ui.bat
shell: cmd
- name: Compile Qt Resource files
run: |
cd src/gui/batchs
./compile_rc.bat
shell: cmd
- name: Generate Main.spec dynamically
run: |
$version = "${{ steps.get_tag.outputs.VERSION }}"
$exeName = "AutoLibrary-$version"
Write-Host "Generating Main.spec for version: $version"
Write-Host "Executable name: $exeName"
$specLines = @(
"# -*- mode: python ; coding: utf-8 -*-"
""
""
"a = Analysis("
" ['src\\Main.py'],"
" pathex=[],"
" binaries=[],"
" datas=["
" ('model\\common.onnx', 'ddddocr'),"
" ('src\\gui\\icons\\AutoLibrary_32x32.ico', 'gui\\icons'),"
" ],"
" hiddenimports=[],"
" hookspath=[],"
" hooksconfig={},"
" runtime_hooks=[],"
" excludes=[],"
" noarchive=False,"
" optimize=0,"
")"
"pyz = PYZ(a.pure)"
""
"exe = EXE("
" pyz,"
" a.scripts,"
" a.binaries,"
" a.datas,"
" [],"
" name='$exeName',"
" debug=False,"
" bootloader_ignore_signals=False,"
" strip=False,"
" upx=True,"
" upx_exclude=[],"
" runtime_tmpdir=None,"
" console=False,"
" disable_windowed_traceback=False,"
" argv_emulation=False,"
" target_arch=None,"
" codesign_identity=None,"
" entitlements_file=None,"
" icon=['src\\gui\\icons\\AutoLibrary_32x32.ico'],"
")"
)
$specLines | Out-File -FilePath "Main.spec" -Encoding UTF8
Write-Host "✓ Main.spec generated successfully"
Write-Host "`n=== Generated Main.spec ==="
Get-Content "Main.spec" | Write-Host
Write-Host "==========================`n"
shell: pwsh
- name: Build with PyInstaller
run: |
pyinstaller Main.spec
- name: Create Release Archive
run: |
$tagName = "${{ steps.get_tag.outputs.TAG_NAME }}"
$version = "${{ steps.get_tag.outputs.VERSION }}"
$exeName = "AutoLibrary-$version.exe"
$zipName = "AutoLibrary.$tagName-windows-x86_64.zip"
Write-Host "Looking for executable: dist/$exeName"
if (Test-Path "dist/$exeName") {
Compress-Archive -Path "dist/$exeName" -DestinationPath $zipName
Write-Host "✓ Created release archive: $zipName"
} else {
Write-Error "✗ Executable not found: dist/$exeName"
Write-Host "Files in dist directory:"
Get-ChildItem "dist" | ForEach-Object { Write-Host " - $($_.Name)" }
exit 1
}
shell: pwsh
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.get_tag.outputs.TAG_NAME }}
name: AutoLibrary ${{ steps.get_tag.outputs.TAG_NAME }}
files: |
AutoLibrary.${{ steps.get_tag.outputs.TAG_NAME }}-windows-x86_64.zip
draft: false
prerelease: false
generate_release_notes: true
body: |
### 下载获取
- **Windows x86_64**: `AutoLibrary.${{ steps.get_tag.outputs.TAG_NAME }}-windows-x86_64.zip`
### 如何使用
1. 下载 `AutoLibrary.${{ steps.get_tag.outputs.TAG_NAME }}-windows-x86_64.zip` 文件
2. 解压到任意目录
3. 下载对应浏览器的驱动文件
4. 运行 `AutoLibrary-${{ steps.get_tag.outputs.VERSION }}.exe` (首次运行会初始化配置文件)
5. 按照提示操作即可
更多详情请访问 [AutoLibrary 网站](http://autolibrary.cv) 和查看 [帮助手册](https://autolibrary.cv/docs/manual_lists.html)
---
**完整更新日志见下方自动生成的 Release Notes**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+102
View File
@@ -0,0 +1,102 @@
name: Commit and Move Tag
on:
workflow_call:
inputs:
tag_name:
description: 'Tag name to move (e.g., v1.0.0)'
required: true
type: string
version:
description: 'Version number for commit message'
required: true
type: string
file_path:
description: 'File path to commit'
required: true
type: string
outputs:
new_commit_sha:
description: 'The new commit SHA after moving the tag'
value: ${{ jobs.commit-and-move-tag.outputs.new_commit_sha }}
jobs:
commit-and-move-tag:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
new_commit_sha: ${{ steps.commit_info.outputs.commit_sha }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Download modified file
uses: actions/download-artifact@v4
with:
name: updated-version-info
path: downloaded-file/
- name: Replace file with updated version
run: |
FILE_PATH="${{ inputs.file_path }}"
FILE_NAME=$(basename "$FILE_PATH")
TARGET_DIR=$(dirname "$FILE_PATH")
mkdir -p "$TARGET_DIR"
cp "downloaded-file/$FILE_NAME" "$FILE_PATH"
echo "✓ File replaced: $FILE_PATH"
echo ""
echo "=== Updated file content ==="
cat "$FILE_PATH"
echo "============================"
- name: Commit changes
id: commit_changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
FILE_PATH="${{ inputs.file_path }}"
VERSION="${{ inputs.version }}"
if [ ! -f "$FILE_PATH" ]; then
echo "✗ Error: File $FILE_PATH not found"
exit 1
fi
git add "$FILE_PATH"
git commit -m "chore(release): v${VERSION} [auto release commit]"
echo "✓ Changes committed"
- name: Push to main branch
run: |
MAIN_BRANCH=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
if [ -z "$MAIN_BRANCH" ]; then
MAIN_BRANCH="main"
fi
echo "Pushing to branch: ${MAIN_BRANCH}"
git push origin HEAD:${MAIN_BRANCH}
echo "✓ Changes pushed to ${MAIN_BRANCH}"
- name: Move tag to new commit
run: |
TAG_NAME="${{ inputs.tag_name }}"
echo "Moving tag ${TAG_NAME} to the new commit..."
git tag -f ${TAG_NAME}
git push origin ${TAG_NAME} --force
echo "✓ Tag ${TAG_NAME} moved to commit $(git rev-parse --short HEAD)"
- name: Output commit info
id: commit_info
run: |
COMMIT_SHA=$(git rev-parse --short HEAD)
echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT
echo "✓ New commit SHA: $COMMIT_SHA"
+115
View File
@@ -0,0 +1,115 @@
name: Update Version Info
on:
workflow_call:
inputs:
tag_name:
description: 'Tag name (e.g., v1.0.0)'
required: true
type: string
ref:
description: 'Git ref to checkout'
required: true
type: string
outputs:
tag_name:
description: 'The tag name'
value: ${{ jobs.update-version-info.outputs.tag_name }}
version:
description: 'The version number'
value: ${{ jobs.update-version-info.outputs.version }}
has_changes:
description: 'Whether ALVersionInfo.py was modified'
value: ${{ jobs.update-version-info.outputs.has_changes }}
jobs:
update-version-info:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
tag_name: ${{ steps.get_version.outputs.TAG_NAME }}
version: ${{ steps.get_version.outputs.VERSION }}
has_changes: ${{ steps.check_changes.outputs.has_changes }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Get tag name and version
id: get_version
env:
TZ: UTC
run: |
TAG_NAME="${{ inputs.tag_name }}"
VERSION="${TAG_NAME#v}"
COMMIT_SHA="${GITHUB_SHA:0:7}"
COMMIT_DATE=$(TZ=UTC git log -1 --format=%cd --date=format-local:'%Y-%m-%d %H:%M:%S UTC')
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_OUTPUT
echo "COMMIT_DATE=$COMMIT_DATE" >> $GITHUB_OUTPUT
echo "✓ Tag: $TAG_NAME"
echo "✓ Version: $VERSION"
echo "✓ Commit SHA: $COMMIT_SHA"
echo "✓ Commit Date: $COMMIT_DATE"
- name: Update ALVersionInfo.py with version info
run: |
VERSION="${{ steps.get_version.outputs.VERSION }}"
TAG_NAME="${{ steps.get_version.outputs.TAG_NAME }}"
COMMIT_SHA="${{ steps.get_version.outputs.COMMIT_SHA }}"
COMMIT_DATE="${{ steps.get_version.outputs.COMMIT_DATE }}"
APP_INFO_FILE="src/gui/ALVersionInfo.py"
BUILD_DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
echo "Updating $APP_INFO_FILE with build information..."
{
echo '# -*- coding: utf-8 -*-'
echo ''
echo '"""'
echo ' The contents of this file will automatically be updated by the'
echo ' workflow process. Do not edit manually.'
echo ' '
echo ' This file is auto-generated during the workflow process.'
echo " Last updated: ${BUILD_DATE}"
echo '"""'
echo ''
echo "AL_VERSION = \"${VERSION}\""
echo "AL_TAG = \"${TAG_NAME}\""
echo "AL_COMMIT_SHA = \"${COMMIT_SHA}\""
echo "AL_COMMIT_DATE = \"${COMMIT_DATE}\" # time zone : UTC"
echo "AL_BUILD_DATE = \"${BUILD_DATE}\" # time zone : UTC"
echo 'AL_VERSION_FULL = f"{AL_VERSION} ({AL_COMMIT_SHA})"'
} > "$APP_INFO_FILE"
echo "✓ ALVersionInfo.py updated successfully"
echo ""
echo "=== Updated ALVersionInfo.py ==="
cat "$APP_INFO_FILE"
echo "=========================="
- name: Check if ALVersionInfo.py was modified
id: check_changes
run: |
if git diff --quiet src/gui/ALVersionInfo.py; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "! No changes detected in ALVersionInfo.py"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "✓ ALVersionInfo.py has been modified"
fi
- name: Upload modified ALVersionInfo.py
if: steps.check_changes.outputs.has_changes == 'true'
uses: actions/upload-artifact@v4
with:
name: updated-version-info
path: src/gui/ALVersionInfo.py
retention-days: 1
+115 -2
View File
@@ -1,5 +1,118 @@
# AutoLibrary
---
请访问[AutoLibrary 网站](http://autolibrary.cv)\
Please access the [AutoLibrary Website](http://autolibrary.cv)
![AutoLibrary Logo](./src/gui/icons/AutoLibrary_128x128.ico)
![License](https://img.shields.io/github/license/KenanZhu/AutoLibrary)
![Issue](https://img.shields.io/github/issues/KenanZhu/AutoLibrary)
![Release](https://img.shields.io/github/v/release/KenanZhu/AutoLibrary)
![Download](https://img.shields.io/github/downloads/KenanZhu/AutoLibrary/total)
了解更多请访问 [_AutoLibrary 网站_](http://autolibrary.cv)
---
### 功能
1. 自动预约 - 支持自动预约
2. 自动续约 - 支持自动续约
3. 自动签到 - 支持自动签到
4. 批量操作 - 支持同时预约多个用户,可以指定当前需要跳过的用户,并将用户分成多个组
5. 定时任务 - 使用内置定时任务管理,添加定时任务,指定时间后按当前预约信息自动运行
*1,2,3 的具体操作方法和注意事项请访问我们的 [帮助手册](https://autolibrary.cv/docs/manual_lists.html)*
### 特点
#### 关于预约等操作的注意事项
工具会自动处理登录过程的验证码识别过程,正常情况下单次识别准确率在 90% 以上,如遇验证码识别错误,大概率是校园网网络环境不佳导致的。
只要确保处于校园网网络环境内,工具都是可以正常运行的。操作处理速度基本上取决于校园网的网络环境,一般情况下在 3-4 秒(不考虑硬件差异)左右即可完成一个用户的操作,完全满足正常使用需求。
> [!NOTE]
> 工具仅作为正常的预约,签到和续约的图书馆辅助工具,请勿干扰图书馆的正常运行(如故意预约多个座位,或同时预约大量的用户等,对此影响图书馆正常运行本工具概不负责,请在善用工具方便自己的情况下尽量不用影响其他同学的使用)。
#### 关于批量操作的注意事项
批量操作时,建议将需要操作的用户分成多个组,每个组的用户数量不要超过 4 人(即一整张桌子的数量),否则会影响操作效率,大量用户同时预约会一定程度上增加图书馆服务器的压力,影响正常使用。根据需要在用户管理界面中可以勾选本次操作是否跳过该用户,以提高运行效率。
#### 关于定时任务的注意事项
定时任务会在指定的时间自动运行,运行时会根据当前预约信息进行操作。一般情况下不建议设置两个运行开始时间比较接近的定时任务,否则后一个任务会等待前一个任务完成后才会运行,按照队列的顺序执行。
### 如何使用
1. 下载最新版本的 [AutoLibrary 压缩包](https://github.com/KenanZhu/AutoLibrary/releases)。
2. 解压下载的文件到任意目录。
3. 下载对应浏览器的驱动文件,并在配置界面的运行配置选项卡对应位置选择你下载好的浏览器驱动
4. 运行 `AutoLibrary.exe` 文件。
5. 按照提示操作即可。
*注意 1*: 关于浏览器驱动的下载和其它相关问题,请参考我们的 [帮助手册](https://autolibrary.cv/docs/manual_lists.html) 中对应软件版本的内容。
#### 平台支持 & 编译步骤
本工具目前仅支持 Windows 平台,由于使用 PySide6 库开发,理论上是可以自行编译并在 Linux 和 macOS 上运行,这里提供简单的编译步骤:
1. 确保系统安装了 Python 3.13 版本 (推荐,过低或高版本会导致兼容问题)。
2. 安装 pyside6 selenium ddddocr 库,命令为 `pip install pyside6 selenium ddddocr`
3.`src/gui/batchs` 目录下运行 `compile_ui.bat` linux 和 macOS 系统使用 `compile_ui.sh`) 文件来编译 Qt 的 UI 文件。
4. 在上一步相同目录内运行 `compile_rc.bat` linux 和 macOS 系统使用 `compile_rc.sh` 文件来编译 Qt 的资源文件。
5. 待上述步骤完成后,运行 `src/Main.py` 文件即可。
*注意 1*:如果 python 使用的是虚拟环境,请在虚拟环境安装依赖后,在激活的虚拟环境终端中使用 `cd src/gui/batchs` 命令切换到 `batchs` 目录下,再运行编译脚本。否则会提示缺少必要的 Qt PySide 依赖库。
*注意 2*:由于 ddddocr 的代码版本问题,其中 `__init__.py` 文件中的函数 `def classification(self, img: bytes):` 中的 `image.resize` 方法传入了不符合当前 pillow 版本的 `resample` 参数 `Image.ANTIALIAS`,该重采样常量已经在 10.0.0 版中删除 [1](@ref)。请将 `image.resize` 方法中的参数替换为 `resample=Image.Resampling.LANCZOS`,具体函数如下:
```python
def classification(self, img: bytes):
image = Image.open(io.BytesIO(img))
image = image.resize((int(image.size[0]*(64/image.size[1])), 64), Image.ANTIALIAS).convert('L')
^^^^^
请将上述参数替换为 `Image.Resampling.LANCZOS`
...
```
[1](@ref)[pillow 中已经删除或已经弃用的常量](https://pillow.ac.cn/en/stable/deprecations.html#constants)
### Q&A
#### 为什么开发这个工具?
当前图书馆的座位预约系统在使用中确实会遇到一些不便。例如,系统登录界面较为陈旧,在输入验证码时,若出现错误常常需要全部重新填写,过程繁琐。尤其在网络环境不稳定的情况下,登录和加载速度缓慢,让人难以快速完成当天的签到或预约次日座位。
此外,当朋友需要帮忙预约座位时,手动操作也会分散自己学习和工作的注意力。
因此,很希望有一个便捷的工具能自动处理这些预约、续约和签到等操作,从而让自己从这些琐碎事务中解脱出来,更专注于手头的重要事项。
#### 工具后续会收费吗?
不会,本工具完全免费使用,也不会有任何额外收费项。如果你觉工具对你很有帮助,可以为我捐助一瓶饮料的价格,以用于 AutoLibrary 网站的维护和软件的稳定更新。
<a href="https://afdian.com/a/autolibrary" style="display:inline-block;padding:10px 30px;background:linear-gradient(135deg,#946CE6,#946CE6);color:white;text-decoration:none;border-radius:6px;font-weight:bold;">❤ 支持作者</a>
#### 会有手机端的版本吗?
暂时没有考虑,而且也没有足够的时间和能力开发多平台的版本并测试维护,所以暂时只提供 Windows 版本。
#### 后续会有哪些功能?
当前 v1.0.0 版本的功能对于正常使用已经足够,不过后续会着重考虑完善 2-4 人预约时的使用体验,暂时有以下构想:
1. 2-4 人一起预约时,往往会偏向于预约并排或对面的整个空座位,这时候工具会按照一定策略查询搜索符合条件的座位,并预约并排或对面的整个座位,而不是各自独立预约。
2. 预约时会考虑到组内用户的预约时间是否冲突,若冲突则会提示用户是否继续预约,若用户选择继续预约,则会按需要调整预约时间,再进行预约。
3. 对于比较固定的用户,会考虑在定时任务管理中添加如 ‘每日任务’ ‘每周任务’ 等选项,用户可以根据需要设置定时任务重复的日期范围,自动完成预约,签到,续约等操作。
不过由于本人的时间和能力有限,也需要考虑到图书馆的正常运行,所以后续功能会有所取舍,但也许会进行一些小的功能验证。
#### 其他功能建议?
如果你有其他功能建议,或者遇到了什么功能性,操作上的问题,欢迎提交 Issue 到本项目。
如果你有足够的开发能力,欢迎为本项目提交 PR,也可以 Fork 本项目,根据自己的需求进行修改和完善。
### 联系我
- 项目维护:[KenanZhu (Nanoki)](https://github.com/KenanZhu)
- 电子邮箱:<nanoki_zh@163.com>
_**Free to use** —— AutoLibrary 是一个基于 MIT 协议免费开源的工具_
+2 -2
View File
@@ -29,7 +29,7 @@ class MsgBase:
msg: str
):
self._output_queue.put(f"[{self._class_name:<12}] >>> : {msg}")
self._output_queue.put(f"[{self._class_name:<15}] >>> : {msg}")
def _showTrace(
@@ -38,7 +38,7 @@ class MsgBase:
):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self._output_queue.put(f"{timestamp}-[{self._class_name:<12}] : {msg}")
self._output_queue.put(f"{timestamp}-[{self._class_name:<15}] : {msg}")
def _waitMsg(
+7 -2
View File
@@ -20,7 +20,9 @@ from PySide6.QtCore import (
QTimer, Qt
)
from gui.AppInfo import AL_VERSION
from gui.ALVersionInfo import (
AL_VERSION, AL_COMMIT_SHA, AL_COMMIT_DATE, AL_BUILD_DATE
)
from gui.Ui_ALAboutDialog import Ui_ALAboutDialog
from gui import AutoLibraryResource
@@ -43,7 +45,7 @@ class ALAboutDialog(QDialog, Ui_ALAboutDialog):
self
):
self.LogoIconLabel.setPixmap(QIcon(":/res/icon/icons/AutoLibrary.ico").pixmap(48, 48))
self.LogoIconLabel.setPixmap(QIcon(":/res/icon/icons/AutoLibrary_32x32.ico").pixmap(48, 48))
info_text = self.generateAboutText()
self.AboutInfoEdit.setHtml(info_text)
self.AboutInfoEdit.setTextInteractionFlags(Qt.TextBrowserInteraction)
@@ -64,6 +66,9 @@ class ALAboutDialog(QDialog, Ui_ALAboutDialog):
about_text = f"""
<h4>Version Information:</h4>
Version: {AL_VERSION}<br>
Commit SHA: {AL_COMMIT_SHA}<br>
Commit date: {AL_COMMIT_DATE}<br>
Build date: {AL_BUILD_DATE}<br>
Python version: {platform.python_version()}<br>
Qt version: {self.getQtVersion()}<br>
+1
View File
@@ -36,6 +36,7 @@ class TimerTaskStatus(Enum):
READY = "已就绪"
RUNNING = "执行中"
EXECUTED = "已执行"
ERROR = "执行失败"
OUTDATED = "已过期"
+4
View File
@@ -667,6 +667,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
group_item = self.addGroup()
if group_item.type() == TreeItemType.USER.value:
group_item = group_item.parent()
if group_item.checkState(1) == Qt.CheckState.Unchecked:
return None
new_user = {
"username": f"新用户-{group_item.childCount()}",
"password": "000000",
@@ -867,6 +869,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
is_checked = item.checkState(1) == Qt.CheckState.Checked
for i in range(item.childCount()):
child = item.child(i)
if self.UserTreeWidget.currentItem() == child:
self.UserTreeWidget.setCurrentItem(item)
child.setDisabled(not is_checked)
else:
is_checked = item.checkState(1) == Qt.CheckState.Checked
+29 -24
View File
@@ -38,6 +38,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
timerTaskIsRunning = Signal(dict)
timerTaskIsExecuted = Signal(dict)
timerTaskIsError = Signal(dict)
def __init__(
self
@@ -74,7 +75,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self
):
self.icon = QIcon(":/res/icon/icons/AutoLibrary.ico")
self.icon = QIcon(":/res/icon/icons/AutoLibrary_32x32.ico")
self.setWindowIcon(self.icon)
self.MessageIOTextEdit.setFont(QFont("Courier New", 10))
self.ManualAction.triggered.connect(self.onManualActionTriggered)
@@ -84,6 +85,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__alTimerTaskWidget = ALTimerTaskWidget(self, self.__config_paths["timer_task"])
self.timerTaskIsRunning.connect(self.__alTimerTaskWidget.onTimerTaskIsRunning)
self.timerTaskIsExecuted.connect(self.__alTimerTaskWidget.onTimerTaskIsExecuted)
self.timerTaskIsError.connect(self.__alTimerTaskWidget.onTimerTaskIsError)
self.__alTimerTaskWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady)
self.__alTimerTaskWidget.timerTaskWidgetClosed.connect(self.onTimerTaskWidgetClosed)
self.__alTimerTaskWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint)
@@ -229,7 +231,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.timerTaskIsRunning.emit(timer_task)
self.__timer_task_timer.stop()
self.__is_running_timer_task = True
self.setControlButtons(False, False, True)
self.setControlButtons(True, True, False)
if not timer_task["silent"]:
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
@@ -245,8 +247,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths
)
self.__current_timer_task_thread.finishedSignal_TimerWorker.connect(self.onTimerTaskFinished)
self.__current_timer_task_thread.showTraceSignal.connect(self.showTrace)
self.__current_timer_task_thread.showMsgSignal.connect(self.showMsg)
self.__current_timer_task_thread.start()
except queue.Empty:
self.__is_running_timer_task = False
@@ -260,9 +260,13 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
start_button_enabled: bool
):
self.ConfigButton.setEnabled(config_button_enabled)
self.StopButton.setEnabled(stop_button_enabled)
self.StartButton.setEnabled(start_button_enabled)
# if the enable is None, then keep the original state
if config_button_enabled is not None:
self.ConfigButton.setEnabled(config_button_enabled)
if stop_button_enabled is not None:
self.StopButton.setEnabled(stop_button_enabled)
if start_button_enabled is not None:
self.StartButton.setEnabled(start_button_enabled)
@Slot()
def showMsg(
@@ -270,7 +274,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
msg: str
):
self.appendToTextEdit(f"[{self.__class_name:<12}] >>> : {msg}")
self.__output_queue.put(f"[{self.__class_name:<15}] >>> : {msg}")
@Slot()
def showTrace(
@@ -279,7 +283,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.appendToTextEdit(f"{timestamp}-[{self.__class_name:<12}] : {msg}")
self.__output_queue.put(f"{timestamp}-[{self.__class_name:<15}] : {msg}")
@Slot()
def pollMsgQueue(
@@ -293,7 +297,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
except queue.Empty:
pass
@Slot()
def onTimerTaskWidgetClosed(
self
@@ -301,7 +304,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.TimerTaskWidgetButton.setEnabled(True)
@Slot(dict)
def onConfigWidgetClosed(
self,
@@ -312,7 +314,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__alConfigWidget.configWidgetCloseSingal.disconnect(self.onConfigWidgetClosed)
self.__alConfigWidget.deleteLater()
self.__alConfigWidget = None
self.setControlButtons(True, False, True)
self.setControlButtons(True, None, None)
self.__config_paths = config_paths
@Slot(dict)
@@ -326,27 +328,31 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot(dict)
def onTimerTaskFinished(
self,
is_error: bool,
timer_task: dict
):
self.__current_timer_task_thread.wait(1000)
self.__current_timer_task_thread.finishedSignal_TimerWorker.disconnect(self.onTimerTaskFinished)
self.__current_timer_task_thread.showTraceSignal.disconnect(self.showTrace)
self.__current_timer_task_thread.showMsgSignal.disconnect(self.showMsg)
self.__current_timer_task_thread.deleteLater()
self.__current_timer_task_thread = None
self.setControlButtons(True, False, True)
self.setControlButtons(None, False, True)
self.__is_running_timer_task = False
self.__timer_task_timer.start(500)
timer_task["executed"] = True
self.TrayIcon.showMessage(
"定时任务 - AutoLibrary",
f"\n定时任务 '{timer_task['name']}' 执行完成",
f"\n定时任务 '{timer_task['name']}' 执行{'失败' if is_error else '完成'}",
QSystemTrayIcon.MessageIcon.Information,
1000
)
self.showTrace(f"定时任务 {timer_task['name']} 执行完成, uuid: {timer_task['task_uuid']}")
self.timerTaskIsExecuted.emit(timer_task)
self.showTrace(
f"定时任务 {timer_task['name']} 执行{'失败' if is_error else '完成'}, uuid: {timer_task['task_uuid']}"
)
if not is_error:
self.timerTaskIsExecuted.emit(timer_task)
else:
self.timerTaskIsError.emit(timer_task)
@Slot()
def onTimerTaskWidgetButtonClicked(
@@ -379,7 +385,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self
):
self.setControlButtons(False, False, True)
self.setControlButtons(None, True, False)
if self.__auto_lib_thread is None:
self.__auto_lib_thread = AutoLibWorker(
self.__input_queue,
@@ -387,8 +393,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__config_paths
)
self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.showMsgSignal.connect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.connect(self.showTrace)
self.__auto_lib_thread.finishedWithErrorSignal.connect(self.onStopButtonClicked)
self.__auto_lib_thread.start()
@Slot()
@@ -400,12 +405,11 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.showTrace("正在停止操作......")
self.__auto_lib_thread.wait(2000)
self.showTrace("操作已停止")
self.__auto_lib_thread.showMsgSignal.disconnect(self.showMsg)
self.__auto_lib_thread.showTraceSignal.disconnect(self.showTrace)
self.__auto_lib_thread.finishedSignal.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.finishedWithErrorSignal.disconnect(self.onStopButtonClicked)
self.__auto_lib_thread.deleteLater()
self.__auto_lib_thread = None
self.setControlButtons(True, False, True)
self.setControlButtons(None, False, True)
@Slot()
def onSendButtonClicked(
@@ -416,4 +420,5 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
if not msg:
return
self.showMsg(msg)
self.__input_queue.put(msg) # put message to input queue
self.MessageEdit.clear()
+63 -68
View File
@@ -12,18 +12,18 @@ import time
import queue
from PySide6.QtCore import (
Signal, QThread
Slot, Signal, QThread
)
from base.MsgBase import MsgBase
from operators.AutoLib import AutoLib
from utils.ConfigReader import ConfigReader
class AutoLibWorker(QThread):
class AutoLibWorker(QThread, MsgBase):
finishedSignal = Signal()
showTraceSignal = Signal(str)
showMsgSignal = Signal(str)
finishedWithErrorSignal = Signal()
def __init__(
self,
@@ -32,10 +32,8 @@ class AutoLibWorker(QThread):
config_paths: dict
):
super().__init__()
super().__init__(input_queue = input_queue, output_queue = output_queue)
self.__input_queue = input_queue
self.__output_queue = output_queue
self.__config_paths = config_paths
@@ -45,6 +43,9 @@ class AutoLibWorker(QThread):
current_time = time.strftime("%H:%M", time.localtime())
if current_time >= "23:30" or current_time <= "07:30":
self._showTrace(
"当前时间不在图书馆开放时间内, 请在 07:30 - 23:30 之间尝试"
)
return False
return True
@@ -56,9 +57,7 @@ class AutoLibWorker(QThread):
if not all(
os.path.exists(path) for path in self.__config_paths.values()
):
self.showTraceSignal.emit(
"配置文件路径不存在, 请检查配置文件路径是否正确。"
)
self._showTrace("配置文件路径不存在, 请检查配置文件路径是否正确")
return False
return True
@@ -67,25 +66,24 @@ class AutoLibWorker(QThread):
self
) -> bool:
self.showTraceSignal.emit(
self._showTrace(
f"正在加载配置文件, 运行配置文件路径: {self.__config_paths["run"]}"
)
self.__run_config = ConfigReader(
self.__config_paths["run"]
).getConfigs()
self.showTraceSignal.emit(
self._showTrace(
f"正在加载配置文件, 用户配置文件路径: {self.__config_paths["user"]}"
)
self.__user_config = ConfigReader(
self.__config_paths["user"]
).getConfigs()
if self.__run_config is None or self.__user_config is None:
self.showTraceSignal.emit(
"配置文件加载失败, 请检查配置文件是否正确"
)
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
return False
if not self.__user_config.get("groups"):
self.showTraceSignal.emit(
self._showTrace(
"用户配置文件中无有效任务组, 请检查用户配置文件是否正确"
)
return False
@@ -97,57 +95,42 @@ class AutoLibWorker(QThread):
):
auto_lib = None
try:
if not self.checkTimeAvailable():
self.showTraceSignal.emit(
"当前时间不在图书馆开放时间内\n"\
" 请在 07:30 - 23:30 之间尝试"
self._showTrace("AutoLibrary 开始运行")
if not self.checkTimeAvailable()\
or not self.checkConfigPaths():
# time or config existence check failed, skip and finish
pass
else:
try:
if not self.loadConfigs():
raise Exception("配置文件加载失败")
auto_lib = AutoLib(
self._input_queue,
self._output_queue,
self.__run_config
)
return
if not self.checkConfigPaths():
return
self.showTraceSignal.emit("AutoLibrary 开始运行")
if not self.loadConfigs():
return
auto_lib = AutoLib(
self.__input_queue,
self.__output_queue,
self.__run_config
)
if auto_lib is None:
self.showTraceSignal.emit(
"AutoLibrary 初始化失败"
)
return
groups = self.__user_config.get("groups")
for group in groups:
time.sleep(0.2) # wait for the message queue to be empty
if not group["enabled"]:
self.showTraceSignal.emit(
f"任务组 {group["name"]} 已跳过"
groups = self.__user_config.get("groups")
for group in groups:
if not group["enabled"]:
self._showTrace(f"任务组 {group["name"]} 已跳过")
continue
self._showTrace(f"正在运行任务组 {group["name"]}")
auto_lib.run(
{ "users": group.get("users", []) }
)
continue
self.showTraceSignal.emit(
f"正在运行任务组 {group["name"]}"
)
auto_lib.run(
{ "users": group.get("users", []) }
)
except Exception as e:
self.showTraceSignal.emit(
f"AutoLibrary 运行时发生异常 : {e}"
)
finally:
if auto_lib:
auto_lib.close()
time.sleep(0.2) # wait for the message queue to be empty
self.showTraceSignal.emit("AutoLibrary 运行结束")
self.finishedSignal.emit()
except Exception as e:
self._showTrace(f"AutoLibrary 运行时发生异常 : {e}")
self.finishedWithErrorSignal.emit()
return
if auto_lib:
auto_lib.close()
self._showTrace("AutoLibrary 运行结束")
self.finishedSignal.emit()
class TimerTaskWorker(AutoLibWorker):
finishedSignal_TimerWorker = Signal(dict)
finishedSignal_TimerWorker = Signal(bool, dict)
def __init__(
self,
@@ -160,16 +143,28 @@ class TimerTaskWorker(AutoLibWorker):
super().__init__(input_queue, output_queue, config_paths)
self.__timer_task = timer_task
self.finishedSignal.connect(self.onTimerTaskIsFinished)
self.finishedWithErrorSignal.connect(self.onTimerTaskIsError)
def run(
self
):
self.showTraceSignal.emit(
f"定时任务 {self.__timer_task['name']} 开始运行"
)
self._showTrace(f"定时任务 {self.__timer_task['name']} 开始运行")
super().run()
self.showTraceSignal.emit(
f"定时任务 {self.__timer_task['name']} 运行结束"
)
self.finishedSignal_TimerWorker.emit(self.__timer_task)
@Slot(dict)
def onTimerTaskIsError(
self
):
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行时发生异常")
self.finishedSignal_TimerWorker.emit(True, self.__timer_task)
@Slot(dict)
def onTimerTaskIsFinished(
self
):
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行结束")
self.finishedSignal_TimerWorker.emit(False, self.__timer_task)
+42 -5
View File
@@ -94,6 +94,9 @@ class TimerTaskItemWidget(QWidget):
case TimerTaskStatus.EXECUTED:
TaskStatusText = "已执行"
TaskStatusColor = "#4CAF50"
case TimerTaskStatus.ERROR:
TaskStatusText = "执行失败"
TaskStatusColor = "#FF5722"
case TimerTaskStatus.OUTDATED:
TaskStatusText = "已过期"
TaskStatusColor = "#FF5722"
@@ -151,6 +154,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
self.__timer_tasks = []
self.__check_timer = None
self.__sort_policy = SortPolicy.BY_EXECUTE_TIME
self.__sort_order = Qt.SortOrder.AscendingOrder
self.__timer_tasks_config_path = timer_tasks_config_path
self.setupUi(self)
@@ -167,6 +171,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
self.AddTimerTaskButton.clicked.connect(self.addTask)
self.ClearAllTimerTasksButton.clicked.connect(self.clearAllTasks)
self.TimerTaskSortTypeComboBox.currentIndexChanged.connect(self.onSortPolicyComboBoxChanged)
self.TimerTaskSortOrderToggleButton.clicked.connect(self.onSortOrderToggleButtonClicked)
self.timerTasksChanged.connect(self.onTimerTasksChanged)
@@ -291,20 +296,24 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
def sortTimerTasks(
self,
policy: SortPolicy = SortPolicy.BY_EXECUTE_TIME
policy: SortPolicy = SortPolicy.BY_EXECUTE_TIME,
order: Qt.SortOrder = Qt.SortOrder.AscendingOrder
):
if policy == SortPolicy.BY_NAME:
self.__timer_tasks.sort(
key = lambda x: x["name"]
key = lambda x: x["name"],
reverse = order is Qt.SortOrder.DescendingOrder
)
elif policy == SortPolicy.BY_ADD_TIME:
self.__timer_tasks.sort(
key = lambda x: x["add_time"]
key = lambda x: x["add_time"],
reverse = order is Qt.SortOrder.DescendingOrder
)
elif policy == SortPolicy.BY_EXECUTE_TIME:
self.__timer_tasks.sort(
key = lambda x: x["execute_time"]
key = lambda x: x["execute_time"],
reverse = order is Qt.SortOrder.DescendingOrder
)
@@ -315,6 +324,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
pending = 0
in_queue = 0
executed = 0
invalid = 0
total = len(self.__timer_tasks)
for timer_task in self.__timer_tasks:
if timer_task["status"] == TimerTaskStatus.PENDING:
@@ -324,10 +334,14 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
in_queue += 1
elif timer_task["status"] == TimerTaskStatus.EXECUTED:
executed += 1
elif timer_task["status"] == TimerTaskStatus.ERROR\
or timer_task["status"] == TimerTaskStatus.OUTDATED:
invalid += 1
self.TotalTaskLabel.setText(f"总任务:{total}")
self.PendingTaskLabel.setText(f"待执行:{pending}")
self.InQueueTaskLabel.setText(f"队列中:{in_queue}")
self.ExecutedTaskLabel.setText(f"已执行:{executed}")
self.InvalidTaskLabel.setText(f"无效的:{invalid}")
def updateTimerTaskList(
@@ -335,7 +349,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
):
self.TimerTasksListWidget.clear()
self.sortTimerTasks(self.__sort_policy)
self.sortTimerTasks(self.__sort_policy, self.__sort_order)
for timer_task in self.__timer_tasks:
item = QListWidgetItem()
item.setData(Qt.UserRole, timer_task)
@@ -437,6 +451,18 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
self.__sort_policy = mapping[policy]
self.updateTimerTaskList()
@Slot()
def onSortOrderToggleButtonClicked(
self
):
self.__sort_order = Qt.SortOrder.AscendingOrder\
if self.__sort_order is Qt.SortOrder.DescendingOrder\
else Qt.SortOrder.DescendingOrder
self.TimerTaskSortOrderToggleButton.setText(
"" if self.__sort_order is Qt.SortOrder.AscendingOrder else ""
)
self.updateTimerTaskList()
@Slot()
def onTimerTasksChanged(
@@ -470,3 +496,14 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.EXECUTED
self.timerTasksChanged.emit()
@Slot(dict)
def onTimerTaskIsError(
self,
timer_task: dict
):
for task in self.__timer_tasks:
if task["task_uuid"] == timer_task["task_uuid"]:
task["status"] = TimerTaskStatus.ERROR
self.timerTasksChanged.emit()
+44 -1
View File
@@ -137,6 +137,30 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="InvalidTaskLabel">
<property name="minimumSize">
<size>
<width>70</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>70</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QLabel {
color: #FF5722
}</string>
</property>
<property name="text">
<string>无效的:0</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="TimerTaskSpaceFrame">
<property name="minimumSize">
@@ -164,7 +188,7 @@
<item>
<layout class="QHBoxLayout" name="TimerTaskSortLayout">
<property name="spacing">
<number>5</number>
<number>0</number>
</property>
<item>
<widget class="QFrame" name="TimerTaskSortSpaceFrame">
@@ -235,6 +259,25 @@
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="TimerTaskSortOrderToggleButton">
<property name="minimumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>↑</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
+1
View File
@@ -60,6 +60,7 @@ class ALUserTreeWidget(QTreeWidget):
self.setDefaultDropAction(Qt.DropAction.IgnoreAction)
self.setAlternatingRowColors(True)
self.setSortingEnabled(True)
self.sortByColumn(0, Qt.SortOrder.AscendingOrder)
self.setAnimated(True)
self.setAllColumnsShowFocus(False)
self.setHeaderHidden(False)
+16
View File
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
"""
The contents of this file will automatically be updated by the
workflow process. Do not edit manually.
This file is auto-generated during the workflow process.
Last updated: 2026-01-02 16:38:53 UTC
"""
AL_VERSION = "1.0.1"
AL_TAG = "v1.0.1"
AL_COMMIT_SHA = "924db3b"
AL_COMMIT_DATE = "2026-01-02 16:35:16 UTC" # time zone : UTC
AL_BUILD_DATE = "2026-01-02 16:38:53 UTC" # time zone : UTC
AL_VERSION_FULL = f"{AL_VERSION} ({AL_COMMIT_SHA})"
-1
View File
@@ -1 +0,0 @@
AL_VERSION = "1.0.0-beta"
+1 -1
View File
@@ -1,6 +1,6 @@
<RCC>
<qresource prefix="/res/icon">
<file>icons/AutoLibrary.ico</file>
<file>icons/AutoLibrary_32x32.ico</file>
</qresource>
<qresource prefix="/res/trans">
<file>translators/qtbase_zh_CN.qm</file>
+62
View File
@@ -0,0 +1,62 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
cd /d "%~dp0.."
echo [AutoLibrary compile] 检查翻译文件...
if exist translators (
cd translators
set ts_count=0
for %%f in (*.ts) do set /a ts_count+=1
if !ts_count! gtr 0 (
echo [AutoLibrary compile] 找到 !ts_count! 个 .ts 文件,开始编译翻译文件...
for %%f in (*.ts) do (
set "qm_filename=%%~nf.qm"
echo [AutoLibrary compile] 正在编译翻译文件: "%%f" -> "!qm_filename!"
pyside6-lrelease "%%f"
if !errorlevel! equ 0 (
echo [AutoLibrary compile] 翻译文件 "%%f" ✓ 编译成功,输出文件: "!qm_filename!"
) else (
echo [AutoLibrary compile] 翻译文件 "%%f" ✗ 编译失败
)
)
echo.
) else (
echo [AutoLibrary compile] 未找到任何 .ts 翻译文件
)
cd ..
) else (
echo [AutoLibrary compile] 未找到 translators 目录
)
echo.
set count=0
for %%f in (*.qrc) do set /a count+=1
if %count% equ 0 (
echo [AutoLibrary compile] 错误: 未找到任何 .qrc 文件
pause
exit /b 1
)
echo [AutoLibrary compile] 找到 %count% 个 .qrc 文件,开始编译...
echo.
for %%f in (*.qrc) do (
set "filename=%%~nf"
set "output_file=!filename!.py"
echo [AutoLibrary compile] 正在编译: "%%f" -> "!output_file!"
pyside6-rcc "%%f" -o "!output_file!"
if !errorlevel! equ 0 (
echo [AutoLibrary compile] 文件 "%%f" ✓ 编译成功,输出文件: "!output_file!"
) else (
echo [AutoLibrary compile] 文件 "%%f" ✗ 编译失败
)
echo.
)
echo [AutoLibrary compile] 所有操作完成。
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PARENT_DIR"
echo "[AutoLibrary compile] 检查翻译文件..."
if [ -d "translators" ]; then
cd translators
ts_files=(*.ts)
ts_count=${#ts_files[@]}
# 如果第一个元素是"*.ts"(表示没有匹配),则数量为0
if [ "$ts_count" -eq 1 ] && [ "${ts_files[0]}" = "*.ts" ]; then
ts_count=0
fi
if [ $ts_count -gt 0 ]; then
echo "[AutoLibrary compile] 找到 $ts_count 个 .ts 文件,开始编译翻译文件..."
for file in *.ts; do
base_name=$(basename "$file" .ts)
qm_file="${base_name}.qm"
echo "[AutoLibrary compile] 正在编译翻译文件: \"$file\" -> \"$qm_file\""
if pyside6-lrelease "$file"; then
echo "[AutoLibrary compile] 翻译文件 \"$file\" ✓ 编译成功,输出文件: \"$qm_file\""
else
echo "[AutoLibrary compile] 翻译文件 \"$file\" ✗ 编译失败"
fi
done
echo
else
echo "[AutoLibrary compile] 未找到任何 .ts 翻译文件"
fi
cd ..
else
echo "[AutoLibrary compile] 未找到 translators 目录"
fi
echo
file_count=$(ls *.qrc 2>/dev/null | wc -l)
if [ $file_count -eq 0 ]; then
echo "[AutoLibrary compile] 错误: 未找到任何 .qrc 文件"
exit 1
fi
echo "[AutoLibrary compile] 找到 $file_count 个 .qrc 文件,开始编译..."
echo
for file in *.qrc; do
base_name=$(basename "$file" .qrc)
output_file="${base_name}.py"
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
if pyside6-rcc "$file" -o "$output_file"; then
echo "[AutoLibrary compile] 文件 \"$file\" ✓ 编译成功,输出文件: \"$output_file\""
else
echo "[AutoLibrary compile] 文件 \"$file\" ✗ 编译失败"
fi
echo
done
echo "[AutoLibrary compile] 所有操作完成。"
+33
View File
@@ -0,0 +1,33 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
cd /d "%~dp0.."
set count=0
for %%f in (*.ui) do set /a count+=1
if %count% equ 0 (
echo [AutoLibrary compile] 错误: 未找到任何 .ui 文件
pause
exit /b 1
)
echo [AutoLibrary compile] 找到 %count% 个 .ui 文件,开始编译...
echo.
for %%f in (*.ui) do (
set "filename=%%~nf"
set "output_file=Ui_!filename!.py"
echo [AutoLibrary compile] 正在编译: "%%f" -> "!output_file!"
pyside6-uic "%%f" -o "!output_file!"
if !errorlevel! equ 0 (
echo [AutoLibrary compile] 文件 "%%f" ✓ 编译成功,输出文件: "!output_file!"
) else (
echo [AutoLibrary compile] 文件 "%%f" ✗ 编译失败
)
echo.
)
echo [AutoLibrary compile] 所有操作完成。
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PARENT_DIR"
file_count=$(ls *.ui 2>/dev/null | wc -l)
if [ $file_count -eq 0 ]; then
echo "[AutoLibrary compile] 错误: 未找到任何 .ui 文件"
exit 1
fi
echo "[AutoLibrary compile] 找到 $file_count 个 .ui 文件,开始编译..."
echo
for file in *.ui; do
base_name=$(basename "$file" .ui)
output_file="Ui_${base_name}.py"
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
if pyside6-uic "$file" -o "$output_file"; then
echo "[AutoLibrary compile] 文件 \"$file\" ✓ 编译成功,输出文件: \"$output_file\""
else
echo "[AutoLibrary compile] 文件 \"$file\" ✗ 编译失败"
fi
echo
done
echo "[AutoLibrary compile] 所有操作完成。"
+1
View File
@@ -0,0 +1 @@
this folder is used to store the batch scripts.

Before

Width:  |  Height:  |  Size: 785 KiB

After

Width:  |  Height:  |  Size: 785 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+2 -2
View File
@@ -41,10 +41,10 @@ class AutoLib(MsgBase):
self.__user_config = None
self.__driver = None
if not self.__initBrowserDriver():
return None
raise Exception("浏览器驱动初始化失败")
else:
if not self.__initDriverUrl():
return None
raise Exception("浏览器驱动URL初始化失败")
self.__initLibOperators()
+10 -13
View File
@@ -88,13 +88,12 @@ class LibLogin(LibOperator):
captcha_img = base64.b64decode(base64_str)
captcha_text = self.__ddddocr.classification(captcha_img)
captcha_text = ''.join(filter(str.isalnum, captcha_text)).lower()
self._showTrace(f"识别到验证码为 : '{captcha_text}'.")
self._showTrace(f"识别到验证码为 : '{captcha_text}'")
if len(captcha_text) != 4:
raise Exception("识别到的验证码长度不等于 4 个字符 !")
return captcha_text
except Exception as e:
self._showTrace(f"验证码识别失败 ! : {e}")
self.__refreshCaptcha()
return ""
@@ -104,15 +103,14 @@ class LibLogin(LibOperator):
# manual recognize captcha
try:
self._show_msg("请输入验证码:")
captcha_text = self._wait_msg(timeout=15)
self._showTrace(f"输入的验证码为 : '{captcha_text}'.")
self._showMsg("请输入验证码:")
captcha_text = self._waitMsg(timeout=15)
self._showTrace(f"输入的验证码为 : '{captcha_text}'")
if len(captcha_text) != 4:
raise Exception("输入的验证码长度不等于 4 个字符 !")
return captcha_text
except Exception as e:
self._showTrace(f"输入验证码失败 ! : {e}")
self.__refreshCaptcha()
return ""
@@ -126,11 +124,9 @@ class LibLogin(LibOperator):
self.__driver.find_element(
By.ID, "loadImgId"
).click()
time.sleep(1)
return True
except Exception as e:
self._showTrace(f"刷新验证码失败 ! : {e}")
self.__refreshCaptcha()
return False
@@ -139,8 +135,7 @@ class LibLogin(LibOperator):
auto_captcha: bool = True
) -> str:
max_attempts = 5
max_attempts = 3 # the possibility of 3 times failed is less than (10%^3)
for _ in range(max_attempts):
if auto_captcha:
captcha_text = self.__autoRecognizeCaptcha()
@@ -149,7 +144,10 @@ class LibLogin(LibOperator):
captcha_text = self.__manualRecognizeCaptcha()
if captcha_text:
return captcha_text
self._showTrace(f"验证码识别失败 {max_attempts} 次, 请检查验证码是否正确 !")
else:
if not self.__refreshCaptcha():
return ""
self._showTrace(f"验证码识别失败 {max_attempts} 次, 达到最大尝试次数 !")
return ""
@@ -165,7 +163,6 @@ class LibLogin(LibOperator):
return True
except Exception as e:
self._showTrace(f"验证码填写失败 ! : {e}")
self.__refreshCaptcha()
return False
@@ -200,7 +197,7 @@ class LibLogin(LibOperator):
"//input[@type='button' and @value='登录']"
).click()
except Exception as e:
self._showTrace(f"登录失败 ! : {e}")
self._showTrace(f"尝试登录失败 ! : {e}")
continue
if self._waitResponseLoad():
self._showTrace(f"用户 {username}{attempt + 1} 次登录成功 !")