@tienne/gestalt 0.101.1 → 0.103.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.ko.md +2 -1
- package/README.md +2 -1
- package/dist/package.json +1 -1
- package/dist/plugin/role-agents/video-summarizer/AGENT.md +177 -113
- package/dist/plugin/skills/_shared/agent-delegation.md +1 -1
- package/dist/plugin/skills/_shared/proactive-routing.md +3 -2
- package/dist/plugin/skills/_shared/research-humanize.md +160 -0
- package/dist/plugin/skills/research/SKILL.md +20 -2
- package/dist/plugin/skills/research/references/design.md +14 -2
- package/dist/plugin/skills/reverse-plan/SKILL.md +3 -1
- package/dist/plugin/skills/ux-research/SKILL.md +3 -1
- package/dist/plugin/skills/video-digest/SKILL.md +408 -0
- package/dist/plugin/skills/video-digest/templates/background.md +15 -0
- package/dist/plugin/skills/video-digest/templates/blog.md +16 -0
- package/dist/plugin/skills/video-digest/templates/comments.md +19 -0
- package/dist/plugin/skills/video-digest/templates/counterpoints.md +15 -0
- package/dist/plugin/skills/video-digest/templates/flashcards.md +15 -0
- package/dist/plugin/skills/video-digest/templates/interview.md +15 -0
- package/dist/plugin/skills/video-digest/templates/meeting.md +15 -0
- package/dist/plugin/skills/video-digest/templates/mindmap.md +16 -0
- package/dist/plugin/skills/video-digest/templates/quiz.md +16 -0
- package/dist/plugin/skills/video-digest/templates/quotes.md +16 -0
- package/dist/plugin/skills/video-digest/templates/resources.md +14 -0
- package/dist/plugin/skills/video-digest/templates/timestamps.md +14 -0
- package/dist/src/cli/commands/digest.d.ts +57 -0
- package/dist/src/cli/commands/digest.d.ts.map +1 -0
- package/dist/src/cli/commands/digest.js +368 -0
- package/dist/src/cli/commands/digest.js.map +1 -0
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +77 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/digest/blocks.d.ts +1853 -0
- package/dist/src/digest/blocks.d.ts.map +1 -0
- package/dist/src/digest/blocks.js +148 -0
- package/dist/src/digest/blocks.js.map +1 -0
- package/dist/src/digest/captions.d.ts +37 -0
- package/dist/src/digest/captions.d.ts.map +1 -0
- package/dist/src/digest/captions.js +138 -0
- package/dist/src/digest/captions.js.map +1 -0
- package/dist/src/digest/comments.d.ts +113 -0
- package/dist/src/digest/comments.d.ts.map +1 -0
- package/dist/src/digest/comments.js +153 -0
- package/dist/src/digest/comments.js.map +1 -0
- package/dist/src/digest/format.d.ts +9 -0
- package/dist/src/digest/format.d.ts.map +1 -0
- package/dist/src/digest/format.js +26 -0
- package/dist/src/digest/format.js.map +1 -0
- package/dist/src/digest/frames.d.ts +26 -0
- package/dist/src/digest/frames.d.ts.map +1 -0
- package/dist/src/digest/frames.js +109 -0
- package/dist/src/digest/frames.js.map +1 -0
- package/dist/src/digest/index.d.ts +25 -0
- package/dist/src/digest/index.d.ts.map +1 -0
- package/dist/src/digest/index.js +25 -0
- package/dist/src/digest/index.js.map +1 -0
- package/dist/src/digest/ingest.d.ts +65 -0
- package/dist/src/digest/ingest.d.ts.map +1 -0
- package/dist/src/digest/ingest.js +303 -0
- package/dist/src/digest/ingest.js.map +1 -0
- package/dist/src/digest/lint-extension.d.ts +57 -0
- package/dist/src/digest/lint-extension.d.ts.map +1 -0
- package/dist/src/digest/lint-extension.js +395 -0
- package/dist/src/digest/lint-extension.js.map +1 -0
- package/dist/src/digest/lint.d.ts +40 -0
- package/dist/src/digest/lint.d.ts.map +1 -0
- package/dist/src/digest/lint.js +517 -0
- package/dist/src/digest/lint.js.map +1 -0
- package/dist/src/digest/merge.d.ts +40 -0
- package/dist/src/digest/merge.d.ts.map +1 -0
- package/dist/src/digest/merge.js +104 -0
- package/dist/src/digest/merge.js.map +1 -0
- package/dist/src/digest/ocr.d.ts +96 -0
- package/dist/src/digest/ocr.d.ts.map +1 -0
- package/dist/src/digest/ocr.js +168 -0
- package/dist/src/digest/ocr.js.map +1 -0
- package/dist/src/digest/prose.d.ts +43 -0
- package/dist/src/digest/prose.d.ts.map +1 -0
- package/dist/src/digest/prose.js +112 -0
- package/dist/src/digest/prose.js.map +1 -0
- package/dist/src/digest/render-blocks.d.ts +32 -0
- package/dist/src/digest/render-blocks.d.ts.map +1 -0
- package/dist/src/digest/render-blocks.js +292 -0
- package/dist/src/digest/render-blocks.js.map +1 -0
- package/dist/src/digest/render-html.d.ts +22 -0
- package/dist/src/digest/render-html.d.ts.map +1 -0
- package/dist/src/digest/render-html.js +466 -0
- package/dist/src/digest/render-html.js.map +1 -0
- package/dist/src/digest/render-md.d.ts +18 -0
- package/dist/src/digest/render-md.d.ts.map +1 -0
- package/dist/src/digest/render-md.js +113 -0
- package/dist/src/digest/render-md.js.map +1 -0
- package/dist/src/digest/render.d.ts +47 -0
- package/dist/src/digest/render.d.ts.map +1 -0
- package/dist/src/digest/render.js +123 -0
- package/dist/src/digest/render.js.map +1 -0
- package/dist/src/digest/reports.d.ts +26 -0
- package/dist/src/digest/reports.d.ts.map +1 -0
- package/dist/src/digest/reports.js +70 -0
- package/dist/src/digest/reports.js.map +1 -0
- package/dist/src/digest/schema-base.d.ts +31 -0
- package/dist/src/digest/schema-base.d.ts.map +1 -0
- package/dist/src/digest/schema-base.js +19 -0
- package/dist/src/digest/schema-base.js.map +1 -0
- package/dist/src/digest/schema.d.ts +5713 -0
- package/dist/src/digest/schema.d.ts.map +1 -0
- package/dist/src/digest/schema.js +229 -0
- package/dist/src/digest/schema.js.map +1 -0
- package/dist/src/digest/server.d.ts +36 -0
- package/dist/src/digest/server.d.ts.map +1 -0
- package/dist/src/digest/server.js +278 -0
- package/dist/src/digest/server.js.map +1 -0
- package/dist/src/digest/slice.d.ts +20 -0
- package/dist/src/digest/slice.d.ts.map +1 -0
- package/dist/src/digest/slice.js +48 -0
- package/dist/src/digest/slice.js.map +1 -0
- package/dist/src/digest/store.d.ts +67 -0
- package/dist/src/digest/store.d.ts.map +1 -0
- package/dist/src/digest/store.js +152 -0
- package/dist/src/digest/store.js.map +1 -0
- package/dist/src/digest/svg.d.ts +44 -0
- package/dist/src/digest/svg.d.ts.map +1 -0
- package/dist/src/digest/svg.js +264 -0
- package/dist/src/digest/svg.js.map +1 -0
- package/dist/src/digest/templates.d.ts +38 -0
- package/dist/src/digest/templates.d.ts.map +1 -0
- package/dist/src/digest/templates.js +125 -0
- package/dist/src/digest/templates.js.map +1 -0
- package/dist/src/digest/translate.d.ts +46 -0
- package/dist/src/digest/translate.d.ts.map +1 -0
- package/dist/src/digest/translate.js +50 -0
- package/dist/src/digest/translate.js.map +1 -0
- package/dist/src/digest/viewer-js.d.ts +4 -0
- package/dist/src/digest/viewer-js.d.ts.map +1 -0
- package/dist/src/digest/viewer-js.js +710 -0
- package/dist/src/digest/viewer-js.js.map +1 -0
- package/dist/src/digest/viewer-logic.d.ts +46 -0
- package/dist/src/digest/viewer-logic.d.ts.map +1 -0
- package/dist/src/digest/viewer-logic.js +89 -0
- package/dist/src/digest/viewer-logic.js.map +1 -0
- package/dist/src/digest/ytdlp.d.ts +34 -0
- package/dist/src/digest/ytdlp.d.ts.map +1 -0
- package/dist/src/digest/ytdlp.js +64 -0
- package/dist/src/digest/ytdlp.js.map +1 -0
- package/dist/src/humanize/detectors.js +2 -2
- package/dist/src/humanize/detectors.js.map +1 -1
- package/dist/src/mcp/schemas.d.ts +78 -4
- package/dist/src/mcp/schemas.d.ts.map +1 -1
- package/dist/src/mcp/schemas.js +40 -2
- package/dist/src/mcp/schemas.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +8 -2
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/digest-passthrough.d.ts +3 -0
- package/dist/src/mcp/tools/digest-passthrough.d.ts.map +1 -0
- package/dist/src/mcp/tools/digest-passthrough.js +148 -0
- package/dist/src/mcp/tools/digest-passthrough.js.map +1 -0
- package/dist/src/mcp/tools/research-passthrough.d.ts.map +1 -1
- package/dist/src/mcp/tools/research-passthrough.js +56 -0
- package/dist/src/mcp/tools/research-passthrough.js.map +1 -1
- package/dist/src/research/index.d.ts +1 -0
- package/dist/src/research/index.d.ts.map +1 -1
- package/dist/src/research/index.js +1 -0
- package/dist/src/research/index.js.map +1 -1
- package/dist/src/research/prose.d.ts +60 -0
- package/dist/src/research/prose.d.ts.map +1 -0
- package/dist/src/research/prose.js +200 -0
- package/dist/src/research/prose.js.map +1 -0
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/role-agents/video-summarizer/AGENT.md +177 -113
- package/plugin/skills/_shared/agent-delegation.md +1 -1
- package/plugin/skills/_shared/proactive-routing.md +3 -2
- package/plugin/skills/_shared/research-humanize.md +160 -0
- package/plugin/skills/research/SKILL.md +20 -2
- package/plugin/skills/research/references/design.md +14 -2
- package/plugin/skills/reverse-plan/SKILL.md +3 -1
- package/plugin/skills/ux-research/SKILL.md +3 -1
- package/plugin/skills/video-digest/SKILL.md +408 -0
- package/plugin/skills/video-digest/templates/background.md +15 -0
- package/plugin/skills/video-digest/templates/blog.md +16 -0
- package/plugin/skills/video-digest/templates/comments.md +19 -0
- package/plugin/skills/video-digest/templates/counterpoints.md +15 -0
- package/plugin/skills/video-digest/templates/flashcards.md +15 -0
- package/plugin/skills/video-digest/templates/interview.md +15 -0
- package/plugin/skills/video-digest/templates/meeting.md +15 -0
- package/plugin/skills/video-digest/templates/mindmap.md +16 -0
- package/plugin/skills/video-digest/templates/quiz.md +16 -0
- package/plugin/skills/video-digest/templates/quotes.md +16 -0
- package/plugin/skills/video-digest/templates/resources.md +14 -0
- package/plugin/skills/video-digest/templates/timestamps.md +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../src/digest/blocks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { citeSchema, LANG_TAG_PATTERN, textSchema } from './schema-base.js';
|
|
3
|
+
/**
|
|
4
|
+
* 확장 리포트(회의록, 퀴즈 같은 템플릿 결과)를 이루는 블록.
|
|
5
|
+
* 템플릿은 이 중에서 쓸 블록을 고르고 렌더러가 타입마다 정해진 꼴로 그린다.
|
|
6
|
+
* 요약 에이전트는 HTML이나 SVG를 쓰지 않고 이 구조만 채운다
|
|
7
|
+
*/
|
|
8
|
+
export const TEMPLATE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
9
|
+
export const templateIdSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.regex(TEMPLATE_ID_PATTERN, '템플릿 id는 영문 소문자, 숫자, -만 쓴다');
|
|
12
|
+
// 3단 이상은 lint가 경로와 함께 잡는다. Bullet과 같은 이유다
|
|
13
|
+
export const listItemSchema = z.lazy(() => z.object({
|
|
14
|
+
text: textSchema,
|
|
15
|
+
cite: citeSchema.optional(),
|
|
16
|
+
children: z.array(listItemSchema).optional(),
|
|
17
|
+
}));
|
|
18
|
+
export const headingBlockSchema = z.object({
|
|
19
|
+
type: z.literal('heading'),
|
|
20
|
+
text: textSchema,
|
|
21
|
+
});
|
|
22
|
+
export const textBlockSchema = z.object({
|
|
23
|
+
type: z.literal('text'),
|
|
24
|
+
text: textSchema,
|
|
25
|
+
cite: citeSchema.optional(),
|
|
26
|
+
});
|
|
27
|
+
export const listBlockSchema = z.object({
|
|
28
|
+
type: z.literal('list'),
|
|
29
|
+
title: textSchema.optional(),
|
|
30
|
+
items: z.array(listItemSchema).min(1),
|
|
31
|
+
});
|
|
32
|
+
/** 대사를 그대로 옮긴 인용. 영상 언어와 리포트 언어가 다르면 번역은 translation에 따로 둔다 */
|
|
33
|
+
export const quoteBlockSchema = z.object({
|
|
34
|
+
type: z.literal('quote'),
|
|
35
|
+
text: textSchema,
|
|
36
|
+
translation: textSchema.optional(),
|
|
37
|
+
speaker: textSchema.optional(),
|
|
38
|
+
note: textSchema.optional(),
|
|
39
|
+
cite: citeSchema,
|
|
40
|
+
});
|
|
41
|
+
/** 영상 목차. 시각은 cite.key 문장의 시작 시각을 쓴다 */
|
|
42
|
+
export const timestampsBlockSchema = z.object({
|
|
43
|
+
type: z.literal('timestamps'),
|
|
44
|
+
items: z
|
|
45
|
+
.array(z.object({ title: textSchema, summary: textSchema.optional(), cite: citeSchema }))
|
|
46
|
+
.min(1)
|
|
47
|
+
.max(60),
|
|
48
|
+
});
|
|
49
|
+
export const actionsBlockSchema = z.object({
|
|
50
|
+
type: z.literal('actions'),
|
|
51
|
+
items: z
|
|
52
|
+
.array(z.object({
|
|
53
|
+
task: textSchema,
|
|
54
|
+
owner: textSchema.optional(),
|
|
55
|
+
due: textSchema.optional(),
|
|
56
|
+
cite: citeSchema.optional(),
|
|
57
|
+
}))
|
|
58
|
+
.min(1)
|
|
59
|
+
.max(50),
|
|
60
|
+
});
|
|
61
|
+
export const qaBlockSchema = z.object({
|
|
62
|
+
type: z.literal('qa'),
|
|
63
|
+
items: z
|
|
64
|
+
.array(z.object({ question: textSchema, answer: textSchema, cite: citeSchema.optional() }))
|
|
65
|
+
.min(1)
|
|
66
|
+
.max(40),
|
|
67
|
+
});
|
|
68
|
+
/** 앞면을 누르면 뒷면이 열린다. HTML은 details라 스크립트 없이 뒤집힌다 */
|
|
69
|
+
export const flashcardsBlockSchema = z.object({
|
|
70
|
+
type: z.literal('flashcards'),
|
|
71
|
+
cards: z
|
|
72
|
+
.array(z.object({ front: textSchema, back: textSchema, cite: citeSchema.optional() }))
|
|
73
|
+
.min(1)
|
|
74
|
+
.max(60),
|
|
75
|
+
});
|
|
76
|
+
/** answer는 choices의 0부터 센 자리다. 범위 밖이면 lint가 오류로 막는다 */
|
|
77
|
+
export const quizBlockSchema = z.object({
|
|
78
|
+
type: z.literal('quiz'),
|
|
79
|
+
questions: z
|
|
80
|
+
.array(z.object({
|
|
81
|
+
question: textSchema,
|
|
82
|
+
choices: z.array(textSchema).min(2).max(6),
|
|
83
|
+
answer: z.number().int().min(0),
|
|
84
|
+
explanation: textSchema,
|
|
85
|
+
cite: citeSchema.optional(),
|
|
86
|
+
}))
|
|
87
|
+
.min(1)
|
|
88
|
+
.max(30),
|
|
89
|
+
});
|
|
90
|
+
/** 깊이와 노드 수 상한은 lint가 경로와 함께 잡는다. 그림이 화면을 넘지 않게 하려는 것이다 */
|
|
91
|
+
export const mindmapNodeSchema = z.lazy(() => z.object({
|
|
92
|
+
label: textSchema,
|
|
93
|
+
cite: citeSchema.optional(),
|
|
94
|
+
children: z.array(mindmapNodeSchema).max(12).optional(),
|
|
95
|
+
}));
|
|
96
|
+
/** SVG 원문은 받지 않는다. 트리 구조만 받고 그림은 렌더러가 그린다 */
|
|
97
|
+
export const mindmapBlockSchema = z.object({
|
|
98
|
+
type: z.literal('mindmap'),
|
|
99
|
+
root: mindmapNodeSchema,
|
|
100
|
+
});
|
|
101
|
+
export const COMMENT_TONES = ['positive', 'negative', 'neutral', 'question'];
|
|
102
|
+
/** yt-dlp가 주는 댓글 id. 영숫자와 -, _, .만 온다 */
|
|
103
|
+
export const commentIdSchema = z.string().regex(/^[A-Za-z0-9_.-]{1,120}$/);
|
|
104
|
+
/** 댓글을 성격별로 묶은 분석. examples.text는 댓글 원문이고 lint가 comments.json과 대조한다 */
|
|
105
|
+
export const commentsBlockSchema = z.object({
|
|
106
|
+
type: z.literal('comments'),
|
|
107
|
+
total: z.number().int().min(0),
|
|
108
|
+
overview: textSchema,
|
|
109
|
+
groups: z
|
|
110
|
+
.array(z.object({
|
|
111
|
+
title: textSchema,
|
|
112
|
+
tone: z.enum(COMMENT_TONES),
|
|
113
|
+
count: z.number().int().min(0),
|
|
114
|
+
summary: textSchema,
|
|
115
|
+
examples: z
|
|
116
|
+
.array(z.object({ id: commentIdSchema, text: textSchema }))
|
|
117
|
+
.max(3)
|
|
118
|
+
.default([]),
|
|
119
|
+
}))
|
|
120
|
+
.min(1)
|
|
121
|
+
.max(12),
|
|
122
|
+
});
|
|
123
|
+
export const blockSchema = z.discriminatedUnion('type', [
|
|
124
|
+
headingBlockSchema,
|
|
125
|
+
textBlockSchema,
|
|
126
|
+
listBlockSchema,
|
|
127
|
+
quoteBlockSchema,
|
|
128
|
+
timestampsBlockSchema,
|
|
129
|
+
actionsBlockSchema,
|
|
130
|
+
qaBlockSchema,
|
|
131
|
+
flashcardsBlockSchema,
|
|
132
|
+
quizBlockSchema,
|
|
133
|
+
mindmapBlockSchema,
|
|
134
|
+
commentsBlockSchema,
|
|
135
|
+
]);
|
|
136
|
+
export const BLOCK_TYPES = blockSchema.options.map((o) => o.shape.type.value);
|
|
137
|
+
export const extensionSchema = z.object({
|
|
138
|
+
template: templateIdSchema,
|
|
139
|
+
title: textSchema,
|
|
140
|
+
lang: z.string().regex(LANG_TAG_PATTERN, '언어 태그(ko, en, pt-BR) 꼴이어야 한다').default('ko'),
|
|
141
|
+
blocks: z.array(blockSchema).min(1),
|
|
142
|
+
});
|
|
143
|
+
/**
|
|
144
|
+
* 같은 템플릿이라도 언어가 다르면 다른 확장이다. 탭 id와 merge 키로 쓴다.
|
|
145
|
+
* 템플릿 id와 언어 태그 둘 다 -를 쓰므로 둘 다 안 쓰는 .으로 잇는다
|
|
146
|
+
*/
|
|
147
|
+
export const extensionKey = (ext) => `${ext.template}.${ext.lang}`;
|
|
148
|
+
//# sourceMappingURL=blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../src/digest/blocks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface Cue {
|
|
2
|
+
startSec: number;
|
|
3
|
+
text: string;
|
|
4
|
+
}
|
|
5
|
+
/** 자막에서 뽑은 단어 하나와 그 단어가 화면에 나온 시각 */
|
|
6
|
+
export interface TimedWord {
|
|
7
|
+
sec: number;
|
|
8
|
+
text: string;
|
|
9
|
+
/** 유튜브 자동 자막의 `>>`(화자 바뀜) 뒤 첫 단어. 앞 문장과 합치지 않는다 */
|
|
10
|
+
breakBefore?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface MergeOptions {
|
|
13
|
+
/** 문장 부호가 없을 때 이만큼 쉬면 끊는다 */
|
|
14
|
+
pauseSec?: number;
|
|
15
|
+
/** 쉼으로 끊을 때 최소 글자 수. 짧은 맞장구가 문장 하나가 되지 않게 */
|
|
16
|
+
minChars?: number;
|
|
17
|
+
/** 부호도 쉼도 없이 이어지면 이 길이에서 끊는다 */
|
|
18
|
+
maxChars?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function toSeconds(stamp: string): number;
|
|
21
|
+
/** VTT와 SRT를 읽어 시작 시각과 글만 남긴다 */
|
|
22
|
+
export declare function parseCues(text: string): Cue[];
|
|
23
|
+
/**
|
|
24
|
+
* 큐를 단어로 쪼갠다. 유튜브 자동 자막의 `<00:00:01.200>` 태그가 있으면 단어마다 그 시각을 쓰고
|
|
25
|
+
* 없으면 큐 시작 시각을 쓴다. 문장이 큐 중간에서 시작해도 시작 시각이 틀어지지 않게 하려는 것이다.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseTimedWords(text: string): TimedWord[];
|
|
28
|
+
/**
|
|
29
|
+
* 단어를 문장으로 합친다. 끝 부호에서 끊고 부호가 없으면 쉼 간격과 길이로 끊는다.
|
|
30
|
+
* 문장 시작 시각은 첫 단어의 시각이다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function mergeSentences(words: TimedWord[], options?: MergeOptions): Cue[];
|
|
33
|
+
/** 단어 시각이 없는 조각(whisper 구간 같은)을 조각 시작 시각의 단어로 편다 */
|
|
34
|
+
export declare function cuesToWords(cues: Cue[]): TimedWord[];
|
|
35
|
+
/** 자막 파일 하나를 문장 단위 큐로 만든다 */
|
|
36
|
+
export declare function captionsToSentences(text: string, options?: MergeOptions): Cue[];
|
|
37
|
+
//# sourceMappingURL=captions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captions.d.ts","sourceRoot":"","sources":["../../../src/digest/captions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C;AAkDD,iCAAiC;AACjC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,CAK7C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CA8BzD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,GAAG,EAAE,CA2BpF;AAED,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,EAAE,CAOpD;AAED,6BAA6B;AAC7B,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,GAAG,EAAE,CAE/E"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const DEFAULT_MERGE = { pauseSec: 1.2, minChars: 40, maxChars: 220 };
|
|
2
|
+
const SENTENCE_END = /[.?!。?!…]["'”’)\]]*$/;
|
|
3
|
+
const TIME_TAG = /<(\d{1,2}:\d{2}:\d{2}[.,]\d{1,3}|\d{1,2}:\d{2}[.,]\d{1,3})>/g;
|
|
4
|
+
export function toSeconds(stamp) {
|
|
5
|
+
const parts = stamp.replace(',', '.').split(':').map(Number);
|
|
6
|
+
while (parts.length < 3)
|
|
7
|
+
parts.unshift(0);
|
|
8
|
+
return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
9
|
+
}
|
|
10
|
+
const ENTITIES = {
|
|
11
|
+
'&': '&',
|
|
12
|
+
'<': '<',
|
|
13
|
+
'>': '>',
|
|
14
|
+
'"': '"',
|
|
15
|
+
''': "'",
|
|
16
|
+
' ': ' ',
|
|
17
|
+
};
|
|
18
|
+
const decodeEntities = (s) => s.replace(/&(?:amp|lt|gt|quot|#39|nbsp);/g, (e) => ENTITIES[e]);
|
|
19
|
+
/** `[music]`, `[Applause]` 같은 소리 표기. 문장에 섞이면 요약 근거로 쓸 수 없다 */
|
|
20
|
+
const SOUND_TAG = /^\[[^\]]*\]$/;
|
|
21
|
+
const cleanLine = (l) => l
|
|
22
|
+
.replace(/<[^>]+>/g, '')
|
|
23
|
+
.replace(/\s+/g, ' ')
|
|
24
|
+
.trim();
|
|
25
|
+
/**
|
|
26
|
+
* 유튜브 자동 자막은 앞 줄이 다음 큐에 되풀이돼서, 직전 큐에 이미 나온 줄은 뺀다.
|
|
27
|
+
* plugin/skills/research/scripts/slide-extract.mjs의 parseCues와 같은 규칙이다. 플러그인 스크립트는 src를 import하지 않아 두 벌을 두고 테스트로 결과를 맞춘다.
|
|
28
|
+
*/
|
|
29
|
+
function readBlocks(text) {
|
|
30
|
+
const out = [];
|
|
31
|
+
let prevLines = new Set();
|
|
32
|
+
for (const block of text.replace(/\r/g, '').split(/\n{2,}/)) {
|
|
33
|
+
const lines = block.split('\n');
|
|
34
|
+
const at = lines.findIndex((l) => l.includes('-->'));
|
|
35
|
+
if (at < 0)
|
|
36
|
+
continue;
|
|
37
|
+
const m = lines[at].match(/([\d:.,]+)\s*-->/);
|
|
38
|
+
if (!m)
|
|
39
|
+
continue;
|
|
40
|
+
const raw = lines.slice(at + 1).filter((l) => cleanLine(l));
|
|
41
|
+
const body = raw.map(cleanLine);
|
|
42
|
+
const freshRaw = raw.filter((_, k) => !prevLines.has(body[k]));
|
|
43
|
+
prevLines = new Set(body);
|
|
44
|
+
if (freshRaw.length === 0)
|
|
45
|
+
continue;
|
|
46
|
+
out.push({ startSec: toSeconds(m[1]), freshRaw });
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** VTT와 SRT를 읽어 시작 시각과 글만 남긴다 */
|
|
51
|
+
export function parseCues(text) {
|
|
52
|
+
return readBlocks(text).map((b) => ({
|
|
53
|
+
startSec: b.startSec,
|
|
54
|
+
text: b.freshRaw.map(cleanLine).join(' '),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 큐를 단어로 쪼갠다. 유튜브 자동 자막의 `<00:00:01.200>` 태그가 있으면 단어마다 그 시각을 쓰고
|
|
59
|
+
* 없으면 큐 시작 시각을 쓴다. 문장이 큐 중간에서 시작해도 시작 시각이 틀어지지 않게 하려는 것이다.
|
|
60
|
+
*/
|
|
61
|
+
export function parseTimedWords(text) {
|
|
62
|
+
const words = [];
|
|
63
|
+
let pendingBreak = false;
|
|
64
|
+
for (const block of readBlocks(text)) {
|
|
65
|
+
for (const line of block.freshRaw) {
|
|
66
|
+
let sec = block.startSec;
|
|
67
|
+
let last = 0;
|
|
68
|
+
const pieces = [];
|
|
69
|
+
for (const m of line.matchAll(TIME_TAG)) {
|
|
70
|
+
pieces.push({ sec, text: line.slice(last, m.index) });
|
|
71
|
+
sec = toSeconds(m[1]);
|
|
72
|
+
last = m.index + m[0].length;
|
|
73
|
+
}
|
|
74
|
+
pieces.push({ sec, text: line.slice(last) });
|
|
75
|
+
for (const p of pieces) {
|
|
76
|
+
for (const w of decodeEntities(cleanLine(p.text)).split(' ')) {
|
|
77
|
+
if (!w || SOUND_TAG.test(w))
|
|
78
|
+
continue;
|
|
79
|
+
if (w === '>>') {
|
|
80
|
+
pendingBreak = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
words.push(pendingBreak ? { sec: p.sec, text: w, breakBefore: true } : { sec: p.sec, text: w });
|
|
84
|
+
pendingBreak = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return words;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 단어를 문장으로 합친다. 끝 부호에서 끊고 부호가 없으면 쉼 간격과 길이로 끊는다.
|
|
93
|
+
* 문장 시작 시각은 첫 단어의 시각이다.
|
|
94
|
+
*/
|
|
95
|
+
export function mergeSentences(words, options = {}) {
|
|
96
|
+
const opt = { ...DEFAULT_MERGE, ...options };
|
|
97
|
+
const out = [];
|
|
98
|
+
let buf = [];
|
|
99
|
+
let len = 0;
|
|
100
|
+
const flush = () => {
|
|
101
|
+
if (buf.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
out.push({ startSec: buf[0].sec, text: buf.map((w) => w.text).join(' ') });
|
|
104
|
+
buf = [];
|
|
105
|
+
len = 0;
|
|
106
|
+
};
|
|
107
|
+
for (let k = 0; k < words.length; k++) {
|
|
108
|
+
const w = words[k];
|
|
109
|
+
if (w.breakBefore)
|
|
110
|
+
flush();
|
|
111
|
+
buf.push(w);
|
|
112
|
+
len += w.text.length + (buf.length > 1 ? 1 : 0);
|
|
113
|
+
const next = words[k + 1];
|
|
114
|
+
if (!next)
|
|
115
|
+
break;
|
|
116
|
+
const gap = next.sec - w.sec;
|
|
117
|
+
if (SENTENCE_END.test(w.text))
|
|
118
|
+
flush();
|
|
119
|
+
else if (gap >= opt.pauseSec && len >= opt.minChars)
|
|
120
|
+
flush();
|
|
121
|
+
else if (len >= opt.maxChars)
|
|
122
|
+
flush();
|
|
123
|
+
}
|
|
124
|
+
flush();
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
/** 단어 시각이 없는 조각(whisper 구간 같은)을 조각 시작 시각의 단어로 편다 */
|
|
128
|
+
export function cuesToWords(cues) {
|
|
129
|
+
return cues.flatMap((c) => c.text
|
|
130
|
+
.split(/\s+/)
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.map((text) => ({ sec: c.startSec, text })));
|
|
133
|
+
}
|
|
134
|
+
/** 자막 파일 하나를 문장 단위 큐로 만든다 */
|
|
135
|
+
export function captionsToSentences(text, options) {
|
|
136
|
+
return mergeSentences(parseTimedWords(text), options);
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=captions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captions.js","sourceRoot":"","sources":["../../../src/digest/captions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { DigestStore } from './store.js';
|
|
3
|
+
import { type RunCommand } from './ytdlp.js';
|
|
4
|
+
/**
|
|
5
|
+
* 유튜브 댓글을 받아 comments.json으로 둔다. 댓글 분석 템플릿이 이 파일을 읽는다.
|
|
6
|
+
* 작성자 이름과 채널 id, 프로필 사진은 저장하지 않는다. 분석에 안 쓰는 개인정보를 레포에 남기지 않으려는 것이다
|
|
7
|
+
*/
|
|
8
|
+
export declare const COMMENTS_DEFAULT_MAX = 200;
|
|
9
|
+
export declare const COMMENTS_MAX = 1000;
|
|
10
|
+
export declare const storedCommentSchema: z.ZodObject<{
|
|
11
|
+
id: z.ZodString;
|
|
12
|
+
text: z.ZodString;
|
|
13
|
+
likes: z.ZodNumber;
|
|
14
|
+
pinned: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
+
byUploader: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
text: string;
|
|
18
|
+
id: string;
|
|
19
|
+
likes: number;
|
|
20
|
+
pinned?: boolean | undefined;
|
|
21
|
+
byUploader?: boolean | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
text: string;
|
|
24
|
+
id: string;
|
|
25
|
+
likes: number;
|
|
26
|
+
pinned?: boolean | undefined;
|
|
27
|
+
byUploader?: boolean | undefined;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const commentsFileSchema: z.ZodObject<{
|
|
30
|
+
schemaVersion: z.ZodLiteral<"1.0.0">;
|
|
31
|
+
videoId: z.ZodString;
|
|
32
|
+
fetchedAt: z.ZodString;
|
|
33
|
+
/** 영상 정보에 적힌 전체 댓글 수. 받은 개수가 아니다 */
|
|
34
|
+
commentCount: z.ZodNullable<z.ZodNumber>;
|
|
35
|
+
comments: z.ZodArray<z.ZodObject<{
|
|
36
|
+
id: z.ZodString;
|
|
37
|
+
text: z.ZodString;
|
|
38
|
+
likes: z.ZodNumber;
|
|
39
|
+
pinned: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
byUploader: z.ZodOptional<z.ZodBoolean>;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
text: string;
|
|
43
|
+
id: string;
|
|
44
|
+
likes: number;
|
|
45
|
+
pinned?: boolean | undefined;
|
|
46
|
+
byUploader?: boolean | undefined;
|
|
47
|
+
}, {
|
|
48
|
+
text: string;
|
|
49
|
+
id: string;
|
|
50
|
+
likes: number;
|
|
51
|
+
pinned?: boolean | undefined;
|
|
52
|
+
byUploader?: boolean | undefined;
|
|
53
|
+
}>, "many">;
|
|
54
|
+
}, "strip", z.ZodTypeAny, {
|
|
55
|
+
comments: {
|
|
56
|
+
text: string;
|
|
57
|
+
id: string;
|
|
58
|
+
likes: number;
|
|
59
|
+
pinned?: boolean | undefined;
|
|
60
|
+
byUploader?: boolean | undefined;
|
|
61
|
+
}[];
|
|
62
|
+
schemaVersion: "1.0.0";
|
|
63
|
+
videoId: string;
|
|
64
|
+
fetchedAt: string;
|
|
65
|
+
commentCount: number | null;
|
|
66
|
+
}, {
|
|
67
|
+
comments: {
|
|
68
|
+
text: string;
|
|
69
|
+
id: string;
|
|
70
|
+
likes: number;
|
|
71
|
+
pinned?: boolean | undefined;
|
|
72
|
+
byUploader?: boolean | undefined;
|
|
73
|
+
}[];
|
|
74
|
+
schemaVersion: "1.0.0";
|
|
75
|
+
videoId: string;
|
|
76
|
+
fetchedAt: string;
|
|
77
|
+
commentCount: number | null;
|
|
78
|
+
}>;
|
|
79
|
+
export type StoredComment = z.infer<typeof storedCommentSchema>;
|
|
80
|
+
export type CommentsFile = z.infer<typeof commentsFileSchema>;
|
|
81
|
+
/**
|
|
82
|
+
* yt-dlp -J 결과에서 최상위 댓글만 골라 좋아요 순으로 max개를 남긴다.
|
|
83
|
+
* 답글은 원 댓글 맥락 없이 읽으면 성격을 잘못 묶기 쉬워서 뺀다
|
|
84
|
+
*/
|
|
85
|
+
export declare function parseYtComments(raw: unknown, max: number): {
|
|
86
|
+
comments: StoredComment[];
|
|
87
|
+
commentCount: number | null;
|
|
88
|
+
};
|
|
89
|
+
export type CommentsRead = {
|
|
90
|
+
kind: 'missing';
|
|
91
|
+
} | {
|
|
92
|
+
kind: 'invalid';
|
|
93
|
+
message: string;
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'ok';
|
|
96
|
+
value: CommentsFile;
|
|
97
|
+
};
|
|
98
|
+
export declare function readComments(store: DigestStore, slug: string): CommentsRead;
|
|
99
|
+
/** lint가 받는 꼴. 파일이 없거나 깨졌으면 null이다 */
|
|
100
|
+
export declare function commentsMap(store: DigestStore, slug: string): Map<string, string> | null;
|
|
101
|
+
export interface FetchCommentsOptions {
|
|
102
|
+
repoRoot: string;
|
|
103
|
+
max?: number;
|
|
104
|
+
run?: RunCommand;
|
|
105
|
+
}
|
|
106
|
+
export interface FetchCommentsResult {
|
|
107
|
+
slug: string;
|
|
108
|
+
path: string;
|
|
109
|
+
fetched: number;
|
|
110
|
+
commentCount: number | null;
|
|
111
|
+
}
|
|
112
|
+
export declare function fetchComments(slug: string, opts: FetchCommentsOptions): Promise<FetchCommentsResult>;
|
|
113
|
+
//# sourceMappingURL=comments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../../src/digest/comments.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,WAAW,EAAW,MAAM,YAAY,CAAC;AAClD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhE;;;GAGG;AAEH,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,YAAY,OAAO,CAAC;AAIjC,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;EAM9B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;IAI7B,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGpC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAW9D;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,MAAM,GACV;IAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAkC5D;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC;AAExC,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAY3E;AAED,sCAAsC;AACtC,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAGxF;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAiD9B"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { writeJsonAtomic } from '../core/json-file.js';
|
|
6
|
+
import { commentIdSchema } from './blocks.js';
|
|
7
|
+
import { youtubeWatchUrl } from './schema.js';
|
|
8
|
+
import { DigestStore, readRaw } from './store.js';
|
|
9
|
+
import { runCommand, ytdlp } from './ytdlp.js';
|
|
10
|
+
/**
|
|
11
|
+
* 유튜브 댓글을 받아 comments.json으로 둔다. 댓글 분석 템플릿이 이 파일을 읽는다.
|
|
12
|
+
* 작성자 이름과 채널 id, 프로필 사진은 저장하지 않는다. 분석에 안 쓰는 개인정보를 레포에 남기지 않으려는 것이다
|
|
13
|
+
*/
|
|
14
|
+
export const COMMENTS_DEFAULT_MAX = 200;
|
|
15
|
+
export const COMMENTS_MAX = 1000;
|
|
16
|
+
/** 댓글 하나가 이보다 길면 자른다. 분석에는 앞부분이면 충분하다 */
|
|
17
|
+
const COMMENT_TEXT_MAX = 2000;
|
|
18
|
+
export const storedCommentSchema = z.object({
|
|
19
|
+
id: commentIdSchema,
|
|
20
|
+
text: z.string().min(1).max(COMMENT_TEXT_MAX),
|
|
21
|
+
likes: z.number().int().min(0),
|
|
22
|
+
pinned: z.boolean().optional(),
|
|
23
|
+
byUploader: z.boolean().optional(),
|
|
24
|
+
});
|
|
25
|
+
export const commentsFileSchema = z.object({
|
|
26
|
+
schemaVersion: z.literal('1.0.0'),
|
|
27
|
+
videoId: z.string(),
|
|
28
|
+
fetchedAt: z.string(),
|
|
29
|
+
/** 영상 정보에 적힌 전체 댓글 수. 받은 개수가 아니다 */
|
|
30
|
+
commentCount: z.number().int().min(0).nullable(),
|
|
31
|
+
comments: z.array(storedCommentSchema),
|
|
32
|
+
});
|
|
33
|
+
const rawCommentSchema = z.object({
|
|
34
|
+
id: z.unknown(),
|
|
35
|
+
text: z.unknown(),
|
|
36
|
+
parent: z.unknown().optional(),
|
|
37
|
+
like_count: z.unknown().optional(),
|
|
38
|
+
is_pinned: z.unknown().optional(),
|
|
39
|
+
author_is_uploader: z.unknown().optional(),
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* yt-dlp -J 결과에서 최상위 댓글만 골라 좋아요 순으로 max개를 남긴다.
|
|
43
|
+
* 답글은 원 댓글 맥락 없이 읽으면 성격을 잘못 묶기 쉬워서 뺀다
|
|
44
|
+
*/
|
|
45
|
+
export function parseYtComments(raw, max) {
|
|
46
|
+
const obj = (raw ?? {});
|
|
47
|
+
const list = Array.isArray(obj.comments) ? obj.comments : [];
|
|
48
|
+
const out = [];
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
for (const item of list) {
|
|
51
|
+
const r = rawCommentSchema.safeParse(item);
|
|
52
|
+
if (!r.success)
|
|
53
|
+
continue;
|
|
54
|
+
const c = r.data;
|
|
55
|
+
if (c.parent !== undefined && c.parent !== 'root')
|
|
56
|
+
continue;
|
|
57
|
+
if (typeof c.id !== 'string' || !commentIdSchema.safeParse(c.id).success)
|
|
58
|
+
continue;
|
|
59
|
+
if (seen.has(c.id))
|
|
60
|
+
continue;
|
|
61
|
+
const text = typeof c.text === 'string' ? c.text.trim().slice(0, COMMENT_TEXT_MAX) : '';
|
|
62
|
+
if (!text)
|
|
63
|
+
continue;
|
|
64
|
+
seen.add(c.id);
|
|
65
|
+
const likes = typeof c.like_count === 'number' && Number.isFinite(c.like_count)
|
|
66
|
+
? Math.max(0, Math.floor(c.like_count))
|
|
67
|
+
: 0;
|
|
68
|
+
out.push({
|
|
69
|
+
id: c.id,
|
|
70
|
+
text,
|
|
71
|
+
likes,
|
|
72
|
+
...(c.is_pinned === true ? { pinned: true } : {}),
|
|
73
|
+
...(c.author_is_uploader === true ? { byUploader: true } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// sort는 안정 정렬이라 좋아요가 같으면 yt-dlp가 준 순서(인기순)를 지킨다
|
|
77
|
+
out.sort((a, b) => b.likes - a.likes);
|
|
78
|
+
const count = typeof obj.comment_count === 'number' && Number.isInteger(obj.comment_count)
|
|
79
|
+
? Math.max(0, obj.comment_count)
|
|
80
|
+
: null;
|
|
81
|
+
return { comments: out.slice(0, max), commentCount: count };
|
|
82
|
+
}
|
|
83
|
+
export function readComments(store, slug) {
|
|
84
|
+
const raw = readRaw(store.commentsPath(slug));
|
|
85
|
+
if (raw.kind === 'missing')
|
|
86
|
+
return raw;
|
|
87
|
+
if (raw.kind === 'invalid-json')
|
|
88
|
+
return { kind: 'invalid', message: raw.message };
|
|
89
|
+
const parsed = commentsFileSchema.safeParse(raw.value);
|
|
90
|
+
if (!parsed.success) {
|
|
91
|
+
const issues = parsed.error.issues
|
|
92
|
+
.slice(0, 3)
|
|
93
|
+
.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);
|
|
94
|
+
return { kind: 'invalid', message: issues.join('; ') };
|
|
95
|
+
}
|
|
96
|
+
return { kind: 'ok', value: parsed.data };
|
|
97
|
+
}
|
|
98
|
+
/** lint가 받는 꼴. 파일이 없거나 깨졌으면 null이다 */
|
|
99
|
+
export function commentsMap(store, slug) {
|
|
100
|
+
const r = readComments(store, slug);
|
|
101
|
+
return r.kind === 'ok' ? new Map(r.value.comments.map((c) => [c.id, c.text])) : null;
|
|
102
|
+
}
|
|
103
|
+
export async function fetchComments(slug, opts) {
|
|
104
|
+
const max = opts.max ?? COMMENTS_DEFAULT_MAX;
|
|
105
|
+
if (!Number.isInteger(max) || max < 1 || max > COMMENTS_MAX) {
|
|
106
|
+
throw new Error(`--max는 1~${COMMENTS_MAX} 사이 정수여야 해요: ${max}`);
|
|
107
|
+
}
|
|
108
|
+
const store = new DigestStore(opts.repoRoot);
|
|
109
|
+
const t = store.readTranscript(slug);
|
|
110
|
+
if (!t.ok)
|
|
111
|
+
throw new Error(t.message);
|
|
112
|
+
const { meta } = t.transcript;
|
|
113
|
+
if (meta.source !== 'youtube' || !meta.videoId) {
|
|
114
|
+
throw new Error('댓글은 유튜브 영상만 받을 수 있어요.');
|
|
115
|
+
}
|
|
116
|
+
// 답글은 받지 않는다(max_comments의 세 번째와 네 번째 값이 0). 정렬은 인기순이다
|
|
117
|
+
const args = [
|
|
118
|
+
...['--skip-download', '--get-comments', '-J'],
|
|
119
|
+
...['--extractor-args', `youtube:max_comments=${max},${max},0,0;comment_sort=top`],
|
|
120
|
+
youtubeWatchUrl(meta.videoId),
|
|
121
|
+
];
|
|
122
|
+
// 레포 안을 cwd로 쓰면 그 레포의 yt-dlp.conf를 읽는다. frames와 같은 이유로 임시 폴더에서 돈다
|
|
123
|
+
const cwd = mkdtempSync(join(tmpdir(), 'gestalt-digest-run-'));
|
|
124
|
+
try {
|
|
125
|
+
const { stdout } = await ytdlp(opts.run ?? runCommand, args, args, {
|
|
126
|
+
cwd,
|
|
127
|
+
// 댓글은 20개씩 끊어 받아서 개수에 비례해 오래 걸린다
|
|
128
|
+
timeoutMs: (120 + max * 0.5) * 1000,
|
|
129
|
+
});
|
|
130
|
+
let raw;
|
|
131
|
+
try {
|
|
132
|
+
raw = JSON.parse(stdout);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
throw new Error(`yt-dlp 출력을 JSON으로 못 읽었어요: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
136
|
+
}
|
|
137
|
+
const { comments, commentCount } = parseYtComments(raw, max);
|
|
138
|
+
const file = {
|
|
139
|
+
schemaVersion: '1.0.0',
|
|
140
|
+
videoId: meta.videoId,
|
|
141
|
+
fetchedAt: new Date().toISOString(),
|
|
142
|
+
commentCount,
|
|
143
|
+
comments,
|
|
144
|
+
};
|
|
145
|
+
const path = store.commentsPath(slug);
|
|
146
|
+
writeJsonAtomic(path, file);
|
|
147
|
+
return { slug, path, fetched: comments.length, commentCount };
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../../../src/digest/comments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Transcript } from './schema.js';
|
|
2
|
+
/** 1시간이 넘는 영상만 시를 붙인다. 스크립트 패널 폭을 아끼려는 것이다 */
|
|
3
|
+
export declare function formatTime(sec: number, withHours?: boolean): string;
|
|
4
|
+
/** 유튜브 영상이면 그 시각으로 여는 주소, 아니면 null */
|
|
5
|
+
export declare function watchUrlAt(meta: Transcript['meta'], sec: number): string | null;
|
|
6
|
+
/** 작성자가 "1.2." 같은 번호를 붙여 와도 렌더러가 다시 매기므로 떼어 낸다 */
|
|
7
|
+
export declare function stripNumbering(title: string): string;
|
|
8
|
+
export declare const CAPTION_LABEL: Record<Transcript['meta']['captionSource'], string>;
|
|
9
|
+
//# sourceMappingURL=format.d.ts.map
|