autotd-buaa 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autotd_buaa-0.1.1/MANIFEST.in +8 -0
- autotd_buaa-0.1.1/PKG-INFO +132 -0
- autotd_buaa-0.1.1/README.md +116 -0
- autotd_buaa-0.1.1/pyproject.toml +35 -0
- autotd_buaa-0.1.1/setup.cfg +4 -0
- autotd_buaa-0.1.1/src/auto_td/__init__.py +6 -0
- autotd_buaa-0.1.1/src/auto_td/cli.py +293 -0
- autotd_buaa-0.1.1/src/auto_td/client.py +140 -0
- autotd_buaa-0.1.1/src/auto_td/constants.py +8 -0
- autotd_buaa-0.1.1/src/auto_td/logging_utils.py +27 -0
- autotd_buaa-0.1.1/src/auto_td/models.py +109 -0
- autotd_buaa-0.1.1/src/auto_td/paths.py +11 -0
- autotd_buaa-0.1.1/src/auto_td/quick.py +72 -0
- autotd_buaa-0.1.1/src/auto_td/runner.py +97 -0
- autotd_buaa-0.1.1/src/auto_td/scheduler.py +64 -0
- autotd_buaa-0.1.1/src/auto_td/storage.py +222 -0
- autotd_buaa-0.1.1/src/auto_td/templates/config.json +84 -0
- autotd_buaa-0.1.1/src/auto_td/templates/settings.json +11 -0
- autotd_buaa-0.1.1/src/autotd_buaa.egg-info/PKG-INFO +132 -0
- autotd_buaa-0.1.1/src/autotd_buaa.egg-info/SOURCES.txt +23 -0
- autotd_buaa-0.1.1/src/autotd_buaa.egg-info/dependency_links.txt +1 -0
- autotd_buaa-0.1.1/src/autotd_buaa.egg-info/entry_points.txt +2 -0
- autotd_buaa-0.1.1/src/autotd_buaa.egg-info/top_level.txt +1 -0
- autotd_buaa-0.1.1/tests/test_protocol_runner_scheduler.py +181 -0
- autotd_buaa-0.1.1/tests/test_storage_cli.py +191 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autotd-buaa
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: BUAA TD automation command line utility
|
|
5
|
+
Author: autoTD maintainers
|
|
6
|
+
Keywords: buaa,td,cli
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# AutoTD
|
|
18
|
+
|
|
19
|
+
`autoTD` 是一个可安装的命令行工具包。安装后提供 `autotd` 命令,用于初始化本地配置目录、管理用户和图片、运行 TD 打卡流程、查询服务器返回的本学期锻炼次数,以及以前台进程方式进行定时轮询。
|
|
20
|
+
|
|
21
|
+
> `autotd user count` 会发送真实 `checkdata` 请求来解析服务器消息中的次数,可能产生服务器侧刷卡/打卡记录。
|
|
22
|
+
|
|
23
|
+
## 安装与打包
|
|
24
|
+
|
|
25
|
+
本项目采用标准 `pyproject.toml` 包结构:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python3 -m pip install -e .
|
|
29
|
+
autotd --help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
构建本地 wheel/sdist:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python3 -m build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
内部测试发布可使用 TestPyPI:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python3 -m twine upload --repository testpypi dist/*
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 初始化
|
|
45
|
+
|
|
46
|
+
所有运行数据存储在 `~/.autoTD` 下。测试或临时运行时可用 `AUTOTD_HOME` 覆盖:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
autotd init
|
|
50
|
+
autotd init --from ./autoTD
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
目录结构:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
~/.autoTD/
|
|
57
|
+
├── config.json
|
|
58
|
+
├── users.json
|
|
59
|
+
├── settings.json
|
|
60
|
+
├── state.json
|
|
61
|
+
├── images/
|
|
62
|
+
└── logs/
|
|
63
|
+
└── YYYY-MM-DD-daytime-log.txt
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 用户管理
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
autotd user add 22375080 --entrance 2 --exit 6 --entrance-image image3.jpg --exit-image image2.jpg
|
|
70
|
+
autotd user add 22375080 --quick 沙河
|
|
71
|
+
autotd user add 22375080 --quick 学院路
|
|
72
|
+
autotd user list
|
|
73
|
+
autotd user show 22375080
|
|
74
|
+
autotd user update 22375080 --card-id CARDID --entrance 3 --exit 7 --entrance-image image1.jpg --exit-image image2.jpg
|
|
75
|
+
autotd user delete 22375080
|
|
76
|
+
autotd user count 22375080
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`card_id` 为空时会自动使用学号的 16 进制大写形式。
|
|
80
|
+
|
|
81
|
+
使用 `--quick` 时,只需要指定校区:
|
|
82
|
+
|
|
83
|
+
- `--quick 沙河`: 随机选择沙河入口机 `8/9/10`、沙河出口机 `11/12/13`,并从 `~/.autoTD/images/` 随机选择入口/出口图片。
|
|
84
|
+
- `--quick 学院路`: 随机选择学院路/本部入口机 `2/3/4`、学院路/本部出口机 `5/6/7`,并从 `~/.autoTD/images/` 随机选择入口/出口图片。
|
|
85
|
+
- 其他字段使用默认值:`rounds=3`、`wait_time_min=180`、`wait_time_max=240`、`card_id` 自动生成。
|
|
86
|
+
|
|
87
|
+
## 图片管理
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
autotd image add ./image3.jpg
|
|
91
|
+
autotd image add ./photo.jpg --name entrance.jpg
|
|
92
|
+
autotd image add ./photo.jpg --name entrance.jpg --overwrite
|
|
93
|
+
autotd image list
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
图片会复制到 `~/.autoTD/images/`。用户配置中引用的是图片文件名。
|
|
97
|
+
|
|
98
|
+
## 运行
|
|
99
|
+
|
|
100
|
+
立即执行一次所有用户:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
autotd run --once
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
如果定时开关关闭,`autotd run` 也会立即执行一次。执行时某个用户失败会记录日志并继续后续用户。
|
|
107
|
+
|
|
108
|
+
## 定时运行
|
|
109
|
+
|
|
110
|
+
定时运行是前台常驻进程,不创建系统服务。
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
autotd schedule show
|
|
114
|
+
autotd schedule enable
|
|
115
|
+
autotd schedule disable
|
|
116
|
+
autotd schedule set --poll-seconds 60 --windows "07:30-10:00,11:30-14:00,15:30-20:00"
|
|
117
|
+
autotd run
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
默认时间段为:
|
|
121
|
+
|
|
122
|
+
- 07:30 - 10:00
|
|
123
|
+
- 11:30 - 14:00
|
|
124
|
+
- 15:30 - 20:00
|
|
125
|
+
|
|
126
|
+
定时模式按北京时间判断日期。一天只会做一次定时批量尝试;无论成功或失败,当天都不会自动重试。
|
|
127
|
+
|
|
128
|
+
## 测试
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python3 -m unittest discover -s tests -v
|
|
132
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# AutoTD
|
|
2
|
+
|
|
3
|
+
`autoTD` 是一个可安装的命令行工具包。安装后提供 `autotd` 命令,用于初始化本地配置目录、管理用户和图片、运行 TD 打卡流程、查询服务器返回的本学期锻炼次数,以及以前台进程方式进行定时轮询。
|
|
4
|
+
|
|
5
|
+
> `autotd user count` 会发送真实 `checkdata` 请求来解析服务器消息中的次数,可能产生服务器侧刷卡/打卡记录。
|
|
6
|
+
|
|
7
|
+
## 安装与打包
|
|
8
|
+
|
|
9
|
+
本项目采用标准 `pyproject.toml` 包结构:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python3 -m pip install -e .
|
|
13
|
+
autotd --help
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
构建本地 wheel/sdist:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python3 -m build
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
内部测试发布可使用 TestPyPI:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python3 -m twine upload --repository testpypi dist/*
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 初始化
|
|
29
|
+
|
|
30
|
+
所有运行数据存储在 `~/.autoTD` 下。测试或临时运行时可用 `AUTOTD_HOME` 覆盖:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
autotd init
|
|
34
|
+
autotd init --from ./autoTD
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
目录结构:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
~/.autoTD/
|
|
41
|
+
├── config.json
|
|
42
|
+
├── users.json
|
|
43
|
+
├── settings.json
|
|
44
|
+
├── state.json
|
|
45
|
+
├── images/
|
|
46
|
+
└── logs/
|
|
47
|
+
└── YYYY-MM-DD-daytime-log.txt
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 用户管理
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
autotd user add 22375080 --entrance 2 --exit 6 --entrance-image image3.jpg --exit-image image2.jpg
|
|
54
|
+
autotd user add 22375080 --quick 沙河
|
|
55
|
+
autotd user add 22375080 --quick 学院路
|
|
56
|
+
autotd user list
|
|
57
|
+
autotd user show 22375080
|
|
58
|
+
autotd user update 22375080 --card-id CARDID --entrance 3 --exit 7 --entrance-image image1.jpg --exit-image image2.jpg
|
|
59
|
+
autotd user delete 22375080
|
|
60
|
+
autotd user count 22375080
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`card_id` 为空时会自动使用学号的 16 进制大写形式。
|
|
64
|
+
|
|
65
|
+
使用 `--quick` 时,只需要指定校区:
|
|
66
|
+
|
|
67
|
+
- `--quick 沙河`: 随机选择沙河入口机 `8/9/10`、沙河出口机 `11/12/13`,并从 `~/.autoTD/images/` 随机选择入口/出口图片。
|
|
68
|
+
- `--quick 学院路`: 随机选择学院路/本部入口机 `2/3/4`、学院路/本部出口机 `5/6/7`,并从 `~/.autoTD/images/` 随机选择入口/出口图片。
|
|
69
|
+
- 其他字段使用默认值:`rounds=3`、`wait_time_min=180`、`wait_time_max=240`、`card_id` 自动生成。
|
|
70
|
+
|
|
71
|
+
## 图片管理
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
autotd image add ./image3.jpg
|
|
75
|
+
autotd image add ./photo.jpg --name entrance.jpg
|
|
76
|
+
autotd image add ./photo.jpg --name entrance.jpg --overwrite
|
|
77
|
+
autotd image list
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
图片会复制到 `~/.autoTD/images/`。用户配置中引用的是图片文件名。
|
|
81
|
+
|
|
82
|
+
## 运行
|
|
83
|
+
|
|
84
|
+
立即执行一次所有用户:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
autotd run --once
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
如果定时开关关闭,`autotd run` 也会立即执行一次。执行时某个用户失败会记录日志并继续后续用户。
|
|
91
|
+
|
|
92
|
+
## 定时运行
|
|
93
|
+
|
|
94
|
+
定时运行是前台常驻进程,不创建系统服务。
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
autotd schedule show
|
|
98
|
+
autotd schedule enable
|
|
99
|
+
autotd schedule disable
|
|
100
|
+
autotd schedule set --poll-seconds 60 --windows "07:30-10:00,11:30-14:00,15:30-20:00"
|
|
101
|
+
autotd run
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
默认时间段为:
|
|
105
|
+
|
|
106
|
+
- 07:30 - 10:00
|
|
107
|
+
- 11:30 - 14:00
|
|
108
|
+
- 15:30 - 20:00
|
|
109
|
+
|
|
110
|
+
定时模式按北京时间判断日期。一天只会做一次定时批量尝试;无论成功或失败,当天都不会自动重试。
|
|
111
|
+
|
|
112
|
+
## 测试
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python3 -m unittest discover -s tests -v
|
|
116
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "autotd-buaa"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "BUAA TD automation command line utility"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "autoTD maintainers" }
|
|
13
|
+
]
|
|
14
|
+
keywords = ["buaa", "td", "cli"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
autotd = "auto_td.cli:main"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
package-dir = {"" = "src"}
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
auto_td = ["templates/*.json"]
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from .client import TDClient
|
|
7
|
+
from .logging_utils import setup_logging
|
|
8
|
+
from .models import User
|
|
9
|
+
from .quick import build_quick_user
|
|
10
|
+
from .runner import run_all_users
|
|
11
|
+
from .scheduler import run_forever
|
|
12
|
+
from .storage import AppStorage
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
16
|
+
parser = build_parser()
|
|
17
|
+
args = parser.parse_args(argv)
|
|
18
|
+
try:
|
|
19
|
+
return int(args.func(args) or 0)
|
|
20
|
+
except Exception as exc:
|
|
21
|
+
print(f"error: {exc}")
|
|
22
|
+
return 1
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
26
|
+
parser = argparse.ArgumentParser(prog="autotd")
|
|
27
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
28
|
+
|
|
29
|
+
init_parser = subparsers.add_parser("init", help="初始化 ~/.autoTD")
|
|
30
|
+
init_parser.add_argument("--from", dest="source", help="从旧 autoTD 项目导入配置")
|
|
31
|
+
init_parser.set_defaults(func=cmd_init)
|
|
32
|
+
|
|
33
|
+
user_parser = subparsers.add_parser("user", help="用户管理")
|
|
34
|
+
user_sub = user_parser.add_subparsers(dest="user_command", required=True)
|
|
35
|
+
user_add = user_sub.add_parser("add", help="新增用户")
|
|
36
|
+
_add_user_identity_args(user_add)
|
|
37
|
+
user_add.set_defaults(func=cmd_user_add)
|
|
38
|
+
|
|
39
|
+
user_sub.add_parser("list", help="列出用户").set_defaults(func=cmd_user_list)
|
|
40
|
+
user_show = user_sub.add_parser("show", help="查看用户")
|
|
41
|
+
user_show.add_argument("student_id")
|
|
42
|
+
user_show.set_defaults(func=cmd_user_show)
|
|
43
|
+
|
|
44
|
+
user_update = user_sub.add_parser("update", help="修改用户")
|
|
45
|
+
user_update.add_argument("student_id")
|
|
46
|
+
user_update.add_argument("--card-id")
|
|
47
|
+
user_update.add_argument("--entrance", type=int)
|
|
48
|
+
user_update.add_argument("--exit", type=int)
|
|
49
|
+
user_update.add_argument("--entrance-image")
|
|
50
|
+
user_update.add_argument("--exit-image")
|
|
51
|
+
user_update.add_argument("--rounds", type=int)
|
|
52
|
+
user_update.add_argument("--wait-time-min", type=int)
|
|
53
|
+
user_update.add_argument("--wait-time-max", type=int)
|
|
54
|
+
user_update.set_defaults(func=cmd_user_update)
|
|
55
|
+
|
|
56
|
+
user_delete = user_sub.add_parser("delete", help="删除用户")
|
|
57
|
+
user_delete.add_argument("student_id")
|
|
58
|
+
user_delete.set_defaults(func=cmd_user_delete)
|
|
59
|
+
|
|
60
|
+
user_count = user_sub.add_parser(
|
|
61
|
+
"count",
|
|
62
|
+
help="发送真实 checkdata 请求并解析服务器返回的本学期锻炼次数;可能产生服务器侧刷卡记录",
|
|
63
|
+
)
|
|
64
|
+
user_count.add_argument("student_id")
|
|
65
|
+
user_count.set_defaults(func=cmd_user_count)
|
|
66
|
+
|
|
67
|
+
image_parser = subparsers.add_parser("image", help="图片管理")
|
|
68
|
+
image_sub = image_parser.add_subparsers(dest="image_command", required=True)
|
|
69
|
+
image_add = image_sub.add_parser("add", help="新增图片")
|
|
70
|
+
image_add.add_argument("path")
|
|
71
|
+
image_add.add_argument("--name")
|
|
72
|
+
image_add.add_argument("--overwrite", action="store_true")
|
|
73
|
+
image_add.set_defaults(func=cmd_image_add)
|
|
74
|
+
image_sub.add_parser("list", help="列出图片").set_defaults(func=cmd_image_list)
|
|
75
|
+
|
|
76
|
+
run_parser = subparsers.add_parser("run", help="运行打卡")
|
|
77
|
+
run_parser.add_argument("--once", action="store_true", help="强制立即运行一次")
|
|
78
|
+
run_parser.set_defaults(func=cmd_run)
|
|
79
|
+
|
|
80
|
+
schedule_parser = subparsers.add_parser("schedule", help="定时配置")
|
|
81
|
+
schedule_sub = schedule_parser.add_subparsers(dest="schedule_command", required=True)
|
|
82
|
+
schedule_sub.add_parser("show", help="查看定时配置").set_defaults(func=cmd_schedule_show)
|
|
83
|
+
schedule_sub.add_parser("enable", help="启用定时").set_defaults(func=cmd_schedule_enable)
|
|
84
|
+
schedule_sub.add_parser("disable", help="关闭定时").set_defaults(func=cmd_schedule_disable)
|
|
85
|
+
schedule_set = schedule_sub.add_parser("set", help="设置定时参数")
|
|
86
|
+
schedule_set.add_argument("--poll-seconds", type=int)
|
|
87
|
+
schedule_set.add_argument("--windows")
|
|
88
|
+
schedule_set.set_defaults(func=cmd_schedule_set)
|
|
89
|
+
return parser
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def cmd_init(args) -> int:
|
|
93
|
+
storage = AppStorage()
|
|
94
|
+
storage.initialize(Path(args.source) if args.source else None)
|
|
95
|
+
print(f"initialized {storage.home}")
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def cmd_user_add(args) -> int:
|
|
100
|
+
storage = _storage()
|
|
101
|
+
if args.quick:
|
|
102
|
+
user = build_quick_user(storage, args.student_id, args.quick, card_id=args.card_id or "")
|
|
103
|
+
else:
|
|
104
|
+
_require_manual_user_args(args)
|
|
105
|
+
user = User.from_dict(_user_args_to_dict(args))
|
|
106
|
+
storage.save_user(user)
|
|
107
|
+
print(f"added {user.student_id}")
|
|
108
|
+
return 0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def cmd_user_list(_args) -> int:
|
|
112
|
+
storage = _storage()
|
|
113
|
+
users = storage.list_users()
|
|
114
|
+
if not users:
|
|
115
|
+
print("no users")
|
|
116
|
+
return 0
|
|
117
|
+
for user in users:
|
|
118
|
+
print(f"{user.student_id}\tcard={user.card_id}\tentrance={user.entrance_machine_id}\texit={user.exit_machine_id}")
|
|
119
|
+
return 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def cmd_user_show(args) -> int:
|
|
123
|
+
storage = _storage()
|
|
124
|
+
user = storage.get_user(args.student_id)
|
|
125
|
+
if user is None:
|
|
126
|
+
raise KeyError(f"用户不存在: {args.student_id}")
|
|
127
|
+
print(json.dumps(user, ensure_ascii=False, indent=2))
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def cmd_user_update(args) -> int:
|
|
132
|
+
storage = _storage()
|
|
133
|
+
current = storage.get_user(args.student_id)
|
|
134
|
+
if current is None:
|
|
135
|
+
raise KeyError(f"用户不存在: {args.student_id}")
|
|
136
|
+
updates = {
|
|
137
|
+
"card_id": args.card_id,
|
|
138
|
+
"entrance_machine_id": args.entrance,
|
|
139
|
+
"exit_machine_id": args.exit,
|
|
140
|
+
"entrance_image": args.entrance_image,
|
|
141
|
+
"exit_image": args.exit_image,
|
|
142
|
+
"rounds": args.rounds,
|
|
143
|
+
"wait_time_min": args.wait_time_min,
|
|
144
|
+
"wait_time_max": args.wait_time_max,
|
|
145
|
+
}
|
|
146
|
+
for key, value in updates.items():
|
|
147
|
+
if value is not None:
|
|
148
|
+
current[key] = value
|
|
149
|
+
user = User.from_dict(current)
|
|
150
|
+
storage.save_user(user)
|
|
151
|
+
print(f"updated {user.student_id}")
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def cmd_user_delete(args) -> int:
|
|
156
|
+
storage = _storage()
|
|
157
|
+
if not storage.delete_user(args.student_id):
|
|
158
|
+
raise KeyError(f"用户不存在: {args.student_id}")
|
|
159
|
+
print(f"deleted {args.student_id}")
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def cmd_user_count(args) -> int:
|
|
164
|
+
storage = _storage()
|
|
165
|
+
user = storage.get_user_model(args.student_id)
|
|
166
|
+
client = TDClient(storage.load_config())
|
|
167
|
+
count = client.query_count(user, machine_id=user.entrance_machine_id)
|
|
168
|
+
storage.set_last_count(user.student_id, count)
|
|
169
|
+
print(f"{user.student_id}: 本学期锻炼次数 {count}")
|
|
170
|
+
return 0
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def cmd_image_add(args) -> int:
|
|
174
|
+
storage = _storage()
|
|
175
|
+
name = storage.add_image(Path(args.path), name=args.name, overwrite=args.overwrite)
|
|
176
|
+
print(f"added image {name}")
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def cmd_image_list(_args) -> int:
|
|
181
|
+
storage = _storage()
|
|
182
|
+
for name in storage.list_images():
|
|
183
|
+
print(name)
|
|
184
|
+
return 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def cmd_run(args) -> int:
|
|
188
|
+
storage = _storage()
|
|
189
|
+
logger = setup_logging(storage)
|
|
190
|
+
|
|
191
|
+
def run_once():
|
|
192
|
+
result = run_all_users(storage, logger=logger)
|
|
193
|
+
print_summary(result)
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
if args.once or not storage.load_settings().get("schedule", {}).get("enabled", False):
|
|
197
|
+
run_once()
|
|
198
|
+
else:
|
|
199
|
+
run_forever(storage, run_once)
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def cmd_schedule_show(_args) -> int:
|
|
204
|
+
storage = _storage()
|
|
205
|
+
print(json.dumps(storage.load_settings().get("schedule", {}), ensure_ascii=False, indent=2))
|
|
206
|
+
return 0
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def cmd_schedule_enable(_args) -> int:
|
|
210
|
+
storage = _storage()
|
|
211
|
+
storage.update_settings({"schedule": {"enabled": True}})
|
|
212
|
+
print("schedule enabled")
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def cmd_schedule_disable(_args) -> int:
|
|
217
|
+
storage = _storage()
|
|
218
|
+
storage.update_settings({"schedule": {"enabled": False}})
|
|
219
|
+
print("schedule disabled")
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def cmd_schedule_set(args) -> int:
|
|
224
|
+
storage = _storage()
|
|
225
|
+
patch = {"schedule": {}}
|
|
226
|
+
if args.poll_seconds is not None:
|
|
227
|
+
if args.poll_seconds <= 0:
|
|
228
|
+
raise ValueError("poll-seconds 必须大于 0")
|
|
229
|
+
patch["schedule"]["poll_seconds"] = args.poll_seconds
|
|
230
|
+
if args.windows:
|
|
231
|
+
patch["schedule"]["windows"] = [item.strip() for item in args.windows.split(",") if item.strip()]
|
|
232
|
+
storage.update_settings(patch)
|
|
233
|
+
print("schedule updated")
|
|
234
|
+
return 0
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def print_summary(result) -> None:
|
|
238
|
+
print(f"total={result.total} success={result.success_count} failure={result.failure_count}")
|
|
239
|
+
for item in result.results:
|
|
240
|
+
status = "success" if item.success else "failure"
|
|
241
|
+
print(f"{item.index}. {item.student_id}: {status} {item.message}")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _storage() -> AppStorage:
|
|
245
|
+
storage = AppStorage()
|
|
246
|
+
if not storage.home.exists():
|
|
247
|
+
raise FileNotFoundError(f"配置目录不存在,请先运行 autotd init: {storage.home}")
|
|
248
|
+
return storage
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _add_user_identity_args(parser: argparse.ArgumentParser) -> None:
|
|
252
|
+
parser.add_argument("student_id")
|
|
253
|
+
parser.add_argument("--card-id")
|
|
254
|
+
parser.add_argument("--quick", help="快速配置校区:沙河 或 学院路")
|
|
255
|
+
parser.add_argument("--entrance", type=int)
|
|
256
|
+
parser.add_argument("--exit", type=int)
|
|
257
|
+
parser.add_argument("--entrance-image")
|
|
258
|
+
parser.add_argument("--exit-image")
|
|
259
|
+
parser.add_argument("--rounds", type=int, default=3)
|
|
260
|
+
parser.add_argument("--wait-time-min", type=int, default=180)
|
|
261
|
+
parser.add_argument("--wait-time-max", type=int, default=240)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _user_args_to_dict(args) -> dict:
|
|
265
|
+
return {
|
|
266
|
+
"student_id": args.student_id,
|
|
267
|
+
"card_id": args.card_id or "",
|
|
268
|
+
"entrance_machine_id": args.entrance,
|
|
269
|
+
"exit_machine_id": args.exit,
|
|
270
|
+
"entrance_image": args.entrance_image,
|
|
271
|
+
"exit_image": args.exit_image,
|
|
272
|
+
"rounds": args.rounds,
|
|
273
|
+
"wait_time_min": args.wait_time_min,
|
|
274
|
+
"wait_time_max": args.wait_time_max,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _require_manual_user_args(args) -> None:
|
|
279
|
+
missing = []
|
|
280
|
+
for attr, flag in (
|
|
281
|
+
("entrance", "--entrance"),
|
|
282
|
+
("exit", "--exit"),
|
|
283
|
+
("entrance_image", "--entrance-image"),
|
|
284
|
+
("exit_image", "--exit-image"),
|
|
285
|
+
):
|
|
286
|
+
if getattr(args, attr) is None:
|
|
287
|
+
missing.append(flag)
|
|
288
|
+
if missing:
|
|
289
|
+
raise ValueError("非 quick 添加用户时必须提供: " + ", ".join(missing))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
raise SystemExit(main())
|