@bifos/dooray-cli 0.1.1 → 0.3.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 +48 -0
- package/dist/index.js +619 -38
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -82,6 +82,54 @@ dooray wiki pages tc-ocr # 페이지 목록
|
|
|
82
82
|
dooray wiki page get tc-ocr <page-id> # 페이지 상세
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
### 메일
|
|
86
|
+
|
|
87
|
+
IMAP을 통해 Dooray 메일을 조회할 수 있습니다.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 초기 설정 (서버 정보는 기본값 제공)
|
|
91
|
+
dooray config set imap-username your@email.com
|
|
92
|
+
dooray config set imap-password <IMAP_APP_PASSWORD>
|
|
93
|
+
|
|
94
|
+
# 메일 조회
|
|
95
|
+
dooray mail list # 최근 메일 목록
|
|
96
|
+
dooray mail list --unread # 안읽은 메일만
|
|
97
|
+
dooray mail list --search "키워드" # 제목 검색
|
|
98
|
+
dooray mail list --size 50 # 조회 개수 지정
|
|
99
|
+
dooray mail get <uid> # 메일 상세
|
|
100
|
+
dooray mail get <uid> --json # JSON 출력
|
|
101
|
+
|
|
102
|
+
# 메일 발송
|
|
103
|
+
dooray mail send --to "user@example.com" --subject "제목" --body "본문"
|
|
104
|
+
dooray mail send --to "a@b.com" --cc "c@d.com" --subject "제목" --body-file ./content.md
|
|
105
|
+
dooray mail send --to "a@b.com" --subject "HTML 메일" --body "<h1>Hello</h1>" --html
|
|
106
|
+
|
|
107
|
+
# 메일 답장 (스레드 유지)
|
|
108
|
+
dooray mail reply <uid> --body "답장 내용"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 첨부파일
|
|
112
|
+
|
|
113
|
+
업무에 파일을 첨부하거나, 첨부된 파일을 다운로드할 수 있습니다.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# 첨부파일 목록
|
|
117
|
+
dooray post file list <project> <number>
|
|
118
|
+
|
|
119
|
+
# 파일 다운로드
|
|
120
|
+
dooray post file download <project> <number> <file-id>
|
|
121
|
+
dooray post file download <project> <number> <file-id> -o ./downloads
|
|
122
|
+
|
|
123
|
+
# 전체 파일 다운로드
|
|
124
|
+
dooray post file download-all <project> <number> -o ./downloads
|
|
125
|
+
|
|
126
|
+
# 파일 업로드
|
|
127
|
+
dooray post file upload <project> <number> ./report.pdf
|
|
128
|
+
|
|
129
|
+
# 파일 삭제
|
|
130
|
+
dooray post file delete <project> <number> <file-id>
|
|
131
|
+
```
|
|
132
|
+
|
|
85
133
|
## 출력 모드
|
|
86
134
|
|
|
87
135
|
| 플래그 | 설명 | 용도 |
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_commander32 = require("commander");
|
|
28
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
29
29
|
|
|
30
30
|
// src/commands/config.ts
|
|
31
31
|
var import_commander = require("commander");
|
|
@@ -52,6 +52,15 @@ var EXIT_AUTH_ERROR = 2;
|
|
|
52
52
|
var EXIT_PARAM_ERROR = 3;
|
|
53
53
|
var EXIT_CONFIG_ERROR = 4;
|
|
54
54
|
|
|
55
|
+
// src/config/types.ts
|
|
56
|
+
var DEFAULTS = {
|
|
57
|
+
baseUrl: "https://api.dooray.com",
|
|
58
|
+
imapHost: "imap.dooray.com",
|
|
59
|
+
imapPort: 993,
|
|
60
|
+
smtpHost: "smtp.dooray.com",
|
|
61
|
+
smtpPort: 465
|
|
62
|
+
};
|
|
63
|
+
|
|
55
64
|
// src/config/store.ts
|
|
56
65
|
var DOORAY_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".dooray");
|
|
57
66
|
var CONFIG_PATH = (0, import_node_path.join)(DOORAY_DIR, "config.json");
|
|
@@ -81,7 +90,7 @@ async function setConfigValue(key, value) {
|
|
|
81
90
|
const config = await getConfig() ?? {
|
|
82
91
|
version: 1,
|
|
83
92
|
apiKey: "",
|
|
84
|
-
baseUrl:
|
|
93
|
+
baseUrl: DEFAULTS.baseUrl
|
|
85
94
|
};
|
|
86
95
|
switch (key) {
|
|
87
96
|
case "api-key":
|
|
@@ -90,10 +99,28 @@ async function setConfigValue(key, value) {
|
|
|
90
99
|
case "base-url":
|
|
91
100
|
config.baseUrl = value;
|
|
92
101
|
break;
|
|
102
|
+
case "imap-host":
|
|
103
|
+
config.imapHost = value;
|
|
104
|
+
break;
|
|
105
|
+
case "imap-port":
|
|
106
|
+
config.imapPort = parseInt(value, 10);
|
|
107
|
+
break;
|
|
108
|
+
case "imap-username":
|
|
109
|
+
config.imapUsername = value;
|
|
110
|
+
break;
|
|
111
|
+
case "imap-password":
|
|
112
|
+
config.imapPassword = value;
|
|
113
|
+
break;
|
|
114
|
+
case "smtp-host":
|
|
115
|
+
config.smtpHost = value;
|
|
116
|
+
break;
|
|
117
|
+
case "smtp-port":
|
|
118
|
+
config.smtpPort = parseInt(value, 10);
|
|
119
|
+
break;
|
|
93
120
|
default:
|
|
94
121
|
throw new DoorayCliError(
|
|
95
122
|
`\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${key}
|
|
96
|
-
\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url`,
|
|
123
|
+
\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url, imap-host, imap-port, imap-username, imap-password, smtp-host, smtp-port`,
|
|
97
124
|
EXIT_CONFIG_ERROR
|
|
98
125
|
);
|
|
99
126
|
}
|
|
@@ -251,6 +278,8 @@ var import_chalk3 = __toESM(require("chalk"));
|
|
|
251
278
|
|
|
252
279
|
// src/api/client.ts
|
|
253
280
|
var import_ky = __toESM(require("ky"));
|
|
281
|
+
var import_promises3 = require("fs/promises");
|
|
282
|
+
var import_node_path3 = require("path");
|
|
254
283
|
function joinIds(ids) {
|
|
255
284
|
return ids && ids.length > 0 ? ids.join(",") : void 0;
|
|
256
285
|
}
|
|
@@ -276,11 +305,15 @@ async function toDoorayCliError(error) {
|
|
|
276
305
|
}
|
|
277
306
|
var DoorayApiClient = class {
|
|
278
307
|
api;
|
|
308
|
+
authHeader;
|
|
309
|
+
baseUrl;
|
|
279
310
|
constructor(apiKey, baseUrl) {
|
|
311
|
+
this.authHeader = `dooray-api ${apiKey}`;
|
|
312
|
+
this.baseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
280
313
|
this.api = import_ky.default.create({
|
|
281
314
|
prefixUrl: baseUrl,
|
|
282
315
|
headers: {
|
|
283
|
-
Authorization:
|
|
316
|
+
Authorization: this.authHeader
|
|
284
317
|
}
|
|
285
318
|
});
|
|
286
319
|
}
|
|
@@ -491,6 +524,97 @@ var DoorayApiClient = class {
|
|
|
491
524
|
return toDoorayCliError(e);
|
|
492
525
|
}
|
|
493
526
|
}
|
|
527
|
+
// ─── Post Files ─────────────────────────────────────
|
|
528
|
+
async getPostFiles(projectId, postId) {
|
|
529
|
+
try {
|
|
530
|
+
return await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/files`).json();
|
|
531
|
+
} catch (e) {
|
|
532
|
+
return toDoorayCliError(e);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async getPostFileMeta(projectId, postId, fileId) {
|
|
536
|
+
try {
|
|
537
|
+
return await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/files/${fileId}`, {
|
|
538
|
+
searchParams: { media: "meta" }
|
|
539
|
+
}).json();
|
|
540
|
+
} catch (e) {
|
|
541
|
+
return toDoorayCliError(e);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async downloadPostFile(projectId, postId, fileId) {
|
|
545
|
+
try {
|
|
546
|
+
const res = await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/files/${fileId}`, {
|
|
547
|
+
searchParams: { media: "raw" },
|
|
548
|
+
redirect: "manual",
|
|
549
|
+
throwHttpErrors: false
|
|
550
|
+
});
|
|
551
|
+
const location = res.headers.get("location");
|
|
552
|
+
if (!location) {
|
|
553
|
+
throw new DoorayCliError("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uB9AC\uB2E4\uC774\uB809\uD2B8 URL\uC744 \uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", EXIT_API_ERROR);
|
|
554
|
+
}
|
|
555
|
+
const fileRes = await fetch(location, {
|
|
556
|
+
headers: { Authorization: this.authHeader }
|
|
557
|
+
});
|
|
558
|
+
if (!fileRes.ok) {
|
|
559
|
+
throw new DoorayCliError(`\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328 (${fileRes.status})`, EXIT_API_ERROR);
|
|
560
|
+
}
|
|
561
|
+
const disposition = fileRes.headers.get("content-disposition");
|
|
562
|
+
let fileName = `file-${fileId}`;
|
|
563
|
+
if (disposition) {
|
|
564
|
+
const match = disposition.match(/filename\*?=(?:UTF-8''|"?)([^";]+)/i);
|
|
565
|
+
if (match) fileName = decodeURIComponent(match[1].replace(/"/g, ""));
|
|
566
|
+
}
|
|
567
|
+
const buffer = await fileRes.arrayBuffer();
|
|
568
|
+
return { buffer, fileName };
|
|
569
|
+
} catch (e) {
|
|
570
|
+
if (e instanceof DoorayCliError) throw e;
|
|
571
|
+
return toDoorayCliError(e);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async uploadPostFile(projectId, postId, filePath) {
|
|
575
|
+
try {
|
|
576
|
+
const fileName = (0, import_node_path3.basename)(filePath);
|
|
577
|
+
const fileBuffer = await (0, import_promises3.readFile)(filePath);
|
|
578
|
+
const formData = new FormData();
|
|
579
|
+
formData.append("file", new Blob([fileBuffer]), fileName);
|
|
580
|
+
const url = `${this.baseUrl}project/v1/projects/${projectId}/posts/${postId}/files`;
|
|
581
|
+
const res = await fetch(url, {
|
|
582
|
+
method: "POST",
|
|
583
|
+
headers: { Authorization: this.authHeader },
|
|
584
|
+
body: formData,
|
|
585
|
+
redirect: "manual"
|
|
586
|
+
});
|
|
587
|
+
if (res.status === 307) {
|
|
588
|
+
const location = res.headers.get("location");
|
|
589
|
+
if (!location) {
|
|
590
|
+
throw new DoorayCliError("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uB9AC\uB2E4\uC774\uB809\uD2B8 URL\uC744 \uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", EXIT_API_ERROR);
|
|
591
|
+
}
|
|
592
|
+
const uploadRes = await fetch(location, {
|
|
593
|
+
method: "POST",
|
|
594
|
+
headers: { Authorization: this.authHeader },
|
|
595
|
+
body: formData
|
|
596
|
+
});
|
|
597
|
+
if (!uploadRes.ok) {
|
|
598
|
+
throw new DoorayCliError(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${uploadRes.status})`, EXIT_API_ERROR);
|
|
599
|
+
}
|
|
600
|
+
return await uploadRes.json();
|
|
601
|
+
}
|
|
602
|
+
if (!res.ok) {
|
|
603
|
+
throw new DoorayCliError(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${res.status})`, EXIT_API_ERROR);
|
|
604
|
+
}
|
|
605
|
+
return await res.json();
|
|
606
|
+
} catch (e) {
|
|
607
|
+
if (e instanceof DoorayCliError) throw e;
|
|
608
|
+
return toDoorayCliError(e);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async deletePostFile(projectId, postId, fileId) {
|
|
612
|
+
try {
|
|
613
|
+
return await this.api.delete(`project/v1/projects/${projectId}/posts/${postId}/files/${fileId}`).json();
|
|
614
|
+
} catch (e) {
|
|
615
|
+
return toDoorayCliError(e);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
494
618
|
};
|
|
495
619
|
|
|
496
620
|
// src/cache/types.ts
|
|
@@ -915,11 +1039,11 @@ var postGetCommand = new import_commander9.Command("get").description("\uC5C5\uB
|
|
|
915
1039
|
|
|
916
1040
|
// src/commands/post/edit.ts
|
|
917
1041
|
var import_commander10 = require("commander");
|
|
918
|
-
var
|
|
1042
|
+
var import_promises5 = __toESM(require("fs/promises"));
|
|
919
1043
|
|
|
920
1044
|
// src/editor/index.ts
|
|
921
1045
|
var import_node_child_process = require("child_process");
|
|
922
|
-
var
|
|
1046
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
923
1047
|
var import_tmp = __toESM(require("tmp"));
|
|
924
1048
|
var import_js_yaml = __toESM(require("js-yaml"));
|
|
925
1049
|
function openInEditor(content) {
|
|
@@ -933,7 +1057,7 @@ function openInEditor(content) {
|
|
|
933
1057
|
const tmpFile = import_tmp.default.fileSync({ prefix: "dooray-", postfix: ".md" });
|
|
934
1058
|
return new Promise(async (resolve, reject) => {
|
|
935
1059
|
try {
|
|
936
|
-
await
|
|
1060
|
+
await import_promises4.default.writeFile(tmpFile.name, content, "utf-8");
|
|
937
1061
|
const child = (0, import_node_child_process.spawn)(editor, [tmpFile.name], {
|
|
938
1062
|
stdio: "inherit"
|
|
939
1063
|
});
|
|
@@ -949,7 +1073,7 @@ function openInEditor(content) {
|
|
|
949
1073
|
EXIT_PARAM_ERROR
|
|
950
1074
|
);
|
|
951
1075
|
}
|
|
952
|
-
const result = await
|
|
1076
|
+
const result = await import_promises4.default.readFile(tmpFile.name, "utf-8");
|
|
953
1077
|
resolve(result);
|
|
954
1078
|
} catch (e) {
|
|
955
1079
|
reject(e);
|
|
@@ -1071,7 +1195,7 @@ async function resolveBody(opts) {
|
|
|
1071
1195
|
}
|
|
1072
1196
|
if (opts.bodyFile) {
|
|
1073
1197
|
if (opts.bodyFile === "-") return readStdin();
|
|
1074
|
-
return
|
|
1198
|
+
return import_promises5.default.readFile(opts.bodyFile, "utf-8");
|
|
1075
1199
|
}
|
|
1076
1200
|
return null;
|
|
1077
1201
|
}
|
|
@@ -1140,13 +1264,13 @@ var postEditCommand = new import_commander10.Command("edit").description("\uC5C5
|
|
|
1140
1264
|
|
|
1141
1265
|
// src/commands/post/create.ts
|
|
1142
1266
|
var import_commander11 = require("commander");
|
|
1143
|
-
var
|
|
1267
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
1144
1268
|
async function readBody(opts) {
|
|
1145
1269
|
if (opts.bodyFile) {
|
|
1146
1270
|
if (opts.bodyFile === "-") {
|
|
1147
1271
|
return readStdin2();
|
|
1148
1272
|
}
|
|
1149
|
-
return
|
|
1273
|
+
return import_promises6.default.readFile(opts.bodyFile, "utf-8");
|
|
1150
1274
|
}
|
|
1151
1275
|
if (opts.body === "-") {
|
|
1152
1276
|
return readStdin2();
|
|
@@ -1249,7 +1373,7 @@ var commentListCommand = new import_commander14.Command("list").description("\uB
|
|
|
1249
1373
|
|
|
1250
1374
|
// src/commands/post/comment/add.ts
|
|
1251
1375
|
var import_commander15 = require("commander");
|
|
1252
|
-
var
|
|
1376
|
+
var import_promises7 = __toESM(require("fs/promises"));
|
|
1253
1377
|
async function readStdin3() {
|
|
1254
1378
|
if (process.stdin.isTTY) {
|
|
1255
1379
|
throw new DoorayCliError(
|
|
@@ -1270,7 +1394,7 @@ async function resolveBody2(opts) {
|
|
|
1270
1394
|
}
|
|
1271
1395
|
if (opts.bodyFile) {
|
|
1272
1396
|
if (opts.bodyFile === "-") return readStdin3();
|
|
1273
|
-
return
|
|
1397
|
+
return import_promises7.default.readFile(opts.bodyFile, "utf-8");
|
|
1274
1398
|
}
|
|
1275
1399
|
return null;
|
|
1276
1400
|
}
|
|
@@ -1305,7 +1429,7 @@ var commentAddCommand = new import_commander15.Command("add").description("\uB31
|
|
|
1305
1429
|
|
|
1306
1430
|
// src/commands/post/comment/edit.ts
|
|
1307
1431
|
var import_commander16 = require("commander");
|
|
1308
|
-
var
|
|
1432
|
+
var import_promises8 = __toESM(require("fs/promises"));
|
|
1309
1433
|
async function readStdin4() {
|
|
1310
1434
|
if (process.stdin.isTTY) {
|
|
1311
1435
|
throw new DoorayCliError(
|
|
@@ -1326,7 +1450,7 @@ async function resolveBody3(opts) {
|
|
|
1326
1450
|
}
|
|
1327
1451
|
if (opts.bodyFile) {
|
|
1328
1452
|
if (opts.bodyFile === "-") return readStdin4();
|
|
1329
|
-
return
|
|
1453
|
+
return import_promises8.default.readFile(opts.bodyFile, "utf-8");
|
|
1330
1454
|
}
|
|
1331
1455
|
return null;
|
|
1332
1456
|
}
|
|
@@ -1376,8 +1500,125 @@ var commentDeleteCommand = new import_commander17.Command("delete").description(
|
|
|
1376
1500
|
`);
|
|
1377
1501
|
});
|
|
1378
1502
|
|
|
1379
|
-
// src/commands/
|
|
1503
|
+
// src/commands/post/file/list.ts
|
|
1380
1504
|
var import_commander18 = require("commander");
|
|
1505
|
+
function formatSize(bytes) {
|
|
1506
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
1507
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
1508
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
1509
|
+
}
|
|
1510
|
+
var fileListCommand = new import_commander18.Command("list").description("\uC5C5\uBB34 \uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").action(async (project, postNumberStr) => {
|
|
1511
|
+
const globalOpts = fileListCommand.optsWithGlobals();
|
|
1512
|
+
const config = await getConfigOrThrow();
|
|
1513
|
+
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
1514
|
+
startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
1515
|
+
const projectId = await resolveProject(client, project);
|
|
1516
|
+
const postId = await resolvePost(client, projectId, Number(postNumberStr));
|
|
1517
|
+
const res = await client.getPostFiles(projectId, postId);
|
|
1518
|
+
stopSpinner(true, `\uCCA8\uBD80\uD30C\uC77C ${res.result.length}\uAC1C`);
|
|
1519
|
+
output(globalOpts, {
|
|
1520
|
+
headers: ["ID", "\uD30C\uC77C\uBA85", "\uD06C\uAE30", "MIME", "\uC0DD\uC131\uC77C"],
|
|
1521
|
+
rows: res.result.map((f) => [
|
|
1522
|
+
f.id,
|
|
1523
|
+
f.name,
|
|
1524
|
+
formatSize(f.size),
|
|
1525
|
+
f.mimeType,
|
|
1526
|
+
f.createdAt
|
|
1527
|
+
]),
|
|
1528
|
+
raw: res.result,
|
|
1529
|
+
ids: res.result.map((f) => f.id)
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
// src/commands/post/file/download.ts
|
|
1534
|
+
var import_commander19 = require("commander");
|
|
1535
|
+
var import_promises9 = require("fs/promises");
|
|
1536
|
+
var import_node_path4 = require("path");
|
|
1537
|
+
var fileDownloadCommand = new import_commander19.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-id>", "\uD30C\uC77C ID").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, fileId, opts) => {
|
|
1538
|
+
const config = await getConfigOrThrow();
|
|
1539
|
+
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
1540
|
+
startSpinner("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
|
|
1541
|
+
const projectId = await resolveProject(client, project);
|
|
1542
|
+
const postId = await resolvePost(client, projectId, Number(postNumberStr));
|
|
1543
|
+
const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
|
|
1544
|
+
const outputPath = (0, import_node_path4.join)(opts.output, fileName);
|
|
1545
|
+
await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
|
|
1546
|
+
stopSpinner(true, "\uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC");
|
|
1547
|
+
process.stdout.write(`${outputPath}
|
|
1548
|
+
`);
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
// src/commands/post/file/download-all.ts
|
|
1552
|
+
var import_commander20 = require("commander");
|
|
1553
|
+
var import_promises10 = require("fs/promises");
|
|
1554
|
+
var import_node_path5 = require("path");
|
|
1555
|
+
var fileDownloadAllCommand = new import_commander20.Command("download-all").description("\uC5C5\uBB34\uC758 \uBAA8\uB4E0 \uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
|
|
1556
|
+
const config = await getConfigOrThrow();
|
|
1557
|
+
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
1558
|
+
const spinner = startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
1559
|
+
const projectId = await resolveProject(client, project);
|
|
1560
|
+
const postId = await resolvePost(client, projectId, Number(postNumberStr));
|
|
1561
|
+
const res = await client.getPostFiles(projectId, postId);
|
|
1562
|
+
if (res.result.length === 0) {
|
|
1563
|
+
stopSpinner(true, "\uCCA8\uBD80\uD30C\uC77C \uC5C6\uC74C");
|
|
1564
|
+
process.stdout.write("\uCCA8\uBD80\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
await (0, import_promises10.mkdir)(opts.output, { recursive: true });
|
|
1568
|
+
const downloaded = [];
|
|
1569
|
+
for (const file of res.result) {
|
|
1570
|
+
spinner.text = `\uB2E4\uC6B4\uB85C\uB4DC \uC911: ${file.name} (${downloaded.length + 1}/${res.result.length})`;
|
|
1571
|
+
const { buffer, fileName } = await client.downloadPostFile(projectId, postId, file.id);
|
|
1572
|
+
const outputPath = (0, import_node_path5.join)(opts.output, fileName);
|
|
1573
|
+
await (0, import_promises10.writeFile)(outputPath, Buffer.from(buffer));
|
|
1574
|
+
downloaded.push(outputPath);
|
|
1575
|
+
}
|
|
1576
|
+
stopSpinner(true, `${downloaded.length}\uAC1C \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC`);
|
|
1577
|
+
for (const path of downloaded) {
|
|
1578
|
+
process.stdout.write(`${path}
|
|
1579
|
+
`);
|
|
1580
|
+
}
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
// src/commands/post/file/upload.ts
|
|
1584
|
+
var import_commander21 = require("commander");
|
|
1585
|
+
var import_node_path6 = require("path");
|
|
1586
|
+
var fileUploadCommand = new import_commander21.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C").action(async (project, postNumberStr, filePath) => {
|
|
1587
|
+
const globalOpts = fileUploadCommand.optsWithGlobals();
|
|
1588
|
+
const config = await getConfigOrThrow();
|
|
1589
|
+
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
1590
|
+
startSpinner("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911...");
|
|
1591
|
+
const projectId = await resolveProject(client, project);
|
|
1592
|
+
const postId = await resolvePost(client, projectId, Number(postNumberStr));
|
|
1593
|
+
const res = await client.uploadPostFile(projectId, postId, filePath);
|
|
1594
|
+
stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
|
|
1595
|
+
if (globalOpts.json) {
|
|
1596
|
+
printJson(res.result);
|
|
1597
|
+
} else if (globalOpts.quiet) {
|
|
1598
|
+
process.stdout.write(`${res.result.id}
|
|
1599
|
+
`);
|
|
1600
|
+
} else {
|
|
1601
|
+
process.stdout.write(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC: ${(0, import_node_path6.basename)(filePath)} (ID: ${res.result.id})
|
|
1602
|
+
`);
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
// src/commands/post/file/delete.ts
|
|
1607
|
+
var import_commander22 = require("commander");
|
|
1608
|
+
var fileDeleteCommand = new import_commander22.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-id>", "\uD30C\uC77C ID").action(async (project, postNumberStr, fileId) => {
|
|
1609
|
+
const config = await getConfigOrThrow();
|
|
1610
|
+
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
1611
|
+
startSpinner("\uD30C\uC77C \uC0AD\uC81C \uC911...");
|
|
1612
|
+
const projectId = await resolveProject(client, project);
|
|
1613
|
+
const postId = await resolvePost(client, projectId, Number(postNumberStr));
|
|
1614
|
+
await client.deletePostFile(projectId, postId, fileId);
|
|
1615
|
+
stopSpinner(true, "\uC0AD\uC81C \uC644\uB8CC");
|
|
1616
|
+
process.stdout.write(`\uD30C\uC77C(${fileId})\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
1617
|
+
`);
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
// src/commands/wiki/list.ts
|
|
1621
|
+
var import_commander23 = require("commander");
|
|
1381
1622
|
|
|
1382
1623
|
// src/formatters/wiki.ts
|
|
1383
1624
|
function formatWikiList(wikis, opts) {
|
|
@@ -1420,7 +1661,7 @@ function formatWikiPageDetail(page, opts) {
|
|
|
1420
1661
|
}
|
|
1421
1662
|
|
|
1422
1663
|
// src/commands/wiki/list.ts
|
|
1423
|
-
var wikiListCommand = new
|
|
1664
|
+
var wikiListCommand = new import_commander23.Command("list").description("\uC704\uD0A4 \uBAA9\uB85D \uC870\uD68C").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (opts) => {
|
|
1424
1665
|
const globalOpts = wikiListCommand.optsWithGlobals();
|
|
1425
1666
|
const config = await getConfigOrThrow();
|
|
1426
1667
|
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
@@ -1434,7 +1675,7 @@ var wikiListCommand = new import_commander18.Command("list").description("\uC704
|
|
|
1434
1675
|
});
|
|
1435
1676
|
|
|
1436
1677
|
// src/commands/wiki/pages.ts
|
|
1437
|
-
var
|
|
1678
|
+
var import_commander24 = require("commander");
|
|
1438
1679
|
|
|
1439
1680
|
// src/resolvers/wiki.ts
|
|
1440
1681
|
async function resolveWiki(client, projectCode) {
|
|
@@ -1453,7 +1694,7 @@ async function resolveWiki(client, projectCode) {
|
|
|
1453
1694
|
}
|
|
1454
1695
|
|
|
1455
1696
|
// src/commands/wiki/pages.ts
|
|
1456
|
-
var wikiPagesCommand = new
|
|
1697
|
+
var wikiPagesCommand = new import_commander24.Command("pages").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").action(async (project, opts) => {
|
|
1457
1698
|
const globalOpts = wikiPagesCommand.optsWithGlobals();
|
|
1458
1699
|
const config = await getConfigOrThrow();
|
|
1459
1700
|
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
@@ -1465,8 +1706,8 @@ var wikiPagesCommand = new import_commander19.Command("pages").description("\uC7
|
|
|
1465
1706
|
});
|
|
1466
1707
|
|
|
1467
1708
|
// src/commands/wiki/page-get.ts
|
|
1468
|
-
var
|
|
1469
|
-
var wikiPageGetCommand = new
|
|
1709
|
+
var import_commander25 = require("commander");
|
|
1710
|
+
var wikiPageGetCommand = new import_commander25.Command("get").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0C1\uC138 \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
|
|
1470
1711
|
const globalOpts = wikiPageGetCommand.optsWithGlobals();
|
|
1471
1712
|
const config = await getConfigOrThrow();
|
|
1472
1713
|
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
@@ -1478,14 +1719,14 @@ var wikiPageGetCommand = new import_commander20.Command("get").description("\uC7
|
|
|
1478
1719
|
});
|
|
1479
1720
|
|
|
1480
1721
|
// src/commands/wiki/page-create.ts
|
|
1481
|
-
var
|
|
1482
|
-
var
|
|
1722
|
+
var import_commander26 = require("commander");
|
|
1723
|
+
var import_promises11 = __toESM(require("fs/promises"));
|
|
1483
1724
|
async function readBody2(opts) {
|
|
1484
1725
|
if (opts.bodyFile) {
|
|
1485
1726
|
if (opts.bodyFile === "-") {
|
|
1486
1727
|
return readStdin5();
|
|
1487
1728
|
}
|
|
1488
|
-
return
|
|
1729
|
+
return import_promises11.default.readFile(opts.bodyFile, "utf-8");
|
|
1489
1730
|
}
|
|
1490
1731
|
if (opts.body === "-") {
|
|
1491
1732
|
return readStdin5();
|
|
@@ -1505,7 +1746,7 @@ async function readStdin5() {
|
|
|
1505
1746
|
}
|
|
1506
1747
|
return Buffer.concat(chunks).toString("utf-8");
|
|
1507
1748
|
}
|
|
1508
|
-
var wikiPageCreateCommand = new
|
|
1749
|
+
var wikiPageCreateCommand = new import_commander26.Command("create").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").requiredOption("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, opts) => {
|
|
1509
1750
|
const globalOpts = wikiPageCreateCommand.optsWithGlobals();
|
|
1510
1751
|
const config = await getConfigOrThrow();
|
|
1511
1752
|
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
@@ -1529,8 +1770,8 @@ var wikiPageCreateCommand = new import_commander21.Command("create").description
|
|
|
1529
1770
|
});
|
|
1530
1771
|
|
|
1531
1772
|
// src/commands/wiki/page-edit.ts
|
|
1532
|
-
var
|
|
1533
|
-
var wikiPageEditCommand = new
|
|
1773
|
+
var import_commander27 = require("commander");
|
|
1774
|
+
var wikiPageEditCommand = new import_commander27.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 ($EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
|
|
1534
1775
|
const config = await getConfigOrThrow();
|
|
1535
1776
|
const client = new DoorayApiClient(config.apiKey, config.baseUrl);
|
|
1536
1777
|
startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC870\uD68C \uC911...");
|
|
@@ -1555,20 +1796,347 @@ var wikiPageEditCommand = new import_commander22.Command("edit").description("\u
|
|
|
1555
1796
|
`);
|
|
1556
1797
|
});
|
|
1557
1798
|
|
|
1799
|
+
// src/commands/mail/list.ts
|
|
1800
|
+
var import_commander28 = require("commander");
|
|
1801
|
+
|
|
1802
|
+
// src/api/imapClient.ts
|
|
1803
|
+
var import_imapflow = require("imapflow");
|
|
1804
|
+
var import_mailparser = require("mailparser");
|
|
1805
|
+
function getImapConfigOrThrow(config) {
|
|
1806
|
+
if (!config.imapUsername || !config.imapPassword) {
|
|
1807
|
+
throw new DoorayCliError(
|
|
1808
|
+
"IMAP \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uC124\uC815\uC744 \uC9C4\uD589\uD558\uC138\uC694:\n dooray config set imap-username <YOUR_EMAIL>\n dooray config set imap-password <YOUR_IMAP_PASSWORD>",
|
|
1809
|
+
EXIT_CONFIG_ERROR
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
return {
|
|
1813
|
+
host: config.imapHost ?? DEFAULTS.imapHost,
|
|
1814
|
+
port: config.imapPort ?? DEFAULTS.imapPort,
|
|
1815
|
+
username: config.imapUsername,
|
|
1816
|
+
password: config.imapPassword
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
function createClient(config) {
|
|
1820
|
+
const imap = getImapConfigOrThrow(config);
|
|
1821
|
+
return new import_imapflow.ImapFlow({
|
|
1822
|
+
host: imap.host,
|
|
1823
|
+
port: imap.port,
|
|
1824
|
+
secure: true,
|
|
1825
|
+
auth: { user: imap.username, pass: imap.password },
|
|
1826
|
+
logger: false
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
async function listMails(config, opts) {
|
|
1830
|
+
const client = createClient(config);
|
|
1831
|
+
const limit = opts.limit ?? 20;
|
|
1832
|
+
try {
|
|
1833
|
+
await client.connect();
|
|
1834
|
+
const lock = await client.getMailboxLock("INBOX");
|
|
1835
|
+
try {
|
|
1836
|
+
const query = {};
|
|
1837
|
+
if (opts.unread) query.seen = false;
|
|
1838
|
+
if (opts.search) query.subject = opts.search;
|
|
1839
|
+
if (Object.keys(query).length === 0) query.all = true;
|
|
1840
|
+
const uids = await client.search(query, { uid: true });
|
|
1841
|
+
if (uids.length === 0) return [];
|
|
1842
|
+
const sorted = uids.sort((a, b) => b - a).slice(0, limit);
|
|
1843
|
+
const uidSet = sorted.join(",");
|
|
1844
|
+
const messages = [];
|
|
1845
|
+
for await (const msg of client.fetch(uidSet, {
|
|
1846
|
+
uid: true,
|
|
1847
|
+
flags: true,
|
|
1848
|
+
envelope: true
|
|
1849
|
+
}, { uid: true })) {
|
|
1850
|
+
messages.push({
|
|
1851
|
+
uid: msg.uid,
|
|
1852
|
+
subject: msg.envelope.subject ?? "(\uC81C\uBAA9 \uC5C6\uC74C)",
|
|
1853
|
+
from: msg.envelope.from?.[0] ? `${msg.envelope.from[0].name || ""} <${msg.envelope.from[0].address || ""}>` : "(unknown)",
|
|
1854
|
+
to: (msg.envelope.to ?? []).map(
|
|
1855
|
+
(t) => `${t.name || ""} <${t.address || ""}>`
|
|
1856
|
+
),
|
|
1857
|
+
date: msg.envelope.date ?? null,
|
|
1858
|
+
isRead: msg.flags.has("\\Seen")
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
messages.sort((a, b) => b.uid - a.uid);
|
|
1862
|
+
return messages;
|
|
1863
|
+
} finally {
|
|
1864
|
+
lock.release();
|
|
1865
|
+
}
|
|
1866
|
+
} finally {
|
|
1867
|
+
await client.logout();
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
async function getMail(config, uid) {
|
|
1871
|
+
const client = createClient(config);
|
|
1872
|
+
try {
|
|
1873
|
+
await client.connect();
|
|
1874
|
+
const lock = await client.getMailboxLock("INBOX");
|
|
1875
|
+
try {
|
|
1876
|
+
const msg = await client.fetchOne(String(uid), {
|
|
1877
|
+
uid: true,
|
|
1878
|
+
flags: true,
|
|
1879
|
+
envelope: true,
|
|
1880
|
+
source: true
|
|
1881
|
+
}, { uid: true });
|
|
1882
|
+
if (!msg) {
|
|
1883
|
+
throw new DoorayCliError(`\uBA54\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: UID ${uid}`, 1);
|
|
1884
|
+
}
|
|
1885
|
+
const parsed = await (0, import_mailparser.simpleParser)(msg.source);
|
|
1886
|
+
const body = parsed.text ?? parsed.html ?? "(\uBCF8\uBB38 \uC5C6\uC74C)";
|
|
1887
|
+
return {
|
|
1888
|
+
uid: msg.uid,
|
|
1889
|
+
subject: msg.envelope.subject ?? "(\uC81C\uBAA9 \uC5C6\uC74C)",
|
|
1890
|
+
from: msg.envelope.from?.[0] ? `${msg.envelope.from[0].name || ""} <${msg.envelope.from[0].address || ""}>` : "(unknown)",
|
|
1891
|
+
to: (msg.envelope.to ?? []).map(
|
|
1892
|
+
(t) => `${t.name || ""} <${t.address || ""}>`
|
|
1893
|
+
),
|
|
1894
|
+
date: msg.envelope.date ?? null,
|
|
1895
|
+
isRead: msg.flags.has("\\Seen"),
|
|
1896
|
+
body
|
|
1897
|
+
};
|
|
1898
|
+
} finally {
|
|
1899
|
+
lock.release();
|
|
1900
|
+
}
|
|
1901
|
+
} finally {
|
|
1902
|
+
await client.logout();
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// src/commands/mail/list.ts
|
|
1907
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
1908
|
+
var mailListCommand = new import_commander28.Command("list").description("\uBA54\uC77C \uBAA9\uB85D \uC870\uD68C").option("--unread", "\uC548\uC77D\uC740 \uBA54\uC77C\uB9CC").option("--search <keyword>", "\uC81C\uBAA9 \uAC80\uC0C9").option("--size <number>", "\uC870\uD68C \uAC1C\uC218", "20").action(async (opts) => {
|
|
1909
|
+
const globalOpts = mailListCommand.optsWithGlobals();
|
|
1910
|
+
const config = await getConfigOrThrow();
|
|
1911
|
+
startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
|
|
1912
|
+
const mails = await listMails(config, {
|
|
1913
|
+
unread: opts.unread,
|
|
1914
|
+
search: opts.search,
|
|
1915
|
+
limit: Number(opts.size)
|
|
1916
|
+
});
|
|
1917
|
+
stopSpinner(true, `\uBA54\uC77C ${mails.length}\uAC74 \uC870\uD68C \uC644\uB8CC`);
|
|
1918
|
+
output(globalOpts, {
|
|
1919
|
+
headers: ["UID", "\uC77D\uC74C", "\uB0A0\uC9DC", "\uBCF4\uB0B8\uC0AC\uB78C", "\uC81C\uBAA9"],
|
|
1920
|
+
rows: mails.map((m) => [
|
|
1921
|
+
String(m.uid),
|
|
1922
|
+
m.isRead ? "\u2713" : import_chalk4.default.bold("\u25CF"),
|
|
1923
|
+
m.date ? m.date.toLocaleDateString("ko-KR") : "",
|
|
1924
|
+
m.from.replace(/<.*>/, "").trim() || m.from,
|
|
1925
|
+
m.subject
|
|
1926
|
+
]),
|
|
1927
|
+
raw: mails.map((m) => ({
|
|
1928
|
+
uid: m.uid,
|
|
1929
|
+
subject: m.subject,
|
|
1930
|
+
from: m.from,
|
|
1931
|
+
to: m.to,
|
|
1932
|
+
date: m.date?.toISOString() ?? null,
|
|
1933
|
+
isRead: m.isRead
|
|
1934
|
+
})),
|
|
1935
|
+
ids: mails.map((m) => String(m.uid))
|
|
1936
|
+
});
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
// src/commands/mail/get.ts
|
|
1940
|
+
var import_commander29 = require("commander");
|
|
1941
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1942
|
+
var mailGetCommand = new import_commander29.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
|
|
1943
|
+
const globalOpts = mailGetCommand.optsWithGlobals();
|
|
1944
|
+
const config = await getConfigOrThrow();
|
|
1945
|
+
startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
|
|
1946
|
+
const mail = await getMail(config, Number(uid));
|
|
1947
|
+
stopSpinner(true, "\uBA54\uC77C \uC870\uD68C \uC644\uB8CC");
|
|
1948
|
+
if (globalOpts.json) {
|
|
1949
|
+
printJson({
|
|
1950
|
+
uid: mail.uid,
|
|
1951
|
+
subject: mail.subject,
|
|
1952
|
+
from: mail.from,
|
|
1953
|
+
to: mail.to,
|
|
1954
|
+
date: mail.date?.toISOString() ?? null,
|
|
1955
|
+
isRead: mail.isRead,
|
|
1956
|
+
body: mail.body
|
|
1957
|
+
});
|
|
1958
|
+
} else {
|
|
1959
|
+
process.stdout.write(
|
|
1960
|
+
`${import_chalk5.default.bold("\uC81C\uBAA9:")} ${mail.subject}
|
|
1961
|
+
${import_chalk5.default.bold("\uBCF4\uB0B8\uC0AC\uB78C:")} ${mail.from}
|
|
1962
|
+
${import_chalk5.default.bold("\uBC1B\uB294\uC0AC\uB78C:")} ${mail.to.join(", ")}
|
|
1963
|
+
${import_chalk5.default.bold("\uB0A0\uC9DC:")} ${mail.date?.toLocaleString("ko-KR") ?? ""}
|
|
1964
|
+
${import_chalk5.default.bold("\uC77D\uC74C:")} ${mail.isRead ? "\uC608" : "\uC544\uB2C8\uC624"}
|
|
1965
|
+
|
|
1966
|
+
${mail.body}
|
|
1967
|
+
`
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
// src/commands/mail/send.ts
|
|
1973
|
+
var import_commander30 = require("commander");
|
|
1974
|
+
var import_promises12 = require("fs/promises");
|
|
1975
|
+
|
|
1976
|
+
// src/api/smtpClient.ts
|
|
1977
|
+
var import_nodemailer = __toESM(require("nodemailer"));
|
|
1978
|
+
function getSmtpConfigOrThrow(config) {
|
|
1979
|
+
if (!config.imapUsername || !config.imapPassword) {
|
|
1980
|
+
throw new DoorayCliError(
|
|
1981
|
+
"\uBA54\uC77C \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uC124\uC815\uC744 \uC9C4\uD589\uD558\uC138\uC694:\n dooray config set imap-username <YOUR_EMAIL>\n dooray config set imap-password <YOUR_IMAP_PASSWORD>",
|
|
1982
|
+
EXIT_CONFIG_ERROR
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
return {
|
|
1986
|
+
host: config.smtpHost ?? DEFAULTS.smtpHost,
|
|
1987
|
+
port: config.smtpPort ?? DEFAULTS.smtpPort,
|
|
1988
|
+
username: config.imapUsername,
|
|
1989
|
+
password: config.imapPassword
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
async function sendMail(config, opts) {
|
|
1993
|
+
const smtp = getSmtpConfigOrThrow(config);
|
|
1994
|
+
const transporter = import_nodemailer.default.createTransport({
|
|
1995
|
+
host: smtp.host,
|
|
1996
|
+
port: smtp.port,
|
|
1997
|
+
secure: true,
|
|
1998
|
+
auth: { user: smtp.username, pass: smtp.password }
|
|
1999
|
+
});
|
|
2000
|
+
const mailOptions = {
|
|
2001
|
+
from: smtp.username,
|
|
2002
|
+
to: opts.to.join(", "),
|
|
2003
|
+
subject: opts.subject,
|
|
2004
|
+
inReplyTo: opts.inReplyTo,
|
|
2005
|
+
references: opts.references
|
|
2006
|
+
};
|
|
2007
|
+
if (opts.cc?.length) mailOptions.cc = opts.cc.join(", ");
|
|
2008
|
+
if (opts.bcc?.length) mailOptions.bcc = opts.bcc.join(", ");
|
|
2009
|
+
if (opts.html) {
|
|
2010
|
+
mailOptions.html = opts.body;
|
|
2011
|
+
} else {
|
|
2012
|
+
mailOptions.text = opts.body;
|
|
2013
|
+
}
|
|
2014
|
+
const info = await transporter.sendMail(mailOptions);
|
|
2015
|
+
return {
|
|
2016
|
+
messageId: info.messageId,
|
|
2017
|
+
accepted: info.accepted ?? [],
|
|
2018
|
+
rejected: info.rejected ?? []
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// src/commands/mail/send.ts
|
|
2023
|
+
var mailSendCommand = new import_commander30.Command("send").description("\uBA54\uC77C \uBC1C\uC1A1").requiredOption("--to <addresses...>", "\uBC1B\uB294\uC0AC\uB78C \uC774\uBA54\uC77C (\uBCF5\uC218 \uAC00\uB2A5)").requiredOption("--subject <title>", "\uC81C\uBAA9").option("--body <text>", "\uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--bcc <addresses...>", "\uC228\uC740\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (opts) => {
|
|
2024
|
+
const globalOpts = mailSendCommand.optsWithGlobals();
|
|
2025
|
+
const config = await getConfigOrThrow();
|
|
2026
|
+
let body = opts.body ?? "";
|
|
2027
|
+
if (opts.bodyFile) {
|
|
2028
|
+
body = await (0, import_promises12.readFile)(opts.bodyFile, "utf-8");
|
|
2029
|
+
}
|
|
2030
|
+
if (!body) {
|
|
2031
|
+
process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
|
|
2032
|
+
process.exit(3);
|
|
2033
|
+
}
|
|
2034
|
+
startSpinner("\uBA54\uC77C \uBC1C\uC1A1 \uC911...");
|
|
2035
|
+
const result = await sendMail(config, {
|
|
2036
|
+
to: opts.to,
|
|
2037
|
+
cc: opts.cc,
|
|
2038
|
+
bcc: opts.bcc,
|
|
2039
|
+
subject: opts.subject,
|
|
2040
|
+
body,
|
|
2041
|
+
html: opts.html
|
|
2042
|
+
});
|
|
2043
|
+
stopSpinner(true, "\uBA54\uC77C \uBC1C\uC1A1 \uC644\uB8CC");
|
|
2044
|
+
if (globalOpts.json) {
|
|
2045
|
+
printJson(result);
|
|
2046
|
+
} else {
|
|
2047
|
+
process.stdout.write(
|
|
2048
|
+
`\uBA54\uC77C \uBC1C\uC1A1 \uC644\uB8CC
|
|
2049
|
+
Message-ID: ${result.messageId}
|
|
2050
|
+
\uC218\uC2E0: ${result.accepted.join(", ")}
|
|
2051
|
+
` + (result.rejected.length ? ` \uAC70\uBD80: ${result.rejected.join(", ")}
|
|
2052
|
+
` : "")
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
// src/commands/mail/reply.ts
|
|
2058
|
+
var import_commander31 = require("commander");
|
|
2059
|
+
var import_promises13 = require("fs/promises");
|
|
2060
|
+
var import_imapflow2 = require("imapflow");
|
|
2061
|
+
async function getMessageId(config, uid) {
|
|
2062
|
+
const imap = getImapConfigOrThrow(config);
|
|
2063
|
+
const client = new import_imapflow2.ImapFlow({
|
|
2064
|
+
host: imap.host,
|
|
2065
|
+
port: imap.port,
|
|
2066
|
+
secure: true,
|
|
2067
|
+
auth: { user: imap.username, pass: imap.password },
|
|
2068
|
+
logger: false
|
|
2069
|
+
});
|
|
2070
|
+
try {
|
|
2071
|
+
await client.connect();
|
|
2072
|
+
const lock = await client.getMailboxLock("INBOX");
|
|
2073
|
+
try {
|
|
2074
|
+
const msg = await client.fetchOne(String(uid), {
|
|
2075
|
+
uid: true,
|
|
2076
|
+
envelope: true
|
|
2077
|
+
}, { uid: true });
|
|
2078
|
+
return msg?.envelope.messageId ?? null;
|
|
2079
|
+
} finally {
|
|
2080
|
+
lock.release();
|
|
2081
|
+
}
|
|
2082
|
+
} finally {
|
|
2083
|
+
await client.logout();
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
var mailReplyCommand = new import_commander31.Command("reply").description("\uBA54\uC77C \uB2F5\uC7A5").argument("<uid>", "\uC6D0\uBCF8 \uBA54\uC77C UID").option("--body <text>", "\uB2F5\uC7A5 \uBCF8\uBB38").option("--body-file <path>", "\uB2F5\uC7A5 \uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (uid, opts) => {
|
|
2087
|
+
const globalOpts = mailReplyCommand.optsWithGlobals();
|
|
2088
|
+
const config = await getConfigOrThrow();
|
|
2089
|
+
let body = opts.body ?? "";
|
|
2090
|
+
if (opts.bodyFile) {
|
|
2091
|
+
body = await (0, import_promises13.readFile)(opts.bodyFile, "utf-8");
|
|
2092
|
+
}
|
|
2093
|
+
if (!body) {
|
|
2094
|
+
process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
|
|
2095
|
+
process.exit(3);
|
|
2096
|
+
}
|
|
2097
|
+
startSpinner("\uC6D0\uBCF8 \uBA54\uC77C \uC870\uD68C \uC911...");
|
|
2098
|
+
const original = await getMail(config, Number(uid));
|
|
2099
|
+
const messageId = await getMessageId(config, Number(uid));
|
|
2100
|
+
const fromMatch = original.from.match(/<(.+?)>/);
|
|
2101
|
+
const replyTo = fromMatch ? fromMatch[1] : original.from;
|
|
2102
|
+
stopSpinner(true, "\uC6D0\uBCF8 \uBA54\uC77C \uC870\uD68C \uC644\uB8CC");
|
|
2103
|
+
startSpinner("\uB2F5\uC7A5 \uBC1C\uC1A1 \uC911...");
|
|
2104
|
+
const result = await sendMail(config, {
|
|
2105
|
+
to: [replyTo],
|
|
2106
|
+
cc: opts.cc,
|
|
2107
|
+
subject: original.subject.startsWith("Re: ") ? original.subject : `Re: ${original.subject}`,
|
|
2108
|
+
body,
|
|
2109
|
+
html: opts.html,
|
|
2110
|
+
inReplyTo: messageId ?? void 0,
|
|
2111
|
+
references: messageId ?? void 0
|
|
2112
|
+
});
|
|
2113
|
+
stopSpinner(true, "\uB2F5\uC7A5 \uBC1C\uC1A1 \uC644\uB8CC");
|
|
2114
|
+
if (globalOpts.json) {
|
|
2115
|
+
printJson(result);
|
|
2116
|
+
} else {
|
|
2117
|
+
process.stdout.write(
|
|
2118
|
+
`\uB2F5\uC7A5 \uBC1C\uC1A1 \uC644\uB8CC
|
|
2119
|
+
To: ${replyTo}
|
|
2120
|
+
Message-ID: ${result.messageId}
|
|
2121
|
+
`
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
|
|
1558
2126
|
// src/index.ts
|
|
1559
|
-
var program = new
|
|
1560
|
-
program.name("dooray").description("Dooray REST API CLI").version("0.
|
|
2127
|
+
var program = new import_commander32.Command();
|
|
2128
|
+
program.name("dooray").description("Dooray REST API CLI").version("0.3.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
|
|
1561
2129
|
program.hook("preAction", () => {
|
|
1562
2130
|
const opts = program.opts();
|
|
1563
2131
|
if (opts.color === false || process.env.NO_COLOR) {
|
|
1564
|
-
|
|
2132
|
+
import_chalk6.default.level = 0;
|
|
1565
2133
|
}
|
|
1566
2134
|
});
|
|
1567
|
-
var projectCommand = new
|
|
2135
|
+
var projectCommand = new import_commander32.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
|
|
1568
2136
|
projectCommand.addCommand(projectListCommand);
|
|
1569
2137
|
projectCommand.addCommand(projectMembersCommand);
|
|
1570
2138
|
projectCommand.addCommand(projectWorkflowsCommand);
|
|
1571
|
-
var postCommand = new
|
|
2139
|
+
var postCommand = new import_commander32.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
|
|
1572
2140
|
postCommand.addCommand(postListCommand);
|
|
1573
2141
|
postCommand.addCommand(postSearchCommand);
|
|
1574
2142
|
postCommand.addCommand(postGetCommand);
|
|
@@ -1576,31 +2144,44 @@ postCommand.addCommand(postEditCommand);
|
|
|
1576
2144
|
postCommand.addCommand(postCreateCommand);
|
|
1577
2145
|
postCommand.addCommand(postDoneCommand);
|
|
1578
2146
|
postCommand.addCommand(postWorkflowCommand);
|
|
1579
|
-
var commentCommand = new
|
|
2147
|
+
var commentCommand = new import_commander32.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
|
|
1580
2148
|
commentCommand.addCommand(commentListCommand);
|
|
1581
2149
|
commentCommand.addCommand(commentAddCommand);
|
|
1582
2150
|
commentCommand.addCommand(commentEditCommand);
|
|
1583
2151
|
commentCommand.addCommand(commentDeleteCommand);
|
|
1584
2152
|
postCommand.addCommand(commentCommand);
|
|
1585
|
-
var
|
|
2153
|
+
var fileCommand = new import_commander32.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
|
|
2154
|
+
fileCommand.addCommand(fileListCommand);
|
|
2155
|
+
fileCommand.addCommand(fileDownloadCommand);
|
|
2156
|
+
fileCommand.addCommand(fileDownloadAllCommand);
|
|
2157
|
+
fileCommand.addCommand(fileUploadCommand);
|
|
2158
|
+
fileCommand.addCommand(fileDeleteCommand);
|
|
2159
|
+
postCommand.addCommand(fileCommand);
|
|
2160
|
+
var wikiCommand = new import_commander32.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
|
|
1586
2161
|
wikiCommand.addCommand(wikiListCommand);
|
|
1587
2162
|
wikiCommand.addCommand(wikiPagesCommand);
|
|
1588
|
-
var wikiPageCommand = new
|
|
2163
|
+
var wikiPageCommand = new import_commander32.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
|
|
1589
2164
|
wikiPageCommand.addCommand(wikiPageGetCommand);
|
|
1590
2165
|
wikiPageCommand.addCommand(wikiPageCreateCommand);
|
|
1591
2166
|
wikiPageCommand.addCommand(wikiPageEditCommand);
|
|
1592
2167
|
wikiCommand.addCommand(wikiPageCommand);
|
|
2168
|
+
var mailCommand = new import_commander32.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
|
|
2169
|
+
mailCommand.addCommand(mailListCommand);
|
|
2170
|
+
mailCommand.addCommand(mailGetCommand);
|
|
2171
|
+
mailCommand.addCommand(mailSendCommand);
|
|
2172
|
+
mailCommand.addCommand(mailReplyCommand);
|
|
1593
2173
|
program.addCommand(configCommand);
|
|
1594
2174
|
program.addCommand(cacheCommand);
|
|
1595
2175
|
program.addCommand(doctorCommand);
|
|
1596
2176
|
program.addCommand(projectCommand);
|
|
1597
2177
|
program.addCommand(postCommand);
|
|
1598
2178
|
program.addCommand(wikiCommand);
|
|
2179
|
+
program.addCommand(mailCommand);
|
|
1599
2180
|
program.parseAsync().catch((err) => {
|
|
1600
2181
|
if (err instanceof DoorayCliError) {
|
|
1601
|
-
process.stderr.write(
|
|
2182
|
+
process.stderr.write(import_chalk6.default.red(`\uC624\uB958: ${err.message}`) + "\n");
|
|
1602
2183
|
process.exit(err.exitCode);
|
|
1603
2184
|
}
|
|
1604
|
-
process.stderr.write(
|
|
2185
|
+
process.stderr.write(import_chalk6.default.red(`\uC624\uB958: ${err.message}`) + "\n");
|
|
1605
2186
|
process.exit(1);
|
|
1606
2187
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bifos/dooray-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI tool for Dooray project management — AI agent & terminal friendly",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dooray",
|
|
@@ -36,14 +36,19 @@
|
|
|
36
36
|
"chalk": "^5.6.2",
|
|
37
37
|
"cli-table3": "^0.6.5",
|
|
38
38
|
"commander": "^14.0.3",
|
|
39
|
+
"imapflow": "^1.2.18",
|
|
39
40
|
"js-yaml": "^4.1.1",
|
|
40
41
|
"ky": "^1.14.3",
|
|
42
|
+
"mailparser": "^3.9.6",
|
|
43
|
+
"nodemailer": "^8.0.4",
|
|
41
44
|
"ora": "^9.3.0",
|
|
42
45
|
"tmp": "^0.2.5"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {
|
|
45
48
|
"@types/js-yaml": "^4.0.9",
|
|
49
|
+
"@types/mailparser": "^3.4.6",
|
|
46
50
|
"@types/node": "^25.5.0",
|
|
51
|
+
"@types/nodemailer": "^7.0.11",
|
|
47
52
|
"@types/tmp": "^0.2.6",
|
|
48
53
|
"tsup": "^8.5.1",
|
|
49
54
|
"typescript": "^6.0.2"
|