refactor(platforms): 平台能力改为子类实现,注册不再逐项绑定函数
- Platform 由 dataclass 字段绑定改为基类:id / label / entries 是类属性,能力是子类方法, 未实现的阶段保持基类 None,capabilities() 与 /api/platforms 输出不变 - gdgxk:session(原 new_session)/ login / list_courses / discover / study / _relogin 收进 class Gdgxk(Platform),解析与上报 helper 仍是模块级函数 - guanyixue:改成 class Guanyixue(Platform),方法转调 jspx / study - 删除 src/study_tool/pugins/ 草稿目录(内容与 platforms/gdgxk.py 重复, pugin.json 里按名字声明 study / entries 的绑定方式作废,元数据改由类属性承载) - 引擎 / 路由 / quiz / contract / 前端未改动 - 同步 docs/architecture.md、AGENTS.md、docs/OPTIMIZATION.md
This commit is contained in:
+34
-25
@@ -61,24 +61,24 @@ pf = registry.get(d.get("platform"))
|
||||
pf.study(key, s, d, workers=..., cap=..., enable_quiz=..., ...)
|
||||
```
|
||||
|
||||
`Platform` 契约(见 `platforms/base.py`):
|
||||
`Platform` 契约(见 `platforms/base.py`):一个平台 = 一个 `Platform` 子类,类属性放元数据,
|
||||
自己确实有的阶段实现成方法;注册时只实例化子类,不再逐项绑定函数。
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Platform:
|
||||
id: str # course["platform"] 的取值
|
||||
label: str # 日志 / 界面展示名,如「莞易学」
|
||||
study: Callable # (key, s, d, **opts) -> None
|
||||
entries: tuple[str, ...] # 登录入口 / 学习入口的 URL 片段
|
||||
# 账号级能力:添加账号时选中的平台决定登录 / 刷新课程 / 发现课程走哪套实现
|
||||
session: Callable # (cookies) -> requests.Session
|
||||
dump_cookies: Callable # (session) -> [cookie]
|
||||
login: Callable # (username, password) -> (session, user)
|
||||
list_courses: Callable # (session) -> [course]
|
||||
discover: Callable # (session, plan_id, project_id, name) -> 课程描述
|
||||
renew_session: Callable # (session) -> bool,无密码续期
|
||||
profile: Callable # (session) -> 显示名
|
||||
# 槽位为 None = 该平台没有这项能力;capabilities() 按非空槽位推导
|
||||
id = "" # course["platform"] 的取值
|
||||
label = "" # 日志 / 界面展示名,如「莞易学」
|
||||
entries = () # 登录入口 / 学习入口的 URL 片段
|
||||
# 槽位:子类实现哪个阶段就覆盖成方法(第一个参数是 self);保持 None = 该平台没有这项能力
|
||||
study = None # study(self, key, s, d, **opts) -> None
|
||||
session = None # session(self, cookies) -> requests.Session
|
||||
dump_cookies = None # dump_cookies(self, session) -> [cookie]
|
||||
login = None # login(self, username, password) -> (session, user)
|
||||
list_courses = None # list_courses(self, session) -> [course]
|
||||
discover = None # discover(self, session, plan_id, project_id, name) -> 课程描述
|
||||
renew_session = None # renew_session(self, session) -> bool,无密码续期
|
||||
profile = None # profile(self, session) -> 显示名
|
||||
# capabilities() 按非空槽位推导能力名
|
||||
```
|
||||
|
||||
`registry.detect(url, html)` 按 `entries` 命中平台;「添加账号」时选择的平台写进
|
||||
@@ -130,19 +130,28 @@ quiz_key / cookies / plan / project / name`。插件可以只取自己需要的
|
||||
from study_tool.platforms import registry
|
||||
from study_tool.platforms.base import Platform
|
||||
|
||||
def _study(key, s, d, **opts):
|
||||
# 用 s(requests.Session,带该账号 cookie)把课程活动跑完
|
||||
# 日志一律 runtime.log(key, ...)
|
||||
...
|
||||
class MyPlatform(Platform):
|
||||
id = "<平台 id>"
|
||||
label = "展示名"
|
||||
entries = ("<登录入口域名>", "<学习入口域名>")
|
||||
|
||||
PLATFORM = registry.register(Platform(
|
||||
id="<平台 id>", label="展示名", study=_study,
|
||||
entries=("<登录入口域名>", "<学习入口域名>"),
|
||||
session=<new_session>, dump_cookies=<dump_cookies>, login=<login>,
|
||||
list_courses=<list_courses>, discover=<discover>,
|
||||
))
|
||||
def session(self, cookies=None): ...
|
||||
def dump_cookies(self, s): ...
|
||||
def login(self, username, password): ...
|
||||
def list_courses(self, s): ...
|
||||
def discover(self, s, plan_id, project_id, name=""): ...
|
||||
def profile(self, s): ...
|
||||
|
||||
def study(self, key, s, d, **opts):
|
||||
# 用 s(requests.Session,带该账号 cookie)把课程活动跑完
|
||||
# 日志一律 runtime.log(key, ...)
|
||||
...
|
||||
|
||||
PLATFORM = registry.register(MyPlatform())
|
||||
```
|
||||
|
||||
平台确实没有的阶段不要写,保持基类的 `None`,引擎按能力判断走不走那个分支。
|
||||
|
||||
2. 在 `platforms/__init__.py` 的 `load_builtin()` 里加一行 `from study_tool.platforms import <名字>`。
|
||||
|
||||
3. 平台注册后会自动出现在「添加账号」的平台下拉框;账号选了该平台,登录 / 刷新课程 /
|
||||
|
||||
Reference in New Issue
Block a user