kakaotalk-chat-analyzer 0.2.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/LICENSE +21 -0
- package/README.md +190 -0
- package/dist/src/analysis.d.ts +8 -0
- package/dist/src/analysis.js +406 -0
- package/dist/src/analysis.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +177 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.js +59 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/date.d.ts +7 -0
- package/dist/src/date.js +32 -0
- package/dist/src/date.js.map +1 -0
- package/dist/src/encoding.d.ts +5 -0
- package/dist/src/encoding.js +53 -0
- package/dist/src/encoding.js.map +1 -0
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.js +206 -0
- package/dist/src/parser.js.map +1 -0
- package/dist/src/providers/brewpage.d.ts +8 -0
- package/dist/src/providers/brewpage.js +96 -0
- package/dist/src/providers/brewpage.js.map +1 -0
- package/dist/src/providers/cloudflare.d.ts +5 -0
- package/dist/src/providers/cloudflare.js +7 -0
- package/dist/src/providers/cloudflare.js.map +1 -0
- package/dist/src/providers/index.d.ts +3 -0
- package/dist/src/providers/index.js +20 -0
- package/dist/src/providers/index.js.map +1 -0
- package/dist/src/providers/tempfile.d.ts +8 -0
- package/dist/src/providers/tempfile.js +60 -0
- package/dist/src/providers/tempfile.js.map +1 -0
- package/dist/src/providers/types.d.ts +32 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/report.d.ts +2 -0
- package/dist/src/report.js +227 -0
- package/dist/src/report.js.map +1 -0
- package/dist/src/types.d.ts +97 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +3 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 claudianus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# KakaoTalk Chat Analyzer
|
|
4
|
+
|
|
5
|
+
### 카카오톡 CSV보내기 → 익명 집계 리포트 → 선택적 임시 공유 · 한 번에 끝내는 CLI
|
|
6
|
+
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](https://claudianus.github.io/kakaotalk-chat-analyzer/)
|
|
10
|
+
|
|
11
|
+
[**랜딩 페이지**](https://claudianus.github.io/kakaotalk-chat-analyzer/) · [**소스 코드**](https://github.com/claudianus/kakaotalk-chat-analyzer) · [**이슈 트래커**](https://github.com/claudianus/kakaotalk-chat-analyzer/issues)
|
|
12
|
+
|
|
13
|
+
<br>
|
|
14
|
+
|
|
15
|
+
<img src="https://readme-typing-svg.demolab.com?font=DM+Sans&weight=700&size=22&pause=1200&color=3EE8C5¢er=true&vCenter=true&width=780&lines=Privacy-first+chat+analytics+for+KakaoTalk+CSV+exports;Single-file+HTML+reports+%2B+optional+zero-login+hosting" alt="tagline animation" />
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 왜 이 프로젝트인가요?
|
|
22
|
+
|
|
23
|
+
카카오톡 대화를 **CSV로 보낸 뒤**, 팀·친구·커뮤니티에 **재미있는 통계**를 공유하고 싶을 때가 있습니다.
|
|
24
|
+
그런데 원문 그대로 올리기엔 **개인정보·민감 URL** 리스크가 큽니다.
|
|
25
|
+
|
|
26
|
+
**`kca`(KakaoTalk Chat Analyzer)**는 메시지 본문을 **리포트 파일에 저장하지 않고**, 집계 통계만 담은 **단일 `index.html`**을 생성합니다. 기본은 참여자 이름을 **부분 마스킹**해 방 안에서 누구인지 감은 오게 보여 주며, 필요하면 **가입 없이** 임시 HTML 호스트에 올려 링크로 공유할 수 있습니다.
|
|
27
|
+
|
|
28
|
+
> 이 도구는 카카오 공식 제품이 아닙니다. 보낸 CSV 형식 변경에 따라 파싱이 깨질 수 있으니, 중요한 데이터는 항상 백업하세요.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 핵심 기능
|
|
33
|
+
|
|
34
|
+
| 영역 | 설명 |
|
|
35
|
+
|------|------|
|
|
36
|
+
| **인코딩** | UTF-8 BOM, UTF-8, CP949/EUC-KR 등 보내기 인코딩 자동 감지 |
|
|
37
|
+
| **파싱** | `Date,User,Message` 헤더 기반 CSV + 멀티라인 메시지 처리 |
|
|
38
|
+
| **리포트** | 한글 UI, 하이라이트, 월별·심야·응답 간격 등 **프리미엄급 집계** |
|
|
39
|
+
| **배포** | BrewPage(기본) / TempFile / Cloudflare 등 **TTL 기반** 임시 호스팅 연동 |
|
|
40
|
+
| **프라이버시** | 원문 미포함, 참여자 **부분 마스킹 표시명**(기본), URL은 **도메인**만 집계 |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 빠른 시작
|
|
45
|
+
|
|
46
|
+
### 요구 사항
|
|
47
|
+
|
|
48
|
+
- [Node.js](https://nodejs.org/) **22 이상**
|
|
49
|
+
|
|
50
|
+
### npx 한 줄 (추천)
|
|
51
|
+
|
|
52
|
+
npm에 패키지가 등록되어 있으면 아래처럼 실행할 수 있습니다(아직 레지스트리에 없다면 **GitHub 줄**을 쓰면 됩니다).
|
|
53
|
+
|
|
54
|
+
**로컬에만** 리포트를 만들고 싶다면(`./report/index.html`):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx -y --package=kakaotalk-chat-analyzer@latest kca ./KakaoTalk_Chat_....csv --local -o ./report
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**리포트 생성 + BrewPage 임시 업로드**까지 한 번에:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx -y --package=kakaotalk-chat-analyzer@latest kca ./KakaoTalk_Chat_....csv
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
GitHub 소스에서 직접 실행(저장소에 컴파일된 `dist` 포함):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx -y github:claudianus/kakaotalk-chat-analyzer kca ./KakaoTalk_Chat_....csv --local -o ./report
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 로컬 클론 개발
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/claudianus/kakaotalk-chat-analyzer.git
|
|
76
|
+
cd kakaotalk-chat-analyzer
|
|
77
|
+
npm install
|
|
78
|
+
npm run build
|
|
79
|
+
npm test
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### CLI 요약
|
|
83
|
+
|
|
84
|
+
기본 동작은 **서브커맨드 없이** `<csv>` 한 개만 주면 됩니다.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 기본: HTML 생성 후 BrewPage 업로드
|
|
88
|
+
kca ./KakaoTalk_Chat_....csv
|
|
89
|
+
|
|
90
|
+
# 업로드 없이 로컬만
|
|
91
|
+
kca ./KakaoTalk_Chat_....csv --local -o ./report
|
|
92
|
+
|
|
93
|
+
# 업로드 생략(드라이런)
|
|
94
|
+
kca ./KakaoTalk_Chat_....csv --dry-run
|
|
95
|
+
|
|
96
|
+
# TempFile 호스트
|
|
97
|
+
kca ./chat.csv --host tempfile --ttl 30
|
|
98
|
+
|
|
99
|
+
# 보내기 구조 점검(원문 출력 없음)
|
|
100
|
+
kca inspect ./KakaoTalk_Chat_....csv
|
|
101
|
+
|
|
102
|
+
kca --help
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
업로드가 실패해도 **로컬 `index.html`은 남습니다**.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 프라이버시 기본값
|
|
110
|
+
|
|
111
|
+
- 기본(`public-masked`)은 참여자 이름을 **앞·뒤 글자만 남기고 가운데 마스킹**합니다(동명이의 충돌 시 `·2`처럼 구분자가 붙을 수 있음). 완전 별칭(`User 001`)은 `--privacy public-anonymous` 로 선택할 수 있습니다.
|
|
112
|
+
- 메시지 텍스트는 **통계 계산에만** 사용되며, **생성된 HTML에 원문이 쓰이지 않습니다**.
|
|
113
|
+
- URL에서 **도메인**만 집계하고, 전체 URL 문자열은 리포트에 보존하지 않습니다.
|
|
114
|
+
- BrewPage **owner 토큰**은 로컬에 저장되어, 이후 링크 관리·삭제에 활용할 수 있습니다.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# 저장된 owner 토큰 삭제
|
|
118
|
+
kca token clear --host brewpage --ns kakao-chat-report
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 아키텍처 한눈에
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
CSV 파일
|
|
127
|
+
→ parser (인코딩·CSV·날짜 파싱)
|
|
128
|
+
→ analysis (집계·부분 마스킹·키워드/도메인·하이라이트 등)
|
|
129
|
+
→ report (단일 HTML 렌더)
|
|
130
|
+
→ [선택] providers (BrewPage / TempFile / Cloudflare 업로드)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 개발
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm install
|
|
139
|
+
npm run build
|
|
140
|
+
npm test
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 문서 사이트 (GitHub Pages)
|
|
146
|
+
|
|
147
|
+
프로젝트 소개·시작 가이드가 담긴 정적 사이트는 **GitHub Actions**로 배포됩니다.
|
|
148
|
+
|
|
149
|
+
- **URL:** [https://claudianus.github.io/kakaotalk-chat-analyzer/](https://claudianus.github.io/kakaotalk-chat-analyzer/)
|
|
150
|
+
|
|
151
|
+
### 첫 설정 팁
|
|
152
|
+
|
|
153
|
+
저장소를 포크하거나 새로 만들었다면, 한 번 확인하세요.
|
|
154
|
+
|
|
155
|
+
1. **Settings → Pages**
|
|
156
|
+
2. **Build and deployment → Source:** `GitHub Actions` 선택
|
|
157
|
+
3. `main` 브랜치에 푸시되면 워크플로 **Deploy GitHub Pages**가 실행됩니다.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 기여하기
|
|
162
|
+
|
|
163
|
+
이슈·PR·아이디어 모두 환영합니다. 큰 변경 전에는 이슈에서 방향을 먼저 짧게 나누면 리뷰가 빨라집니다.
|
|
164
|
+
|
|
165
|
+
1. Fork → 브랜치 생성
|
|
166
|
+
2. `npm test` 통과
|
|
167
|
+
3. PR 설명에 **동기(왜)** 와 **테스트 방법**을 적어 주세요.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 보안
|
|
172
|
+
|
|
173
|
+
민감한 CSV나 토큰이 포함된 스크린샷은 **이슈/PR에 첨부하지 마세요**.
|
|
174
|
+
취약점으로 보이는 내용은 비공개 채널(예: GitHub Security Advisories)로 알려 주시면 감사하겠습니다.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 라이선스
|
|
179
|
+
|
|
180
|
+
[MIT License](./LICENSE)
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
<div align="center">
|
|
185
|
+
|
|
186
|
+
**Made with care for safer chat analytics** · [@claudianus](https://github.com/claudianus)
|
|
187
|
+
|
|
188
|
+
⭐ 이 프로젝트가 도움이 되었다면 스타 한 번 부탁드립니다.
|
|
189
|
+
|
|
190
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParseResult, PrivacyMode, ReportData } from "./types.js";
|
|
2
|
+
export declare function buildReportData(result: ParseResult, options?: {
|
|
3
|
+
privacy?: PrivacyMode;
|
|
4
|
+
top?: number;
|
|
5
|
+
}): ReportData;
|
|
6
|
+
export declare function safeInputName(filePath: string): string;
|
|
7
|
+
/** 참여자 실명 대신 앞·뒤 일부만 남기고 가운데는 마스킹합니다. */
|
|
8
|
+
export declare function maskPartialDisplayName(raw: string): string;
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import { formatDate, formatDateTime, partsToUtcMs, weekdayIndex } from "./date.js";
|
|
3
|
+
const ATTACHMENT_MARKERS = [
|
|
4
|
+
"사진",
|
|
5
|
+
"동영상",
|
|
6
|
+
"파일",
|
|
7
|
+
"이모티콘",
|
|
8
|
+
"지도",
|
|
9
|
+
"연락처",
|
|
10
|
+
"투표",
|
|
11
|
+
"공유",
|
|
12
|
+
"음성메시지",
|
|
13
|
+
"삭제된 메시지",
|
|
14
|
+
];
|
|
15
|
+
const WEEKDAY_LABELS_KO = ["일", "월", "화", "수", "목", "금", "토"];
|
|
16
|
+
const URL_RE = /\bhttps?:\/\/[^\s<>"']+|www\.[^\s<>"']+/gi;
|
|
17
|
+
const EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
18
|
+
const PHONE_RE = /\b(?:\+?\d[\d\s().-]{7,}\d)\b/g;
|
|
19
|
+
const TOKEN_RE = /[가-힣A-Za-z][가-힣A-Za-z0-9_+-]{1,}/g;
|
|
20
|
+
const STOPWORDS = new Set([
|
|
21
|
+
"그리고",
|
|
22
|
+
"그냥",
|
|
23
|
+
"근데",
|
|
24
|
+
"그래서",
|
|
25
|
+
"저는",
|
|
26
|
+
"제가",
|
|
27
|
+
"우리",
|
|
28
|
+
"오늘",
|
|
29
|
+
"내일",
|
|
30
|
+
"어제",
|
|
31
|
+
"이거",
|
|
32
|
+
"저거",
|
|
33
|
+
"그거",
|
|
34
|
+
"수정",
|
|
35
|
+
"확인",
|
|
36
|
+
"가능",
|
|
37
|
+
"입니다",
|
|
38
|
+
"합니다",
|
|
39
|
+
"있습니다",
|
|
40
|
+
"없는",
|
|
41
|
+
"있는",
|
|
42
|
+
"the",
|
|
43
|
+
"and",
|
|
44
|
+
"for",
|
|
45
|
+
"with",
|
|
46
|
+
"this",
|
|
47
|
+
"that",
|
|
48
|
+
"from",
|
|
49
|
+
"http",
|
|
50
|
+
"https",
|
|
51
|
+
]);
|
|
52
|
+
const NIGHT_HOURS = new Set([23, 0, 1, 2, 3, 4, 5]);
|
|
53
|
+
const MAX_GAP_MS = 7 * 24 * 60 * 60 * 1000;
|
|
54
|
+
export function buildReportData(result, options) {
|
|
55
|
+
const top = options?.top ?? 30;
|
|
56
|
+
const privacy = options?.privacy ?? "public-masked";
|
|
57
|
+
const aliases = buildSenderLabels(result.records, privacy);
|
|
58
|
+
const senderStats = new Map();
|
|
59
|
+
const daily = new Map();
|
|
60
|
+
const monthly = new Map();
|
|
61
|
+
const hourly = Array.from({ length: 24 }, () => 0);
|
|
62
|
+
const weekdays = Array.from({ length: 7 }, () => 0);
|
|
63
|
+
const attachments = new Map();
|
|
64
|
+
const domains = new Map();
|
|
65
|
+
const keywords = new Map();
|
|
66
|
+
const senderNames = new Set(result.records.map((record) => normalizeToken(record.sender)));
|
|
67
|
+
let totalCharacters = 0;
|
|
68
|
+
let messagesWithLinks = 0;
|
|
69
|
+
let messagesWithAttachments = 0;
|
|
70
|
+
let nightMessages = 0;
|
|
71
|
+
let emojiMessages = 0;
|
|
72
|
+
const gapsMs = [];
|
|
73
|
+
let prevMs = null;
|
|
74
|
+
let prevSender = null;
|
|
75
|
+
let runSender = null;
|
|
76
|
+
let runLen = 0;
|
|
77
|
+
for (const record of result.records) {
|
|
78
|
+
const alias = aliases.get(record.sender) ?? "???";
|
|
79
|
+
const stat = getParticipantStat(senderStats, alias);
|
|
80
|
+
const messageLength = record.message.length;
|
|
81
|
+
const foundAttachments = getAttachmentMarkers(record.message);
|
|
82
|
+
const foundDomains = getDomains(record.message);
|
|
83
|
+
const ms = partsToUtcMs(record.date);
|
|
84
|
+
if (/\p{Extended_Pictographic}/u.test(record.message)) {
|
|
85
|
+
emojiMessages += 1;
|
|
86
|
+
}
|
|
87
|
+
if (NIGHT_HOURS.has(record.date.hour)) {
|
|
88
|
+
nightMessages += 1;
|
|
89
|
+
stat.nightMessages += 1;
|
|
90
|
+
}
|
|
91
|
+
if (prevMs !== null) {
|
|
92
|
+
const delta = ms - prevMs;
|
|
93
|
+
if (delta > 0 && delta <= MAX_GAP_MS)
|
|
94
|
+
gapsMs.push(delta);
|
|
95
|
+
}
|
|
96
|
+
prevMs = ms;
|
|
97
|
+
if (record.sender === prevSender) {
|
|
98
|
+
runLen += 1;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
if (prevSender !== null && runSender !== null) {
|
|
102
|
+
const prevAlias = aliases.get(prevSender) ?? "???";
|
|
103
|
+
const prevStat = getParticipantStat(senderStats, prevAlias);
|
|
104
|
+
prevStat.maxConsecutive = Math.max(prevStat.maxConsecutive, runLen);
|
|
105
|
+
}
|
|
106
|
+
runSender = record.sender;
|
|
107
|
+
runLen = 1;
|
|
108
|
+
}
|
|
109
|
+
prevSender = record.sender;
|
|
110
|
+
stat.messages += 1;
|
|
111
|
+
stat.characters += messageLength;
|
|
112
|
+
totalCharacters += messageLength;
|
|
113
|
+
if (foundAttachments.length > 0) {
|
|
114
|
+
stat.attachmentMessages += 1;
|
|
115
|
+
messagesWithAttachments += 1;
|
|
116
|
+
for (const marker of foundAttachments)
|
|
117
|
+
increment(attachments, marker);
|
|
118
|
+
}
|
|
119
|
+
if (foundDomains.length > 0) {
|
|
120
|
+
stat.linkMessages += 1;
|
|
121
|
+
messagesWithLinks += 1;
|
|
122
|
+
for (const domain of foundDomains)
|
|
123
|
+
increment(domains, domain);
|
|
124
|
+
}
|
|
125
|
+
for (const keyword of extractKeywords(record.message, senderNames)) {
|
|
126
|
+
increment(keywords, keyword);
|
|
127
|
+
}
|
|
128
|
+
const dayKey = formatDate(record.date);
|
|
129
|
+
increment(daily, dayKey);
|
|
130
|
+
increment(monthly, `${record.date.year}-${pad2(record.date.month)}`);
|
|
131
|
+
hourly[record.date.hour] = (hourly[record.date.hour] ?? 0) + 1;
|
|
132
|
+
weekdays[weekdayIndex(record.date)] = (weekdays[weekdayIndex(record.date)] ?? 0) + 1;
|
|
133
|
+
}
|
|
134
|
+
if (prevSender !== null && runSender !== null) {
|
|
135
|
+
const prevAlias = aliases.get(prevSender) ?? "???";
|
|
136
|
+
const prevStat = getParticipantStat(senderStats, prevAlias);
|
|
137
|
+
prevStat.maxConsecutive = Math.max(prevStat.maxConsecutive, runLen);
|
|
138
|
+
}
|
|
139
|
+
const total = result.records.length;
|
|
140
|
+
const participantStats = [...senderStats.values()]
|
|
141
|
+
.map((stat) => {
|
|
142
|
+
const sharePercent = total > 0 ? round((stat.messages / total) * 100, 1) : 0;
|
|
143
|
+
return {
|
|
144
|
+
alias: stat.alias,
|
|
145
|
+
messages: stat.messages,
|
|
146
|
+
characters: stat.characters,
|
|
147
|
+
averageLength: round(stat.characters / Math.max(stat.messages, 1), 1),
|
|
148
|
+
attachmentMessages: stat.attachmentMessages,
|
|
149
|
+
linkMessages: stat.linkMessages,
|
|
150
|
+
sharePercent,
|
|
151
|
+
nightMessages: stat.nightMessages,
|
|
152
|
+
maxConsecutive: stat.maxConsecutive,
|
|
153
|
+
};
|
|
154
|
+
})
|
|
155
|
+
.sort((a, b) => b.messages - a.messages)
|
|
156
|
+
.slice(0, top);
|
|
157
|
+
const sortedDays = [...daily.keys()].sort();
|
|
158
|
+
const longestStreak = longestDateStreak(sortedDays);
|
|
159
|
+
let peakHour = null;
|
|
160
|
+
let peakCount = -1;
|
|
161
|
+
for (let h = 0; h < 24; h += 1) {
|
|
162
|
+
const c = hourly[h] ?? 0;
|
|
163
|
+
if (c > peakCount) {
|
|
164
|
+
peakCount = c;
|
|
165
|
+
peakHour = h;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (peakCount <= 0)
|
|
169
|
+
peakHour = null;
|
|
170
|
+
let busiestIdx = -1;
|
|
171
|
+
let busiestCount = -1;
|
|
172
|
+
for (let i = 0; i < 7; i += 1) {
|
|
173
|
+
const c = weekdays[i] ?? 0;
|
|
174
|
+
if (c > busiestCount) {
|
|
175
|
+
busiestCount = c;
|
|
176
|
+
busiestIdx = i;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const busiestWeekdayLabel = busiestIdx >= 0 && busiestCount > 0 ? `${WEEKDAY_LABELS_KO[busiestIdx] ?? ""}요일` : null;
|
|
180
|
+
const medianReplyGapMinutes = gapsMs.length > 0 ? round(medianSorted([...gapsMs].sort((a, b) => a - b)) / 60_000, 1) : null;
|
|
181
|
+
const nightSharePercent = total > 0 ? round((nightMessages / total) * 100, 1) : 0;
|
|
182
|
+
const activeDays = daily.size;
|
|
183
|
+
const messagesPerActiveDay = activeDays > 0 ? round(total / activeDays, 1) : 0;
|
|
184
|
+
const highlights = buildHighlights({
|
|
185
|
+
total,
|
|
186
|
+
topAlias: participantStats[0]?.alias ?? null,
|
|
187
|
+
topShare: participantStats[0]?.sharePercent ?? null,
|
|
188
|
+
busiestWeekdayLabel,
|
|
189
|
+
peakHour,
|
|
190
|
+
medianReplyGapMinutes,
|
|
191
|
+
nightSharePercent,
|
|
192
|
+
longestStreak,
|
|
193
|
+
emojiMessages,
|
|
194
|
+
messagesWithAttachments,
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
generatedAt: new Date().toISOString(),
|
|
198
|
+
privacy,
|
|
199
|
+
source: {
|
|
200
|
+
fileName: "KakaoTalk export",
|
|
201
|
+
encoding: result.encoding,
|
|
202
|
+
physicalLines: result.physicalLines,
|
|
203
|
+
warnings: result.warnings.length,
|
|
204
|
+
},
|
|
205
|
+
summary: {
|
|
206
|
+
totalMessages: total,
|
|
207
|
+
participants: aliases.size,
|
|
208
|
+
activeDays,
|
|
209
|
+
firstMessage: result.records[0] ? formatDateTime(result.records[0].date) : null,
|
|
210
|
+
lastMessage: result.records.at(-1) ? formatDateTime(result.records.at(-1).date) : null,
|
|
211
|
+
averageMessageLength: round(totalCharacters / Math.max(total, 1), 1),
|
|
212
|
+
messagesWithLinks,
|
|
213
|
+
messagesWithAttachments,
|
|
214
|
+
messagesPerActiveDay,
|
|
215
|
+
longestActiveStreakDays: longestStreak,
|
|
216
|
+
peakHour,
|
|
217
|
+
busiestWeekdayLabel,
|
|
218
|
+
medianReplyGapMinutes,
|
|
219
|
+
nightSharePercent,
|
|
220
|
+
emojiMessages,
|
|
221
|
+
},
|
|
222
|
+
participants: participantStats,
|
|
223
|
+
daily: [...daily.entries()].map(([date, count]) => ({ date, count })).sort((a, b) => a.date.localeCompare(b.date)),
|
|
224
|
+
hourly,
|
|
225
|
+
weekdays: weekdays.map((count, index) => ({ label: `${WEEKDAY_LABELS_KO[index] ?? index}요일`, count })),
|
|
226
|
+
monthly: [...monthly.entries()].map(([date, count]) => ({ date, count })).sort((a, b) => a.date.localeCompare(b.date)),
|
|
227
|
+
attachments: topCounts(attachments, top),
|
|
228
|
+
domains: topCounts(domains, top),
|
|
229
|
+
keywords: topCounts(keywords, top),
|
|
230
|
+
highlights,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export function safeInputName(filePath) {
|
|
234
|
+
const name = basename(filePath);
|
|
235
|
+
return name.length > 80 ? `${name.slice(0, 77)}...` : name;
|
|
236
|
+
}
|
|
237
|
+
function getParticipantStat(stats, alias) {
|
|
238
|
+
const existing = stats.get(alias);
|
|
239
|
+
if (existing)
|
|
240
|
+
return existing;
|
|
241
|
+
const created = {
|
|
242
|
+
alias,
|
|
243
|
+
messages: 0,
|
|
244
|
+
characters: 0,
|
|
245
|
+
attachmentMessages: 0,
|
|
246
|
+
linkMessages: 0,
|
|
247
|
+
nightMessages: 0,
|
|
248
|
+
maxConsecutive: 0,
|
|
249
|
+
};
|
|
250
|
+
stats.set(alias, created);
|
|
251
|
+
return created;
|
|
252
|
+
}
|
|
253
|
+
function buildSenderLabels(records, privacy) {
|
|
254
|
+
const unique = [];
|
|
255
|
+
const seen = new Set();
|
|
256
|
+
for (const r of records) {
|
|
257
|
+
if (!seen.has(r.sender)) {
|
|
258
|
+
seen.add(r.sender);
|
|
259
|
+
unique.push(r.sender);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (privacy === "public-anonymous") {
|
|
263
|
+
const map = new Map();
|
|
264
|
+
unique.forEach((sender, i) => map.set(sender, `User ${String(i + 1).padStart(3, "0")}`));
|
|
265
|
+
return map;
|
|
266
|
+
}
|
|
267
|
+
const map = new Map();
|
|
268
|
+
const used = new Map();
|
|
269
|
+
for (const raw of unique) {
|
|
270
|
+
let base = maskPartialDisplayName(raw);
|
|
271
|
+
const n = (used.get(base) ?? 0) + 1;
|
|
272
|
+
used.set(base, n);
|
|
273
|
+
if (n > 1)
|
|
274
|
+
base = `${base}·${n}`;
|
|
275
|
+
map.set(raw, base);
|
|
276
|
+
}
|
|
277
|
+
return map;
|
|
278
|
+
}
|
|
279
|
+
/** 참여자 실명 대신 앞·뒤 일부만 남기고 가운데는 마스킹합니다. */
|
|
280
|
+
export function maskPartialDisplayName(raw) {
|
|
281
|
+
const s = raw.trim();
|
|
282
|
+
if (!s)
|
|
283
|
+
return "?";
|
|
284
|
+
const chars = [...s];
|
|
285
|
+
if (chars.length === 1)
|
|
286
|
+
return `${chars[0]}*`;
|
|
287
|
+
if (chars.length === 2)
|
|
288
|
+
return `${chars[0]}*`;
|
|
289
|
+
const midLen = Math.min(chars.length - 2, 6);
|
|
290
|
+
const middle = "*".repeat(Math.max(1, midLen));
|
|
291
|
+
return `${chars[0]}${middle}${chars[chars.length - 1]}`;
|
|
292
|
+
}
|
|
293
|
+
function getAttachmentMarkers(message) {
|
|
294
|
+
return ATTACHMENT_MARKERS.filter((marker) => message.includes(marker));
|
|
295
|
+
}
|
|
296
|
+
function getDomains(message) {
|
|
297
|
+
const matches = message.match(URL_RE) ?? [];
|
|
298
|
+
const domains = [];
|
|
299
|
+
for (const match of matches) {
|
|
300
|
+
const urlText = match.startsWith("http") ? match : `https://${match}`;
|
|
301
|
+
try {
|
|
302
|
+
const url = new URL(urlText);
|
|
303
|
+
domains.push(url.hostname.toLowerCase().replace(/^www\./, ""));
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return domains;
|
|
310
|
+
}
|
|
311
|
+
function extractKeywords(message, senderNames) {
|
|
312
|
+
const withoutSensitivePatterns = message
|
|
313
|
+
.replace(URL_RE, " ")
|
|
314
|
+
.replace(EMAIL_RE, " ")
|
|
315
|
+
.replace(PHONE_RE, " ");
|
|
316
|
+
const tokens = withoutSensitivePatterns.match(TOKEN_RE) ?? [];
|
|
317
|
+
const keywords = [];
|
|
318
|
+
for (const token of tokens) {
|
|
319
|
+
const normalized = normalizeToken(token);
|
|
320
|
+
if (!normalized)
|
|
321
|
+
continue;
|
|
322
|
+
if (normalized.length < 2 || normalized.length > 30)
|
|
323
|
+
continue;
|
|
324
|
+
if (STOPWORDS.has(normalized))
|
|
325
|
+
continue;
|
|
326
|
+
if (senderNames.has(normalized))
|
|
327
|
+
continue;
|
|
328
|
+
if (/^\d+$/.test(normalized))
|
|
329
|
+
continue;
|
|
330
|
+
keywords.push(normalized);
|
|
331
|
+
}
|
|
332
|
+
return keywords;
|
|
333
|
+
}
|
|
334
|
+
function normalizeToken(token) {
|
|
335
|
+
return /^[A-Za-z0-9_+-]+$/.test(token) ? token.toLowerCase() : token.trim();
|
|
336
|
+
}
|
|
337
|
+
function increment(map, key, amount = 1) {
|
|
338
|
+
map.set(key, (map.get(key) ?? 0) + amount);
|
|
339
|
+
}
|
|
340
|
+
function topCounts(map, limit) {
|
|
341
|
+
return [...map.entries()]
|
|
342
|
+
.map(([label, count]) => ({ label, count }))
|
|
343
|
+
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label))
|
|
344
|
+
.slice(0, limit);
|
|
345
|
+
}
|
|
346
|
+
function round(value, decimals) {
|
|
347
|
+
const factor = 10 ** decimals;
|
|
348
|
+
return Math.round(value * factor) / factor;
|
|
349
|
+
}
|
|
350
|
+
function pad2(value) {
|
|
351
|
+
return value.toString().padStart(2, "0");
|
|
352
|
+
}
|
|
353
|
+
function medianSorted(sorted) {
|
|
354
|
+
if (sorted.length === 0)
|
|
355
|
+
return 0;
|
|
356
|
+
const mid = Math.floor(sorted.length / 2);
|
|
357
|
+
return sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
358
|
+
}
|
|
359
|
+
function longestDateStreak(sortedYmd) {
|
|
360
|
+
if (sortedYmd.length === 0)
|
|
361
|
+
return 0;
|
|
362
|
+
let best = 1;
|
|
363
|
+
let cur = 1;
|
|
364
|
+
for (let i = 1; i < sortedYmd.length; i += 1) {
|
|
365
|
+
const a = new Date(`${sortedYmd[i - 1]}T12:00:00Z`).getTime();
|
|
366
|
+
const b = new Date(`${sortedYmd[i]}T12:00:00Z`).getTime();
|
|
367
|
+
const diffDays = Math.round((b - a) / 86_400_000);
|
|
368
|
+
if (diffDays === 1) {
|
|
369
|
+
cur += 1;
|
|
370
|
+
best = Math.max(best, cur);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
cur = 1;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return best;
|
|
377
|
+
}
|
|
378
|
+
function buildHighlights(input) {
|
|
379
|
+
const out = [];
|
|
380
|
+
if (input.topAlias && input.topShare !== null && input.total > 0) {
|
|
381
|
+
out.push(`가장 말이 많았던 분은 **${input.topAlias}** (전체의 **${input.topShare}%**).`);
|
|
382
|
+
}
|
|
383
|
+
if (input.busiestWeekdayLabel) {
|
|
384
|
+
out.push(`요일별로는 **${input.busiestWeekdayLabel}**에 활동이 가장 활발했어요.`);
|
|
385
|
+
}
|
|
386
|
+
if (input.peakHour !== null) {
|
|
387
|
+
out.push(`시간대는 **${input.peakHour}시**대에 메시지가 가장 몰렸습니다.`);
|
|
388
|
+
}
|
|
389
|
+
if (input.medianReplyGapMinutes !== null) {
|
|
390
|
+
out.push(`연속 메시지 사이 간격의 중앙값은 약 **${input.medianReplyGapMinutes}분**이에요.`);
|
|
391
|
+
}
|
|
392
|
+
if (input.nightSharePercent > 0) {
|
|
393
|
+
out.push(`심야(23~05시) 메시지 비중은 **${input.nightSharePercent}%**입니다.`);
|
|
394
|
+
}
|
|
395
|
+
if (input.longestStreak > 1) {
|
|
396
|
+
out.push(`하루도 빠짐없이 이어진 최장 **${input.longestStreak}일** 연속 활동 기록이 있어요.`);
|
|
397
|
+
}
|
|
398
|
+
if (input.emojiMessages > 0) {
|
|
399
|
+
out.push(`이모지·스티커 느낌의 메시지는 **${input.emojiMessages}**건 정도 감지됐어요.`);
|
|
400
|
+
}
|
|
401
|
+
if (input.messagesWithAttachments > 0) {
|
|
402
|
+
out.push(`사진·파일·동영상 등 첨부가 들어간 메시지는 **${input.messagesWithAttachments}**건입니다.`);
|
|
403
|
+
}
|
|
404
|
+
return out.slice(0, 8);
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis.js","sourceRoot":"","sources":["../../src/analysis.ts"],"names":[],"mappings":"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"}
|