bb-sync 1.9.0__py3-none-any.whl

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.
@@ -0,0 +1,143 @@
1
+ """Blackboard 领域模型与站点常量。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from datetime import date
8
+
9
+ BASE = "https://bb.cuhk.edu.cn"
10
+
11
+ #: ADFS 页面元素选择器
12
+ ADFS_USER = "input[name='userNameInput'], #userNameInput"
13
+ ADFS_PASS = "input[name='PasswordInput'], #passwordInput"
14
+ ADFS_NEXT = "#nextButton, #submitButton" # 第一页「下一步」
15
+ ADFS_SUBMIT = "#submitButton, #nextButton, span.submit" # 密码页「登录」
16
+ LOGIN_URL = f"{BASE}/webapps/login/"
17
+
18
+ _PORTAL_HOME = f"{BASE}/webapps/portal/execute/tabs/tabAction?tab_tab_group_id=_1_1"
19
+ _PORTAL_COURSES = f"{BASE}/webapps/portal/execute/tabs/tabAction?tab_tab_group_id=_25_1"
20
+
21
+
22
+ @dataclass
23
+ class Course:
24
+ """一门课程。"""
25
+
26
+ bb_id: str # e.g. _12345_1
27
+ title: str # 原始标题
28
+ code: str = "" # 提取的课程代码,如 CSC5010
29
+ slug: str = "" # 本地文件夹名
30
+
31
+ def __post_init__(self) -> None:
32
+ m = re.search(r"\b([A-Z]{3,4}\d{4}[A-Z]?)\b", self.title.upper())
33
+ if m:
34
+ self.code = m.group(1)
35
+
36
+ def as_dict(self) -> dict[str, str]:
37
+ return {"id": self.bb_id, "code": self.code, "title": self.title, "folder": self.slug}
38
+
39
+
40
+ @dataclass
41
+ class FileItem:
42
+ """内容区里的一个待下载条目。"""
43
+
44
+ name: str | None
45
+ url: str
46
+ category: str # lectures / assignments / tutorials / other
47
+ folder_hint: str = "" # 内容区路径提示
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class DueItem:
52
+ """一项待办:课程主页 Due / To Do 任务,或从公告整理出的课堂测验 / 考试。"""
53
+
54
+ course_id: str
55
+ course_code: str
56
+ course_title: str
57
+ title: str
58
+ due_date: date | None
59
+ due_text: str = ""
60
+ kind: str = "task" # task = To Do 模块;quiz / exam = 公告里的课堂测验 / 考试
61
+
62
+ @property
63
+ def course_label(self) -> str:
64
+ """优先显示课程代码,没有代码时退回原始标题。"""
65
+ return self.course_code or self.course_title or self.course_id
66
+
67
+ def as_dict(self) -> dict[str, str | None]:
68
+ return {
69
+ "course_id": self.course_id,
70
+ "course_code": self.course_code,
71
+ "course_title": self.course_title,
72
+ "title": self.title,
73
+ "due_date": self.due_date.isoformat() if self.due_date else None,
74
+ "due_text": self.due_text,
75
+ "kind": self.kind,
76
+ }
77
+
78
+
79
+ @dataclass
80
+ class SyncStats:
81
+ """一次同步的统计结果。"""
82
+
83
+ downloaded: int = 0
84
+ exists: int = 0
85
+ would_download: int = 0
86
+ empty: int = 0
87
+ failed: int = 0
88
+ courses: list[str] = field(default_factory=list)
89
+
90
+ def bump(self, status: str) -> None:
91
+ key = status.replace("-", "_")
92
+ if hasattr(self, key):
93
+ setattr(self, key, getattr(self, key) + 1)
94
+
95
+ def as_dict(self) -> dict[str, object]:
96
+ return {
97
+ "downloaded": self.downloaded,
98
+ "exists": self.exists,
99
+ "would_download": self.would_download,
100
+ "empty": self.empty,
101
+ "failed": self.failed,
102
+ "courses": self.courses,
103
+ }
104
+
105
+
106
+ @dataclass
107
+ class DueStats:
108
+ """一次 ``bb-sync due`` 的结果。"""
109
+
110
+ path: str = ""
111
+ items: list[DueItem] = field(default_factory=list)
112
+ failed: int = 0 # Due / To Do 模块抓取失败的课程数
113
+ announcement_failed: int = 0 # 公告抓取失败的课程数
114
+ courses: list[str] = field(default_factory=list)
115
+
116
+ @property
117
+ def total(self) -> int:
118
+ return len(self.items)
119
+
120
+ def as_dict(self) -> dict[str, object]:
121
+ return {
122
+ "path": self.path,
123
+ "total": self.total,
124
+ "failed": self.failed,
125
+ "announcement_failed": self.announcement_failed,
126
+ "courses": self.courses,
127
+ "items": [item.as_dict() for item in self.items],
128
+ }
129
+
130
+
131
+ __all__ = [
132
+ "ADFS_NEXT",
133
+ "ADFS_PASS",
134
+ "ADFS_SUBMIT",
135
+ "ADFS_USER",
136
+ "BASE",
137
+ "LOGIN_URL",
138
+ "Course",
139
+ "DueItem",
140
+ "DueStats",
141
+ "FileItem",
142
+ "SyncStats",
143
+ ]