@qzsy/zhidao-skill 0.0.0-stage → 0.1.1
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.
- package/SKILL.md +63 -0
- package/dist/catalog.d.ts +81 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +214 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +212 -0
- package/dist/cli.js.map +1 -0
- package/dist/form.d.ts +17 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +37 -0
- package/dist/form.js.map +1 -0
- package/dist/http.d.ts +74 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +94 -0
- package/dist/http.js.map +1 -0
- package/dist/login.d.ts +43 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +87 -0
- package/dist/login.js.map +1 -0
- package/dist/session.d.ts +77 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +133 -0
- package/dist/session.js.map +1 -0
- package/flows.md +51 -0
- package/openapi/zhidao.openapi.yml +242072 -0
- package/package.json +36 -6
- package/README.md +0 -3
package/dist/form.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按至道页面的 jQuery 方式编码表单。
|
|
3
|
+
* JSON 请求体在发出去之前先过这里。
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 把对象编码成查询串。
|
|
7
|
+
* `undefined` 省略,`null` 编码成空字符串。
|
|
8
|
+
*/
|
|
9
|
+
export function encodeForm(data) {
|
|
10
|
+
if (!data)
|
|
11
|
+
return "";
|
|
12
|
+
const pairs = [];
|
|
13
|
+
for (const key of Object.keys(data)) {
|
|
14
|
+
const value = data[key];
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
continue;
|
|
17
|
+
appendParam(pairs, key, value);
|
|
18
|
+
}
|
|
19
|
+
return pairs.join("&");
|
|
20
|
+
}
|
|
21
|
+
function appendParam(pairs, key, value) {
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
value.forEach((item, index) => {
|
|
24
|
+
const marker = item !== null && typeof item === "object" ? String(index) : "";
|
|
25
|
+
appendParam(pairs, `${key}[${marker}]`, item);
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (value !== null && typeof value === "object") {
|
|
30
|
+
for (const child of Object.keys(value))
|
|
31
|
+
appendParam(pairs, `${key}[${child}]`, value[child]);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const text = value == null ? "" : String(value);
|
|
35
|
+
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(text)}`);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=form.js.map
|
package/dist/form.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAA2C;IACpE,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACpB,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;QACvB,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QACjC,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;IAChC,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED,SAAS,WAAW,CAAC,KAAe,EAAE,GAAW,EAAE,KAAgB;IACjE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC5B,MAAM,MAAM,GAAG,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAC7E,WAAW,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,CAAA;QAC/C,CAAC,CAAC,CAAA;QACF,OAAM;IACR,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,WAAW,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,KAAK,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5F,OAAM;IACR,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC/C,KAAK,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACtE,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用 Node.js 全局 fetch 调用至道后台。
|
|
3
|
+
* Cookie 从会话带上,响应里的 Set-Cookie 写回同一份会话。
|
|
4
|
+
*/
|
|
5
|
+
import type { FormValue } from "./form.js";
|
|
6
|
+
import type { ZhidaoSession } from "./session.js";
|
|
7
|
+
/**
|
|
8
|
+
* 一次上游调用。
|
|
9
|
+
* `path` 以 `/` 开头,和 OpenAPI 里的路径一致。
|
|
10
|
+
*/
|
|
11
|
+
export interface ZhidaoCall {
|
|
12
|
+
/**
|
|
13
|
+
* HTTP 方法。
|
|
14
|
+
* 使用大写,例如 `GET`、`POST`。
|
|
15
|
+
*/
|
|
16
|
+
method: string;
|
|
17
|
+
/**
|
|
18
|
+
* 上游路径。
|
|
19
|
+
* 例如 `/login/captcha`、`/tableRecord/submitTaskNew`。
|
|
20
|
+
*/
|
|
21
|
+
path: string;
|
|
22
|
+
/**
|
|
23
|
+
* 查询参数。
|
|
24
|
+
* 登录令牌会另外附在 `idToken` 上,不必在这里重复。
|
|
25
|
+
*/
|
|
26
|
+
query?: Record<string, FormValue>;
|
|
27
|
+
/**
|
|
28
|
+
* 表单字段。
|
|
29
|
+
* POST 时按 `application/x-www-form-urlencoded` 提交。
|
|
30
|
+
*/
|
|
31
|
+
body?: Record<string, FormValue>;
|
|
32
|
+
/**
|
|
33
|
+
* 是否附带登录令牌。
|
|
34
|
+
* 验证码和登录请求本身不要带。
|
|
35
|
+
*/
|
|
36
|
+
withToken?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* fetch 的原始结果。
|
|
40
|
+
* 业务是否成功由调用方看 JSON 里的 `code`。
|
|
41
|
+
*/
|
|
42
|
+
export interface ZhidaoHttpResult {
|
|
43
|
+
/**
|
|
44
|
+
* HTTP 状态码。
|
|
45
|
+
* 跟随重定向之后的最终响应。
|
|
46
|
+
*/
|
|
47
|
+
status: number;
|
|
48
|
+
/**
|
|
49
|
+
* 响应 Content-Type。
|
|
50
|
+
* 不带参数。
|
|
51
|
+
*/
|
|
52
|
+
contentType: string;
|
|
53
|
+
/**
|
|
54
|
+
* 响应体字节。
|
|
55
|
+
* 验证码图片直接用它转 base64。
|
|
56
|
+
*/
|
|
57
|
+
bytes: Uint8Array;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 发送一次请求,并把新 Cookie 留在会话对象上。
|
|
61
|
+
* 调用方负责 `saveSession`。
|
|
62
|
+
*/
|
|
63
|
+
export declare function request(session: ZhidaoSession, call: ZhidaoCall): Promise<ZhidaoHttpResult>;
|
|
64
|
+
/**
|
|
65
|
+
* 拼出最终地址。
|
|
66
|
+
* 有令牌且这次调用允许时,加上查询参数 `idToken`。
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildUrl(session: ZhidaoSession, call: ZhidaoCall): string;
|
|
69
|
+
/**
|
|
70
|
+
* 根据文件头判断图片类型。
|
|
71
|
+
* 对不上时返回空字符串。
|
|
72
|
+
*/
|
|
73
|
+
export declare function imageMediaType(headerType: string, bytes: Uint8Array): string;
|
|
74
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjD;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IAEZ;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAEjC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAEhC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,KAAK,EAAE,UAAU,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAqCjG;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAMzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAmB5E"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { encodeForm } from "./form.js";
|
|
2
|
+
/**
|
|
3
|
+
* 发送一次请求,并把新 Cookie 留在会话对象上。
|
|
4
|
+
* 调用方负责 `saveSession`。
|
|
5
|
+
*/
|
|
6
|
+
export async function request(session, call) {
|
|
7
|
+
const fetchImpl = globalThis.fetch;
|
|
8
|
+
if (!fetchImpl)
|
|
9
|
+
throw new Error("当前 Node 没有 fetch");
|
|
10
|
+
let current = buildUrl(session, call);
|
|
11
|
+
let method = call.method.toUpperCase();
|
|
12
|
+
let body;
|
|
13
|
+
if (method !== "GET" && method !== "HEAD")
|
|
14
|
+
body = encodeForm(call.body);
|
|
15
|
+
for (let hop = 0; hop < 5; hop += 1) {
|
|
16
|
+
const headers = new Headers();
|
|
17
|
+
headers.set("accept", "application/json, text/plain, image/*, */*");
|
|
18
|
+
headers.set("x-requested-with", "XMLHttpRequest");
|
|
19
|
+
headers.set("origin", session.baseUrl);
|
|
20
|
+
headers.set("referer", `${session.baseUrl}/login`);
|
|
21
|
+
if (Object.keys(session.cookies).length) {
|
|
22
|
+
headers.set("cookie", Object.entries(session.cookies).map(([key, value]) => `${key}=${value}`).join("; "));
|
|
23
|
+
}
|
|
24
|
+
if (body !== undefined)
|
|
25
|
+
headers.set("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
|
|
26
|
+
const response = await fetchImpl(current, { method, headers, body, redirect: "manual" });
|
|
27
|
+
absorbCookies(session, response);
|
|
28
|
+
if (response.status < 300 || response.status >= 400) {
|
|
29
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
30
|
+
const contentType = (response.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
31
|
+
return { status: response.status, contentType, bytes };
|
|
32
|
+
}
|
|
33
|
+
const location = response.headers.get("location");
|
|
34
|
+
if (!location) {
|
|
35
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
36
|
+
const contentType = (response.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
37
|
+
return { status: response.status, contentType, bytes };
|
|
38
|
+
}
|
|
39
|
+
current = new URL(location, current).toString();
|
|
40
|
+
if (response.status === 303 || method !== "GET") {
|
|
41
|
+
method = "GET";
|
|
42
|
+
body = undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throw new Error("上游重定向次数过多");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 拼出最终地址。
|
|
49
|
+
* 有令牌且这次调用允许时,加上查询参数 `idToken`。
|
|
50
|
+
*/
|
|
51
|
+
export function buildUrl(session, call) {
|
|
52
|
+
const path = call.path.startsWith("/") ? call.path : `/${call.path}`;
|
|
53
|
+
const query = { ...(call.query ?? {}) };
|
|
54
|
+
if (call.withToken !== false && session.token)
|
|
55
|
+
query.idToken = session.token;
|
|
56
|
+
const encoded = encodeForm(query);
|
|
57
|
+
return encoded ? `${session.baseUrl}${path}?${encoded}` : `${session.baseUrl}${path}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 根据文件头判断图片类型。
|
|
61
|
+
* 对不上时返回空字符串。
|
|
62
|
+
*/
|
|
63
|
+
export function imageMediaType(headerType, bytes) {
|
|
64
|
+
if (bytes.length >= 8 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
|
|
65
|
+
return "image/png";
|
|
66
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
|
|
67
|
+
return "image/jpeg";
|
|
68
|
+
if (bytes.length >= 6 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)
|
|
69
|
+
return "image/gif";
|
|
70
|
+
if (bytes.length >= 12 &&
|
|
71
|
+
bytes[0] === 0x52 &&
|
|
72
|
+
bytes[1] === 0x49 &&
|
|
73
|
+
bytes[2] === 0x46 &&
|
|
74
|
+
bytes[3] === 0x46 &&
|
|
75
|
+
bytes[8] === 0x57 &&
|
|
76
|
+
bytes[9] === 0x45 &&
|
|
77
|
+
bytes[10] === 0x42 &&
|
|
78
|
+
bytes[11] === 0x50) {
|
|
79
|
+
return "image/webp";
|
|
80
|
+
}
|
|
81
|
+
if (headerType.startsWith("image/"))
|
|
82
|
+
return headerType;
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
function absorbCookies(session, response) {
|
|
86
|
+
const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
|
|
87
|
+
for (const raw of setCookies) {
|
|
88
|
+
const pair = raw.split(";")[0] ?? "";
|
|
89
|
+
const eq = pair.indexOf("=");
|
|
90
|
+
if (eq > 0)
|
|
91
|
+
session.cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/login.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ZhidaoSession } from "./session.js";
|
|
2
|
+
/**
|
|
3
|
+
* 交给用户看的验证码。
|
|
4
|
+
* `image` 是 data URL,可以直接放进 Markdown 图片。
|
|
5
|
+
*/
|
|
6
|
+
export interface CaptchaImage {
|
|
7
|
+
/**
|
|
8
|
+
* data URL。
|
|
9
|
+
* 形如 `data:image/png;base64,...`。
|
|
10
|
+
*/
|
|
11
|
+
image: string;
|
|
12
|
+
/**
|
|
13
|
+
* 图片媒体类型。
|
|
14
|
+
* 来自文件头,例如 `image/png`。
|
|
15
|
+
*/
|
|
16
|
+
mediaType: string;
|
|
17
|
+
/**
|
|
18
|
+
* 写到本机的图片路径。
|
|
19
|
+
* 对话里展示不出来 data URL 时,改读这个文件。
|
|
20
|
+
*/
|
|
21
|
+
file: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 拉取一张图形验证码并保存 Cookie。
|
|
25
|
+
* 同一次登录里的 `login` 会复用这份 Cookie。
|
|
26
|
+
*/
|
|
27
|
+
export declare function fetchCaptcha(session: ZhidaoSession): Promise<CaptchaImage>;
|
|
28
|
+
/**
|
|
29
|
+
* 用账号、密码和用户看到的验证码登录。
|
|
30
|
+
* 成功后把 `token` 和 Cookie 写进会话,不保存密码。
|
|
31
|
+
*/
|
|
32
|
+
export declare function login(session: ZhidaoSession, userName: string, password: string, captcha: string, priorityId?: string): Promise<Record<string, unknown>>;
|
|
33
|
+
/**
|
|
34
|
+
* 读取登录页上的 RSA 公钥。
|
|
35
|
+
* 页面没有 `setPublicKey` 时抛错。
|
|
36
|
+
*/
|
|
37
|
+
export declare function publicKeyFor(baseUrl: string): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* 把账号和密码打成 `identification`。
|
|
40
|
+
* 算法与登录页一致,使用 RSA PKCS1。
|
|
41
|
+
*/
|
|
42
|
+
export declare function encryptLogin(publicKey: string, userName: string, password: string): string;
|
|
43
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAQA,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAE3E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAahF;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAkB3J;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK1F"}
|
package/dist/login.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图形验证码和账号密码登录。
|
|
3
|
+
* 账号密码按登录页的 RSA PKCS1 加密后再提交。
|
|
4
|
+
*/
|
|
5
|
+
import { constants, publicEncrypt } from "node:crypto";
|
|
6
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { imageMediaType, request } from "./http.js";
|
|
9
|
+
import { saveSession, sessionPath } from "./session.js";
|
|
10
|
+
/**
|
|
11
|
+
* 拉取一张图形验证码并保存 Cookie。
|
|
12
|
+
* 同一次登录里的 `login` 会复用这份 Cookie。
|
|
13
|
+
*/
|
|
14
|
+
export async function fetchCaptcha(session) {
|
|
15
|
+
const result = await request(session, { method: "GET", path: "/login/captcha", withToken: false });
|
|
16
|
+
saveSession(session);
|
|
17
|
+
const mediaType = imageMediaType(result.contentType, result.bytes);
|
|
18
|
+
if (!mediaType) {
|
|
19
|
+
const text = new TextDecoder().decode(result.bytes).slice(0, 180);
|
|
20
|
+
throw new Error(`验证码不是图片(HTTP ${result.status})${text ? `:${text}` : ""}`);
|
|
21
|
+
}
|
|
22
|
+
const base64 = Buffer.from(result.bytes).toString("base64");
|
|
23
|
+
const file = join(dirname(sessionPath()), "captcha.png");
|
|
24
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
25
|
+
writeFileSync(file, result.bytes);
|
|
26
|
+
return { image: `data:${mediaType};base64,${base64}`, mediaType, file };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 用账号、密码和用户看到的验证码登录。
|
|
30
|
+
* 成功后把 `token` 和 Cookie 写进会话,不保存密码。
|
|
31
|
+
*/
|
|
32
|
+
export async function login(session, userName, password, captcha, priorityId = "1") {
|
|
33
|
+
const key = await publicKeyFor(session.baseUrl);
|
|
34
|
+
const identification = encryptLogin(key, userName, password);
|
|
35
|
+
const result = await request(session, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
path: "/login/doLogin",
|
|
38
|
+
withToken: false,
|
|
39
|
+
body: {
|
|
40
|
+
captcha,
|
|
41
|
+
identification,
|
|
42
|
+
uniqueId: session.uniqueId,
|
|
43
|
+
priorityId,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const payload = parseJson(result.bytes);
|
|
47
|
+
if (typeof payload.token === "string" && payload.token)
|
|
48
|
+
session.token = payload.token;
|
|
49
|
+
saveSession(session);
|
|
50
|
+
return payload;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 读取登录页上的 RSA 公钥。
|
|
54
|
+
* 页面没有 `setPublicKey` 时抛错。
|
|
55
|
+
*/
|
|
56
|
+
export async function publicKeyFor(baseUrl) {
|
|
57
|
+
const response = await fetch(new URL("/login", baseUrl));
|
|
58
|
+
const html = await response.text();
|
|
59
|
+
const match = html.match(/setPublicKey\(["']([A-Za-z0-9+/=]+)["']\)/);
|
|
60
|
+
const key = match?.[1];
|
|
61
|
+
if (!key)
|
|
62
|
+
throw new Error("登录页没有读到加密公钥");
|
|
63
|
+
return key;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 把账号和密码打成 `identification`。
|
|
67
|
+
* 算法与登录页一致,使用 RSA PKCS1。
|
|
68
|
+
*/
|
|
69
|
+
export function encryptLogin(publicKey, userName, password) {
|
|
70
|
+
const body = publicKey.match(/.{1,64}/g)?.join("\n") ?? publicKey;
|
|
71
|
+
const pem = `-----BEGIN PUBLIC KEY-----\n${body}\n-----END PUBLIC KEY-----`;
|
|
72
|
+
const payload = Buffer.from(JSON.stringify({ userName, password }), "utf8");
|
|
73
|
+
return publicEncrypt({ key: pem, padding: constants.RSA_PKCS1_PADDING }, payload).toString("base64");
|
|
74
|
+
}
|
|
75
|
+
function parseJson(bytes) {
|
|
76
|
+
const text = new TextDecoder().decode(bytes);
|
|
77
|
+
try {
|
|
78
|
+
const payload = JSON.parse(text);
|
|
79
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload))
|
|
80
|
+
return payload;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return { code: -1, msg: text.slice(0, 180) || "登录响应不是 JSON" };
|
|
84
|
+
}
|
|
85
|
+
return { code: -1, msg: "登录响应不是 JSON 对象" };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一份已保存的登录态。
|
|
3
|
+
* 验证码和后续请求都读这里的 Cookie。
|
|
4
|
+
*/
|
|
5
|
+
export interface ZhidaoSession {
|
|
6
|
+
/**
|
|
7
|
+
* 后台根地址。
|
|
8
|
+
* 不要以斜杠结尾,默认是泉州五中至道 OA。
|
|
9
|
+
*/
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
/**
|
|
12
|
+
* 上游 Cookie。
|
|
13
|
+
* 键是 Cookie 名。没有键就视为未登录。
|
|
14
|
+
*/
|
|
15
|
+
cookies: Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* 登录令牌。
|
|
18
|
+
* 来自 `doLogin` 的 `token`,请求时作为查询参数 `idToken`。
|
|
19
|
+
*/
|
|
20
|
+
token?: string;
|
|
21
|
+
/**
|
|
22
|
+
* 设备标识。
|
|
23
|
+
* 对应登录请求里的 `uniqueId`,同一份会话保持不变。
|
|
24
|
+
*/
|
|
25
|
+
uniqueId: string;
|
|
26
|
+
/**
|
|
27
|
+
* 最近一次写盘时间。
|
|
28
|
+
* ISO 8601 字符串。
|
|
29
|
+
*/
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 会话文件路径。
|
|
34
|
+
* `ZHIDAO_SESSION` 优先,否则用用户目录下的 `.zhidao-oa/session.json`。
|
|
35
|
+
*/
|
|
36
|
+
export declare function sessionPath(): string;
|
|
37
|
+
/**
|
|
38
|
+
* 读会话,没有文件时给一份空会话。
|
|
39
|
+
* 环境变量里的 Cookie 和令牌会盖过文件。
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadSession(): ZhidaoSession;
|
|
42
|
+
/**
|
|
43
|
+
* 把会话写回磁盘。
|
|
44
|
+
* 不写入账号和密码。
|
|
45
|
+
*/
|
|
46
|
+
export declare function saveSession(session: ZhidaoSession): void;
|
|
47
|
+
/**
|
|
48
|
+
* 是否已经有上游 Cookie。
|
|
49
|
+
* 技能把 Cookie 当作登录秘钥,空对象表示还要走登录。
|
|
50
|
+
*/
|
|
51
|
+
export declare function hasCookie(session: ZhidaoSession): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* 是否可以调用业务接口。
|
|
54
|
+
* 需要 Cookie,也需要登录令牌。
|
|
55
|
+
*/
|
|
56
|
+
export declare function isLoggedIn(session: ZhidaoSession): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* 换成另一个后台时清掉旧站点的登录态。
|
|
59
|
+
* 地址没变时原样返回。
|
|
60
|
+
*/
|
|
61
|
+
export declare function assignBase(session: ZhidaoSession, value: string | undefined): void;
|
|
62
|
+
/**
|
|
63
|
+
* 丢掉 Cookie 和令牌。
|
|
64
|
+
* `uniqueId` 保留,避免同一台机器的设备标识来回变。
|
|
65
|
+
*/
|
|
66
|
+
export declare function clearLogin(session: ZhidaoSession): void;
|
|
67
|
+
/**
|
|
68
|
+
* 校正后台根地址。
|
|
69
|
+
* 只接受 http 和 https,并去掉末尾斜杠。
|
|
70
|
+
*/
|
|
71
|
+
export declare function normalizeBase(value: string | undefined): string;
|
|
72
|
+
/**
|
|
73
|
+
* 给技能看的令牌摘要。
|
|
74
|
+
* 只留前 8 位,避免整段令牌出现在对话里。
|
|
75
|
+
*/
|
|
76
|
+
export declare function tokenPreview(token: string | undefined): string;
|
|
77
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE/B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,aAAa,CAS3C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAKxD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAEzD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAOlF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAGvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK/D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG9D"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本机至道会话。
|
|
3
|
+
* Cookie 和登录令牌写在用户目录,不进仓库。
|
|
4
|
+
*/
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
const DEFAULT_BASE = "https://wzoa.qz5z.com";
|
|
10
|
+
/**
|
|
11
|
+
* 会话文件路径。
|
|
12
|
+
* `ZHIDAO_SESSION` 优先,否则用用户目录下的 `.zhidao-oa/session.json`。
|
|
13
|
+
*/
|
|
14
|
+
export function sessionPath() {
|
|
15
|
+
const override = process.env.ZHIDAO_SESSION?.trim();
|
|
16
|
+
if (override)
|
|
17
|
+
return override;
|
|
18
|
+
return join(homedir(), ".zhidao-oa", "session.json");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 读会话,没有文件时给一份空会话。
|
|
22
|
+
* 环境变量里的 Cookie 和令牌会盖过文件。
|
|
23
|
+
*/
|
|
24
|
+
export function loadSession() {
|
|
25
|
+
const session = readSessionFile();
|
|
26
|
+
const base = process.env.ZHIDAO_BASE_URL?.trim();
|
|
27
|
+
if (base)
|
|
28
|
+
session.baseUrl = normalizeBase(base);
|
|
29
|
+
const cookie = process.env.ZHIDAO_COOKIE?.trim();
|
|
30
|
+
if (cookie)
|
|
31
|
+
session.cookies = { ...session.cookies, ...parseCookieHeader(cookie) };
|
|
32
|
+
const token = process.env.ZHIDAO_TOKEN?.trim();
|
|
33
|
+
if (token)
|
|
34
|
+
session.token = token;
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 把会话写回磁盘。
|
|
39
|
+
* 不写入账号和密码。
|
|
40
|
+
*/
|
|
41
|
+
export function saveSession(session) {
|
|
42
|
+
session.updatedAt = new Date().toISOString();
|
|
43
|
+
const file = sessionPath();
|
|
44
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
45
|
+
writeFileSync(file, `${JSON.stringify(session, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 是否已经有上游 Cookie。
|
|
49
|
+
* 技能把 Cookie 当作登录秘钥,空对象表示还要走登录。
|
|
50
|
+
*/
|
|
51
|
+
export function hasCookie(session) {
|
|
52
|
+
return Object.keys(session.cookies).length > 0;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 是否可以调用业务接口。
|
|
56
|
+
* 需要 Cookie,也需要登录令牌。
|
|
57
|
+
*/
|
|
58
|
+
export function isLoggedIn(session) {
|
|
59
|
+
return hasCookie(session) && Boolean(session.token);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 换成另一个后台时清掉旧站点的登录态。
|
|
63
|
+
* 地址没变时原样返回。
|
|
64
|
+
*/
|
|
65
|
+
export function assignBase(session, value) {
|
|
66
|
+
if (!value)
|
|
67
|
+
return;
|
|
68
|
+
const next = normalizeBase(value);
|
|
69
|
+
if (next === session.baseUrl)
|
|
70
|
+
return;
|
|
71
|
+
session.baseUrl = next;
|
|
72
|
+
session.cookies = {};
|
|
73
|
+
session.token = undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 丢掉 Cookie 和令牌。
|
|
77
|
+
* `uniqueId` 保留,避免同一台机器的设备标识来回变。
|
|
78
|
+
*/
|
|
79
|
+
export function clearLogin(session) {
|
|
80
|
+
session.cookies = {};
|
|
81
|
+
session.token = undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 校正后台根地址。
|
|
85
|
+
* 只接受 http 和 https,并去掉末尾斜杠。
|
|
86
|
+
*/
|
|
87
|
+
export function normalizeBase(value) {
|
|
88
|
+
const raw = (value || DEFAULT_BASE).trim().replace(/\/+$/, "");
|
|
89
|
+
const url = new URL(raw);
|
|
90
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
91
|
+
throw new Error("服务地址需要是 http 或 https");
|
|
92
|
+
return raw;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 给技能看的令牌摘要。
|
|
96
|
+
* 只留前 8 位,避免整段令牌出现在对话里。
|
|
97
|
+
*/
|
|
98
|
+
export function tokenPreview(token) {
|
|
99
|
+
if (!token)
|
|
100
|
+
return "";
|
|
101
|
+
return `${token.slice(0, 8)}…`;
|
|
102
|
+
}
|
|
103
|
+
function readSessionFile() {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(readFileSync(sessionPath(), "utf8"));
|
|
106
|
+
return {
|
|
107
|
+
baseUrl: normalizeBase(parsed.baseUrl),
|
|
108
|
+
cookies: parsed.cookies && typeof parsed.cookies === "object" ? parsed.cookies : {},
|
|
109
|
+
token: typeof parsed.token === "string" ? parsed.token : undefined,
|
|
110
|
+
uniqueId: typeof parsed.uniqueId === "string" && parsed.uniqueId ? parsed.uniqueId : randomUUID(),
|
|
111
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : "",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return {
|
|
116
|
+
baseUrl: normalizeBase(undefined),
|
|
117
|
+
cookies: {},
|
|
118
|
+
uniqueId: randomUUID(),
|
|
119
|
+
updatedAt: "",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function parseCookieHeader(header) {
|
|
124
|
+
const cookies = {};
|
|
125
|
+
for (const part of header.split(";")) {
|
|
126
|
+
const eq = part.indexOf("=");
|
|
127
|
+
if (eq <= 0)
|
|
128
|
+
continue;
|
|
129
|
+
cookies[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
|
|
130
|
+
}
|
|
131
|
+
return cookies;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/flows.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# 请假和审批
|
|
2
|
+
|
|
3
|
+
字段以 `describe` 的结果为准。下面是办公场景里先查哪条路径。
|
|
4
|
+
|
|
5
|
+
提交前把路径和字段复述给用户,得到同意再 `call`。
|
|
6
|
+
|
|
7
|
+
## 先看有没有待办
|
|
8
|
+
|
|
9
|
+
登录后如果用户只说「有没有要我批的」:
|
|
10
|
+
|
|
11
|
+
1. `POST /desk/systemTaskList` 看办公桌系统待办。
|
|
12
|
+
2. `POST /tableRecord/taskHandleList` 看待我办理的流程。
|
|
13
|
+
3. `POST /tableRecord/taskQueryMeList` 看我发起的。常用字段:`pageNumber`、`pageSize`、`search_text`、`oaStatus`、`tableId`。
|
|
14
|
+
|
|
15
|
+
把标题、时间和状态列出来,让用户选一条,不要擅自审批。
|
|
16
|
+
|
|
17
|
+
## OA 流程单
|
|
18
|
+
|
|
19
|
+
请假单如果走的是「我的工作」流程,而不是考勤模块:
|
|
20
|
+
|
|
21
|
+
1. 用 `find 流程` 或用户给出的表单名定位 `process_id`。没有 id 时先查列表,让用户选定。
|
|
22
|
+
2. 需要请假天数时 `POST /tableRecord/calcLeaveDays`。字段:`startTime`、`endTime`、`includeWeekend`。
|
|
23
|
+
3. 草稿是 `POST /tableRecord/saveTaskNewAsDraft`。字段:`process_id`、`fieldDescs`。
|
|
24
|
+
4. 正式提交是 `POST /tableRecord/submitTaskNew`。字段:`process_id`、`content`、`processes`、`copyPerson`、`fieldDescs`、`isSms`、`autoIndexUpdate`、`autoRelationJson`。
|
|
25
|
+
5. 办理已有单是 `POST /tableRecord/submitTaskHandle`。字段:`id`、`content`、`processes`、`copyPerson`、`isSms`、`autoIndexUpdate`。
|
|
26
|
+
|
|
27
|
+
`content`、`processes`、`fieldDescs` 通常是页面拼出来的 JSON 字符串。缺结构时先 `describe` 相邻的 `editTaskNew`、`editTaskHandle`,用返回值里的字段问用户,不要手写一套流程 JSON。
|
|
28
|
+
|
|
29
|
+
## 教师考勤请假
|
|
30
|
+
|
|
31
|
+
路径在 `/teacherAttendance/selfRecord/`:
|
|
32
|
+
|
|
33
|
+
- `findTeacherLeaveRecord` 查请假记录
|
|
34
|
+
- `findAttendanceApply`、`editAttendanceApply` 看已有申请
|
|
35
|
+
- `saveAttendanceApply` 新建
|
|
36
|
+
- `updateAttendanceApply` 修改
|
|
37
|
+
- `deleteAttendanceApply` 删除
|
|
38
|
+
|
|
39
|
+
`saveAttendanceApply` 的请求体在扫描结果里没有固定字段。先拉编辑接口看现有单,再只提交用户确认过的键。
|
|
40
|
+
|
|
41
|
+
## 学生请假和临时请假
|
|
42
|
+
|
|
43
|
+
- 学生请假申请:`POST /stuAttendance/stuLeaveApplication`
|
|
44
|
+
- 临时请假:`/leave/temporaryLeave` 下的 `list`、`edit`、`save`、`update`
|
|
45
|
+
- 离校通知:`/leave/leaveSchoolNotice` 下的 `list`、`edit`、`save`、`update`
|
|
46
|
+
|
|
47
|
+
先 `list` 或 `edit`,把可选项告诉用户,再提交。
|
|
48
|
+
|
|
49
|
+
## 调用失败
|
|
50
|
+
|
|
51
|
+
`code` 为 `-1` 时会话失效,清掉后重新走验证码登录。`code` 为 `1` 时看 `errors`,把字段提示问回用户,改完再提交。
|