dsh-plugin-lookatstudy 0.11.0 → 0.12.0
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/README.md +28 -7
- package/lib/client.js +1460 -109
- package/lib/client.js.map +1 -1
- package/lib/docx-parser-BhyqPImb.mjs +55 -0
- package/lib/epub-parser-oH96guBW.mjs +306 -0
- package/lib/html-article-Da8ksU0i.mjs +460 -0
- package/lib/index.d.mts +5 -4
- package/lib/index.mjs +3098 -1287
- package/lib/inflate-DIKUrTRi.mjs +311 -0
- package/lib/pptx-parser-B9IDkiGM.mjs +96 -0
- package/lib/zip-reader-KnRrq0av.mjs +67 -0
- package/package.json +1 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
//#region src/vendor/inflate.ts
|
|
2
|
+
/** Growable output buffer; back-references read bytes this pass already wrote. */
|
|
3
|
+
var Out = class {
|
|
4
|
+
buf = /* @__PURE__ */ new Uint8Array(1024);
|
|
5
|
+
len = 0;
|
|
6
|
+
pushByte(b) {
|
|
7
|
+
if (this.len === this.buf.length) {
|
|
8
|
+
const next = new Uint8Array(this.buf.length * 2);
|
|
9
|
+
next.set(this.buf);
|
|
10
|
+
this.buf = next;
|
|
11
|
+
}
|
|
12
|
+
this.buf[this.len++] = b;
|
|
13
|
+
}
|
|
14
|
+
pushBytes(src) {
|
|
15
|
+
for (const b of src) this.pushByte(b);
|
|
16
|
+
}
|
|
17
|
+
result() {
|
|
18
|
+
return this.buf.subarray(0, this.len);
|
|
19
|
+
}
|
|
20
|
+
peek(idx) {
|
|
21
|
+
if (idx < 0 || idx >= this.len) throw new Error("inflate: 回引越界");
|
|
22
|
+
return this.buf[idx];
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/** LSB-first bit reader over a byte source. */
|
|
26
|
+
var BitReader = class {
|
|
27
|
+
src;
|
|
28
|
+
pos = 0;
|
|
29
|
+
bit = 0;
|
|
30
|
+
constructor(src) {
|
|
31
|
+
this.src = src;
|
|
32
|
+
}
|
|
33
|
+
readBit() {
|
|
34
|
+
if (this.pos >= this.src.length) throw new Error("inflate: 意外的输入结束");
|
|
35
|
+
const b = this.src[this.pos] >> this.bit & 1;
|
|
36
|
+
if (++this.bit === 8) {
|
|
37
|
+
this.bit = 0;
|
|
38
|
+
this.pos++;
|
|
39
|
+
}
|
|
40
|
+
return b;
|
|
41
|
+
}
|
|
42
|
+
readBits(n) {
|
|
43
|
+
let v = 0;
|
|
44
|
+
for (let i = 0; i < n; i++) v |= this.readBit() << i;
|
|
45
|
+
return v;
|
|
46
|
+
}
|
|
47
|
+
readBytes(n) {
|
|
48
|
+
if (this.bit !== 0) {
|
|
49
|
+
this.bit = 0;
|
|
50
|
+
this.pos++;
|
|
51
|
+
}
|
|
52
|
+
if (this.pos + n > this.src.length) throw new Error("inflate: 意外的输入结束");
|
|
53
|
+
const out = this.src.subarray(this.pos, this.pos + n);
|
|
54
|
+
this.pos += n;
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/** Canonical Huffman decoder built from code lengths (per RFC 1951 §3.2.2). */
|
|
59
|
+
var Huffman = class {
|
|
60
|
+
count = new Array(16).fill(0);
|
|
61
|
+
symbols = [];
|
|
62
|
+
constructor(lengths) {
|
|
63
|
+
for (const l of lengths) this.count[l]++;
|
|
64
|
+
this.count[0] = 0;
|
|
65
|
+
const offs = new Array(16).fill(0);
|
|
66
|
+
for (let len = 1; len < 16; len++) offs[len] = offs[len - 1] + this.count[len - 1];
|
|
67
|
+
for (let sym = 0; sym < lengths.length; sym++) {
|
|
68
|
+
const l = lengths[sym];
|
|
69
|
+
if (l !== 0) this.symbols[offs[l]++] = sym;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
decode(br) {
|
|
73
|
+
let code = 0, first = 0, index = 0;
|
|
74
|
+
for (let len = 1; len < 16; len++) {
|
|
75
|
+
code |= br.readBit();
|
|
76
|
+
const cnt = this.count[len];
|
|
77
|
+
if (code - first < cnt) return this.symbols[index + code - first];
|
|
78
|
+
index += cnt;
|
|
79
|
+
first = first + cnt << 1;
|
|
80
|
+
code <<= 1;
|
|
81
|
+
}
|
|
82
|
+
throw new Error("inflate: 无效的 Huffman 编码");
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const FIXED_LIT = /* @__PURE__ */ new Uint8Array(288);
|
|
86
|
+
for (let i = 0; i < 144; i++) FIXED_LIT[i] = 8;
|
|
87
|
+
for (let i = 144; i < 256; i++) FIXED_LIT[i] = 9;
|
|
88
|
+
for (let i = 256; i < 280; i++) FIXED_LIT[i] = 7;
|
|
89
|
+
for (let i = 280; i < 288; i++) FIXED_LIT[i] = 8;
|
|
90
|
+
const FIXED_DIST = (/* @__PURE__ */ new Uint8Array(30)).fill(5);
|
|
91
|
+
const LEN_BASE = [
|
|
92
|
+
3,
|
|
93
|
+
4,
|
|
94
|
+
5,
|
|
95
|
+
6,
|
|
96
|
+
7,
|
|
97
|
+
8,
|
|
98
|
+
9,
|
|
99
|
+
10,
|
|
100
|
+
11,
|
|
101
|
+
13,
|
|
102
|
+
15,
|
|
103
|
+
17,
|
|
104
|
+
19,
|
|
105
|
+
23,
|
|
106
|
+
27,
|
|
107
|
+
31,
|
|
108
|
+
35,
|
|
109
|
+
43,
|
|
110
|
+
51,
|
|
111
|
+
59,
|
|
112
|
+
67,
|
|
113
|
+
83,
|
|
114
|
+
99,
|
|
115
|
+
115,
|
|
116
|
+
131,
|
|
117
|
+
163,
|
|
118
|
+
195,
|
|
119
|
+
227,
|
|
120
|
+
258
|
|
121
|
+
];
|
|
122
|
+
const LEN_EXTRA = [
|
|
123
|
+
0,
|
|
124
|
+
0,
|
|
125
|
+
0,
|
|
126
|
+
0,
|
|
127
|
+
0,
|
|
128
|
+
0,
|
|
129
|
+
0,
|
|
130
|
+
0,
|
|
131
|
+
1,
|
|
132
|
+
1,
|
|
133
|
+
1,
|
|
134
|
+
1,
|
|
135
|
+
2,
|
|
136
|
+
2,
|
|
137
|
+
2,
|
|
138
|
+
2,
|
|
139
|
+
3,
|
|
140
|
+
3,
|
|
141
|
+
3,
|
|
142
|
+
3,
|
|
143
|
+
4,
|
|
144
|
+
4,
|
|
145
|
+
4,
|
|
146
|
+
4,
|
|
147
|
+
5,
|
|
148
|
+
5,
|
|
149
|
+
5,
|
|
150
|
+
5,
|
|
151
|
+
0
|
|
152
|
+
];
|
|
153
|
+
const DIST_BASE = [
|
|
154
|
+
1,
|
|
155
|
+
2,
|
|
156
|
+
3,
|
|
157
|
+
4,
|
|
158
|
+
5,
|
|
159
|
+
7,
|
|
160
|
+
9,
|
|
161
|
+
13,
|
|
162
|
+
17,
|
|
163
|
+
25,
|
|
164
|
+
33,
|
|
165
|
+
49,
|
|
166
|
+
65,
|
|
167
|
+
97,
|
|
168
|
+
129,
|
|
169
|
+
193,
|
|
170
|
+
257,
|
|
171
|
+
385,
|
|
172
|
+
513,
|
|
173
|
+
769,
|
|
174
|
+
1025,
|
|
175
|
+
1537,
|
|
176
|
+
2049,
|
|
177
|
+
3073,
|
|
178
|
+
4097,
|
|
179
|
+
6145,
|
|
180
|
+
8193,
|
|
181
|
+
12289,
|
|
182
|
+
16385,
|
|
183
|
+
24577
|
|
184
|
+
];
|
|
185
|
+
const DIST_EXTRA = [
|
|
186
|
+
0,
|
|
187
|
+
0,
|
|
188
|
+
0,
|
|
189
|
+
0,
|
|
190
|
+
1,
|
|
191
|
+
1,
|
|
192
|
+
2,
|
|
193
|
+
2,
|
|
194
|
+
3,
|
|
195
|
+
3,
|
|
196
|
+
4,
|
|
197
|
+
4,
|
|
198
|
+
5,
|
|
199
|
+
5,
|
|
200
|
+
6,
|
|
201
|
+
6,
|
|
202
|
+
7,
|
|
203
|
+
7,
|
|
204
|
+
8,
|
|
205
|
+
8,
|
|
206
|
+
9,
|
|
207
|
+
9,
|
|
208
|
+
10,
|
|
209
|
+
10,
|
|
210
|
+
11,
|
|
211
|
+
11,
|
|
212
|
+
12,
|
|
213
|
+
12,
|
|
214
|
+
13,
|
|
215
|
+
13
|
|
216
|
+
];
|
|
217
|
+
const CLEN_ORDER = [
|
|
218
|
+
16,
|
|
219
|
+
17,
|
|
220
|
+
18,
|
|
221
|
+
0,
|
|
222
|
+
8,
|
|
223
|
+
7,
|
|
224
|
+
9,
|
|
225
|
+
6,
|
|
226
|
+
10,
|
|
227
|
+
5,
|
|
228
|
+
11,
|
|
229
|
+
4,
|
|
230
|
+
12,
|
|
231
|
+
3,
|
|
232
|
+
13,
|
|
233
|
+
2,
|
|
234
|
+
14,
|
|
235
|
+
1,
|
|
236
|
+
15
|
|
237
|
+
];
|
|
238
|
+
/** Inflate one compressed block body (fixed or dynamic tables already built). */
|
|
239
|
+
function inflateBlock(br, out, lit, dist) {
|
|
240
|
+
for (;;) {
|
|
241
|
+
const sym = lit.decode(br);
|
|
242
|
+
if (sym < 256) out.pushByte(sym);
|
|
243
|
+
else if (sym === 256) return;
|
|
244
|
+
else {
|
|
245
|
+
const li = sym - 257;
|
|
246
|
+
if (li >= LEN_BASE.length) throw new Error("inflate: 无效长度码");
|
|
247
|
+
const length = LEN_BASE[li] + (LEN_EXTRA[li] ? br.readBits(LEN_EXTRA[li]) : 0);
|
|
248
|
+
const dsym = dist.decode(br);
|
|
249
|
+
if (dsym >= DIST_BASE.length) throw new Error("inflate: 无效距离码");
|
|
250
|
+
const distance = DIST_BASE[dsym] + (DIST_EXTRA[dsym] ? br.readBits(DIST_EXTRA[dsym]) : 0);
|
|
251
|
+
if (distance > out.len) throw new Error("inflate: 距离越过已输出起点");
|
|
252
|
+
const from = out.len - distance;
|
|
253
|
+
for (let i = 0; i < length; i++) out.pushByte(out.peek(from + i));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/** Decompress a raw DEFLATE stream (zip method 8, no zlib header). Throws on truncation/corruption. */
|
|
258
|
+
function inflateRaw(src) {
|
|
259
|
+
const br = new BitReader(src);
|
|
260
|
+
const out = new Out();
|
|
261
|
+
for (;;) {
|
|
262
|
+
const bfinal = br.readBit();
|
|
263
|
+
const btype = br.readBits(2);
|
|
264
|
+
if (btype === 0) {
|
|
265
|
+
const head = br.readBytes(4);
|
|
266
|
+
const len = head[0] | head[1] << 8;
|
|
267
|
+
const nlen = head[2] | head[3] << 8;
|
|
268
|
+
if ((len ^ 65535) !== nlen) throw new Error("inflate: stored 块 LEN/Nlen 校验失败");
|
|
269
|
+
out.pushBytes(br.readBytes(len));
|
|
270
|
+
} else if (btype === 1) inflateBlock(br, out, new Huffman(FIXED_LIT), new Huffman(FIXED_DIST));
|
|
271
|
+
else if (btype === 2) {
|
|
272
|
+
const hlit = br.readBits(5) + 257;
|
|
273
|
+
const hdist = br.readBits(5) + 1;
|
|
274
|
+
const hclen = br.readBits(4) + 4;
|
|
275
|
+
const clen = new Array(19).fill(0);
|
|
276
|
+
for (let i = 0; i < hclen; i++) clen[CLEN_ORDER[i]] = br.readBits(3);
|
|
277
|
+
const clenHuff = new Huffman(clen);
|
|
278
|
+
const lengths = [];
|
|
279
|
+
while (lengths.length < hlit + hdist) {
|
|
280
|
+
const sym = clenHuff.decode(br);
|
|
281
|
+
if (sym < 16) lengths.push(sym);
|
|
282
|
+
else if (sym === 16) {
|
|
283
|
+
const prev = lengths[lengths.length - 1];
|
|
284
|
+
if (prev === void 0) throw new Error("inflate: 重复码出现在开头");
|
|
285
|
+
const n = 3 + br.readBits(2);
|
|
286
|
+
for (let i = 0; i < n; i++) lengths.push(prev);
|
|
287
|
+
} else if (sym === 17) {
|
|
288
|
+
const n = 3 + br.readBits(3);
|
|
289
|
+
for (let i = 0; i < n; i++) lengths.push(0);
|
|
290
|
+
} else {
|
|
291
|
+
const n = 11 + br.readBits(7);
|
|
292
|
+
for (let i = 0; i < n; i++) lengths.push(0);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (lengths.length > hlit + hdist) throw new Error("inflate: 码长超表");
|
|
296
|
+
inflateBlock(br, out, new Huffman(lengths.slice(0, hlit)), new Huffman(lengths.slice(hlit, hlit + hdist)));
|
|
297
|
+
} else throw new Error("inflate: 保留块类型 11");
|
|
298
|
+
if (bfinal) return out.result();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/** Decompress a zlib (RFC 1950) stream: 2-byte header + raw deflate (+ ignored adler32). */
|
|
302
|
+
function inflateZlib(src) {
|
|
303
|
+
if (src.length < 6) throw new Error("inflate: zlib 流过短");
|
|
304
|
+
const cmf = src[0], flg = src[1];
|
|
305
|
+
if ((cmf & 15) !== 8) throw new Error("inflate: 非 deflate 的 zlib CM");
|
|
306
|
+
if ((cmf << 8 | flg) % 31 !== 0) throw new Error("inflate: zlib 头校验失败");
|
|
307
|
+
if (flg & 32) throw new Error("inflate: 预置字典不支持");
|
|
308
|
+
return inflateRaw(src.subarray(2));
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
export { inflateZlib as n, inflateRaw as t };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { n as readZipText, t as readZip } from "./zip-reader-KnRrq0av.mjs";
|
|
2
|
+
//#region src/vendor/pptx-parser.ts
|
|
3
|
+
function decodeEntities(s) {
|
|
4
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/&/g, "&");
|
|
5
|
+
}
|
|
6
|
+
/** Extract this shape tree's text paragraphs (a:t runs joined per a:p). */
|
|
7
|
+
function shapeTexts(xml) {
|
|
8
|
+
const out = [];
|
|
9
|
+
const pRe = /<a:p\b[^>]*>[\s\S]*?<\/a:p>/g;
|
|
10
|
+
let pm;
|
|
11
|
+
while ((pm = pRe.exec(xml)) !== null) {
|
|
12
|
+
let text = "";
|
|
13
|
+
const tRe = /<a:t(?:\s[^>]*)?>([\s\S]*?)<\/a:t>/g;
|
|
14
|
+
let tm;
|
|
15
|
+
while ((tm = tRe.exec(pm[0])) !== null) text += decodeEntities(tm[1] ?? "");
|
|
16
|
+
const t = text.trim();
|
|
17
|
+
if (t) out.push(t);
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function slideNumber(name) {
|
|
22
|
+
return Number(name.match(/slide(\d+)\.xml$/i)?.[1] ?? 0);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One `a:tbl` block → GFM markdown table, or "" when the table is an
|
|
26
|
+
* all-empty placeholder. Cells: text via a:p runs; gridSpan advances the
|
|
27
|
+
* column index (spanned columns padded); vMerge continuation cells carry no
|
|
28
|
+
* text of their own. Pipes escaped, inner whitespace collapsed.
|
|
29
|
+
*/
|
|
30
|
+
function tableToMarkdown(tblXml) {
|
|
31
|
+
const rows = [];
|
|
32
|
+
const trRe = /<a:tr\b[^>]*>([\s\S]*?)<\/a:tr>/g;
|
|
33
|
+
let trm;
|
|
34
|
+
while ((trm = trRe.exec(tblXml)) !== null) {
|
|
35
|
+
const cells = [];
|
|
36
|
+
const tcRe = /<a:tc\b([^>]*)>([\s\S]*?)<\/a:tc>/g;
|
|
37
|
+
let col = 0;
|
|
38
|
+
let tcm;
|
|
39
|
+
while ((tcm = tcRe.exec(trm[1])) !== null) {
|
|
40
|
+
const attrs = tcm[1] ?? "";
|
|
41
|
+
const span = Math.max(1, Number(attrs.match(/gridSpan="(\d+)"/)?.[1] ?? 1));
|
|
42
|
+
const vMerge = /vMerge="1"/.test(attrs);
|
|
43
|
+
const raw = shapeTexts(tcm[2] ?? "").join(" ").trim().replace(/\|/g, "\\|").replace(/\s+/g, " ");
|
|
44
|
+
if (!vMerge) cells.push({
|
|
45
|
+
col,
|
|
46
|
+
text: raw
|
|
47
|
+
});
|
|
48
|
+
col += span;
|
|
49
|
+
}
|
|
50
|
+
rows.push(cells);
|
|
51
|
+
}
|
|
52
|
+
if (rows.filter((cs) => cs.length > 0).length === 0) return "";
|
|
53
|
+
if (rows.every((cs) => cs.every((c) => !c.text))) return "";
|
|
54
|
+
const sorted = rows.filter((cs) => cs.length > 0).map((cs) => cs.sort((a, b) => a.col - b.col));
|
|
55
|
+
const width = Math.max(...sorted.map((cs) => cs[cs.length - 1].col + 1));
|
|
56
|
+
const line = (cs) => {
|
|
57
|
+
const texts = Array.from({ length: width }, () => "");
|
|
58
|
+
for (const c of cs) texts[c.col] = c.text;
|
|
59
|
+
return `| ${texts.join(" | ")} |`;
|
|
60
|
+
};
|
|
61
|
+
return [
|
|
62
|
+
line(sorted[0]),
|
|
63
|
+
`| ${Array.from({ length: width }, () => "---").join(" | ")} |`,
|
|
64
|
+
...sorted.slice(1).map(line)
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
67
|
+
/** .pptx → markdown(每张 slide 一个 ##)。失败抛错,调用方按"无内容"兜底。 */
|
|
68
|
+
function parsePptx(buf) {
|
|
69
|
+
const entries = readZip(buf);
|
|
70
|
+
const slideNames = [...entries.keys()].filter((n) => /^ppt\/slides\/slide\d+\.xml$/i.test(n)).sort((a, b) => slideNumber(a) - slideNumber(b));
|
|
71
|
+
if (slideNames.length === 0) throw new Error("pptx 结构异常:没有幻灯片");
|
|
72
|
+
const lines = [`# ${decodeEntities(readZipText(entries, "docProps/app.xml").match(/<TitlesOfParts>[\s\S]*?<vt:lpstr>([^<]+)<\/vt:lpstr>/i)?.[1] ?? "").trim() || "PPTX"}`];
|
|
73
|
+
for (const name of slideNames) {
|
|
74
|
+
const xml = readZipText(entries, name);
|
|
75
|
+
const no = slideNumber(name);
|
|
76
|
+
const tables = [];
|
|
77
|
+
const texts = shapeTexts(xml.replace(/<a:tbl\b[\s\S]*?<\/a:tbl>/g, (tbl) => {
|
|
78
|
+
const md = tableToMarkdown(tbl);
|
|
79
|
+
if (md) tables.push(md);
|
|
80
|
+
return "";
|
|
81
|
+
}));
|
|
82
|
+
const title = texts[0] ?? "";
|
|
83
|
+
const body = [...texts.slice(1), ...tables];
|
|
84
|
+
lines.push(`\n## Slide ${no}: ${title || "(无标题)"}\n`);
|
|
85
|
+
if (body.length) lines.push(body.join("\n\n"));
|
|
86
|
+
const notes = shapeTexts(readZipText(entries, `ppt/notesSlides/notesSlide${no}.xml`));
|
|
87
|
+
if (notes.length) lines.push(`\n**讲者备注:** ${notes.join(" ")}`);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
markdown: lines.join("\n"),
|
|
91
|
+
slideCount: slideNames.length,
|
|
92
|
+
images: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { parsePptx };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { t as inflateRaw } from "./inflate-DIKUrTRi.mjs";
|
|
2
|
+
//#region src/vendor/zip-reader.ts
|
|
3
|
+
const EOCD_SIG = 101010256;
|
|
4
|
+
const CEN_SIG = 33639248;
|
|
5
|
+
const LOC_SIG = 67324752;
|
|
6
|
+
function u16(b, off) {
|
|
7
|
+
return b[off] | b[off + 1] << 8;
|
|
8
|
+
}
|
|
9
|
+
function u32(b, off) {
|
|
10
|
+
return (b[off] | b[off + 1] << 8 | b[off + 2] << 16 | b[off + 3] << 24) >>> 0;
|
|
11
|
+
}
|
|
12
|
+
/** Locate the End-of-Central-Directory record, scanning back over an optional comment. */
|
|
13
|
+
function findEocd(buf) {
|
|
14
|
+
const minOff = Math.max(0, buf.length - 22 - 65535);
|
|
15
|
+
for (let i = buf.length - 22; i >= minOff; i--) if (u32(buf, i) === EOCD_SIG) return i;
|
|
16
|
+
throw new Error("zip: 找不到 EOCD(不是 zip 文件?)");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read every regular file entry in a zip archive into a name→bytes map.
|
|
20
|
+
* Directory entries (trailing "/") are skipped. Data descriptors (bit 3 of
|
|
21
|
+
* general purpose flag) are fine — sizes come from the central directory.
|
|
22
|
+
*/
|
|
23
|
+
function readZip(buf) {
|
|
24
|
+
const eocd = findEocd(buf);
|
|
25
|
+
const entryCount = u16(buf, eocd + 10);
|
|
26
|
+
let cenOff = u32(buf, eocd + 16);
|
|
27
|
+
const out = /* @__PURE__ */ new Map();
|
|
28
|
+
const decoder = new TextDecoder("utf-8");
|
|
29
|
+
for (let i = 0; i < entryCount; i++) {
|
|
30
|
+
if (u32(buf, cenOff) !== CEN_SIG) throw new Error(`zip: 中央目录第 ${i} 项签名错误`);
|
|
31
|
+
const method = u16(buf, cenOff + 10);
|
|
32
|
+
const compSize = u32(buf, cenOff + 20);
|
|
33
|
+
const uncompSize = u32(buf, cenOff + 24);
|
|
34
|
+
const nameLen = u16(buf, cenOff + 28);
|
|
35
|
+
const extraLen = u16(buf, cenOff + 30);
|
|
36
|
+
const commentLen = u16(buf, cenOff + 32);
|
|
37
|
+
const localOff = u32(buf, cenOff + 42);
|
|
38
|
+
const name = decoder.decode(buf.subarray(cenOff + 46, cenOff + 46 + nameLen));
|
|
39
|
+
if (!name.endsWith("/")) {
|
|
40
|
+
if (u32(buf, localOff) !== LOC_SIG) throw new Error(`zip: ${name} 本地头签名错误`);
|
|
41
|
+
const lNameLen = u16(buf, localOff + 26);
|
|
42
|
+
const lExtraLen = u16(buf, localOff + 28);
|
|
43
|
+
const dataStart = localOff + 30 + lNameLen + lExtraLen;
|
|
44
|
+
const raw = buf.subarray(dataStart, dataStart + compSize);
|
|
45
|
+
let data;
|
|
46
|
+
if (method === 0) data = raw;
|
|
47
|
+
else if (method === 8) data = inflateRaw(raw);
|
|
48
|
+
else throw new Error(`zip: ${name} 使用不支持的压缩方法 ${method}`);
|
|
49
|
+
if (uncompSize > 0 && data.length !== uncompSize) throw new Error(`zip: ${name} 解压后大小不符(期望 ${uncompSize},实得 ${data.length})`);
|
|
50
|
+
out.set(name, {
|
|
51
|
+
name,
|
|
52
|
+
data,
|
|
53
|
+
method,
|
|
54
|
+
uncompressedSize: uncompSize
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
cenOff += 46 + nameLen + extraLen + commentLen;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** Convenience: decode one entry as UTF-8 text ("" when missing). */
|
|
62
|
+
function readZipText(entries, name) {
|
|
63
|
+
const e = entries.get(name);
|
|
64
|
+
return e ? new TextDecoder("utf-8").decode(e.data) : "";
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { readZipText as n, readZip as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-lookatstudy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"packageManager": "pnpm@11.7.0",
|
|
5
5
|
"description": "Turn any markdown, local folder, or GitHub learning repo into a guided course inside DeepSeek Harness: gated skill-tree progression, BKT mastery tracking, SM-2 spaced repetition.",
|
|
6
6
|
"type": "module",
|