dooray-mail-cli 0.1.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 +19 -0
- package/SKILL.md +195 -0
- package/dist/mail-client.js +381 -0
- package/dist/security.js +163 -0
- package/dooray-cli.js +378 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Dooray OpenClaw Skill
|
|
2
|
+
|
|
3
|
+
Dooray mail CLI for OpenClaw - IMAP/SMTP integration
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
dooray-cli config # Setup
|
|
15
|
+
dooray-cli recent # List recent mails
|
|
16
|
+
dooray-cli test # Test connection
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
See `SKILL.md` for OpenClaw integration.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dooray
|
|
3
|
+
description: "Manage Dooray emails via IMAP/SMTP using the dooray-cli command. List, read, send, and search emails from the terminal. Use when working with Dooray email, checking Dooray inbox, or when the user mentions Dooray mail operations."
|
|
4
|
+
homepage: https://dooray.com
|
|
5
|
+
metadata:
|
|
6
|
+
{
|
|
7
|
+
"openclaw":
|
|
8
|
+
{
|
|
9
|
+
"emoji": "š§",
|
|
10
|
+
"requires": { "bins": ["dooray-cli"] },
|
|
11
|
+
"install":
|
|
12
|
+
[
|
|
13
|
+
{
|
|
14
|
+
"id": "npm-global",
|
|
15
|
+
"kind": "npm",
|
|
16
|
+
"package": "dooray-mail-cli",
|
|
17
|
+
"bins": ["dooray-cli"],
|
|
18
|
+
"label": "Install Dooray CLI (npm)",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Dooray Email CLI
|
|
26
|
+
|
|
27
|
+
Dooray CLI is a command-line tool for managing Dooray emails using IMAP/SMTP protocols.
|
|
28
|
+
|
|
29
|
+
## Prerequisites
|
|
30
|
+
|
|
31
|
+
1. Dooray CLI installed (`dooray-cli --help` to verify)
|
|
32
|
+
2. Dooray email account with IMAP/SMTP access enabled
|
|
33
|
+
3. Configuration file setup (created via `dooray-cli config`)
|
|
34
|
+
|
|
35
|
+
## Configuration Setup
|
|
36
|
+
|
|
37
|
+
Run the interactive configuration wizard:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dooray-cli config
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This will:
|
|
44
|
+
- Prompt for your Dooray email address
|
|
45
|
+
- Securely encrypt and store your password
|
|
46
|
+
- Save configuration to `~/.dooray-config.json`
|
|
47
|
+
|
|
48
|
+
**Default Dooray IMAP/SMTP settings:**
|
|
49
|
+
- IMAP: `imap.dooray.com:993` (SSL)
|
|
50
|
+
- SMTP: `smtp.dooray.com:465` (SSL)
|
|
51
|
+
|
|
52
|
+
## Common Operations
|
|
53
|
+
|
|
54
|
+
### List Recent Emails (All)
|
|
55
|
+
|
|
56
|
+
List 10 most recent emails (read and unread):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
dooray-cli recent
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Output format:
|
|
63
|
+
```
|
|
64
|
+
š¬ Recent Mails (10)
|
|
65
|
+
|
|
66
|
+
1. ā [UID: 279] sender@example.com <sender@example.com>
|
|
67
|
+
Subject: Project Update
|
|
68
|
+
Date: 2026-02-09T08:29:51.000Z
|
|
69
|
+
|
|
70
|
+
2. ā [UID: 278] John Doe <john.doe@example.com>
|
|
71
|
+
Subject: Meeting Schedule - 02.12(Thu) 03:30 PM
|
|
72
|
+
Date: 2026-02-09T07:00:47.000Z
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**ā** = Read, **ā** = Unread
|
|
76
|
+
|
|
77
|
+
### List Unread Emails Only
|
|
78
|
+
|
|
79
|
+
List up to 10 most recent unread emails:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
dooray-cli list
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Output format:
|
|
86
|
+
```
|
|
87
|
+
š¬ Unread Mails (3)
|
|
88
|
+
|
|
89
|
+
1. [UID: 123] sender@example.com <sender@example.com>
|
|
90
|
+
Subject: Project Update
|
|
91
|
+
Date: 2026-02-09T10:30:00.000Z
|
|
92
|
+
|
|
93
|
+
2. [UID: 124] colleague@example.com <colleague@example.com>
|
|
94
|
+
Subject: Meeting Notes
|
|
95
|
+
Date: 2026-02-09T11:15:00.000Z
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Read a Specific Email
|
|
99
|
+
|
|
100
|
+
Read email by UID (from list output):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
dooray-cli read 123
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Send a New Email
|
|
107
|
+
|
|
108
|
+
**Non-interactive (recommended for automation):**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
dooray-cli send --to "recipient@example.com" --subject "Subject here" --body "Message body here"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Interactive mode:**
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
dooray-cli send
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
You'll be prompted for:
|
|
121
|
+
- To: recipient email address
|
|
122
|
+
- Subject: email subject
|
|
123
|
+
- Body: email content (press Ctrl+D when done)
|
|
124
|
+
|
|
125
|
+
### Search Emails
|
|
126
|
+
|
|
127
|
+
Search for emails by keywords in subject:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
dooray-cli search "meeting"
|
|
131
|
+
dooray-cli search "project" "update"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Check Unread Count
|
|
135
|
+
|
|
136
|
+
Get total unread email count:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
dooray-cli unread
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Output: `š¬ Unread mails: 5`
|
|
143
|
+
|
|
144
|
+
### Test Connection
|
|
145
|
+
|
|
146
|
+
Verify IMAP/SMTP connection:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
dooray-cli test
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Output Formats
|
|
153
|
+
|
|
154
|
+
All commands output to stdout with emoji indicators:
|
|
155
|
+
|
|
156
|
+
- `ā
` Success
|
|
157
|
+
- `ā` Error
|
|
158
|
+
- `š¬` Unread mail info
|
|
159
|
+
- `š§` Mail details
|
|
160
|
+
- `š` Search results
|
|
161
|
+
|
|
162
|
+
## Error Handling
|
|
163
|
+
|
|
164
|
+
Common errors:
|
|
165
|
+
|
|
166
|
+
**Configuration not found:**
|
|
167
|
+
```
|
|
168
|
+
ā Configuration not found. Run: dooray-cli config
|
|
169
|
+
```
|
|
170
|
+
Solution: Run `dooray-cli config` to set up credentials.
|
|
171
|
+
|
|
172
|
+
**Connection failed:**
|
|
173
|
+
```
|
|
174
|
+
ā IMAP connection failed
|
|
175
|
+
```
|
|
176
|
+
Solution: Check network, verify credentials, or run `dooray-cli test`.
|
|
177
|
+
|
|
178
|
+
**Invalid UID:**
|
|
179
|
+
```
|
|
180
|
+
ā Usage: dooray-cli read <uid>
|
|
181
|
+
```
|
|
182
|
+
Solution: Use `dooray-cli list` to get valid UIDs.
|
|
183
|
+
|
|
184
|
+
## Tips
|
|
185
|
+
|
|
186
|
+
- UIDs are relative to the inbox folder
|
|
187
|
+
- Password is encrypted using AES-256 and stored locally
|
|
188
|
+
- Configuration file: `~/.dooray-config.json` (Windows: `%USERPROFILE%\.dooray-config.json`)
|
|
189
|
+
- Run `dooray-cli help` for quick reference
|
|
190
|
+
|
|
191
|
+
## Security Notes
|
|
192
|
+
|
|
193
|
+
- Never share your `.dooray-config.json` file
|
|
194
|
+
- Password is encrypted but stored on local filesystem
|
|
195
|
+
- Use app-specific passwords if 2FA is enabled on Dooray account
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dooray Mail Client (IMAP/SMTP)
|
|
4
|
+
*
|
|
5
|
+
* IMAP/SMTP ķė”ķ ģ½ģ ģ¬ģ©ķģ¬ Dooray ė©ģ¼ź³¼ ķµģ ķ©ėė¤.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.DoorayMailClient = void 0;
|
|
42
|
+
const nodemailer = __importStar(require("nodemailer"));
|
|
43
|
+
const imaps = __importStar(require("imap-simple"));
|
|
44
|
+
const mailparser_1 = require("mailparser");
|
|
45
|
+
class DoorayMailClient {
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.config = config;
|
|
48
|
+
// SMTP ģ¤ģ
|
|
49
|
+
this.smtpTransporter = nodemailer.createTransport({
|
|
50
|
+
host: config.smtp.host,
|
|
51
|
+
port: config.smtp.port,
|
|
52
|
+
secure: config.smtp.secure,
|
|
53
|
+
auth: {
|
|
54
|
+
user: config.email,
|
|
55
|
+
pass: config.password
|
|
56
|
+
},
|
|
57
|
+
tls: {
|
|
58
|
+
rejectUnauthorized: false // Dooray SSL ģøģ¦ģ 문ģ ķ“ź²°
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* IMAP ģ°ź²° ģģ±
|
|
64
|
+
*/
|
|
65
|
+
async getImapConnection() {
|
|
66
|
+
const imapConfig = {
|
|
67
|
+
imap: {
|
|
68
|
+
user: this.config.email,
|
|
69
|
+
password: this.config.password,
|
|
70
|
+
host: this.config.imap.host,
|
|
71
|
+
port: this.config.imap.port,
|
|
72
|
+
tls: this.config.imap.secure,
|
|
73
|
+
tlsOptions: {
|
|
74
|
+
rejectUnauthorized: false
|
|
75
|
+
},
|
|
76
|
+
authTimeout: 30000
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
const connection = await imaps.connect(imapConfig);
|
|
81
|
+
return connection;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const err = error;
|
|
85
|
+
console.error('[Dooray IMAP] Connection failed:', err.message);
|
|
86
|
+
throw new Error(`IMAP ģ°ź²° ģ¤ķØ: ${err.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* ģ½ģ§ ģģ ė©ģ¼ ź°ģ
|
|
91
|
+
*/
|
|
92
|
+
async getUnreadCount() {
|
|
93
|
+
let connection;
|
|
94
|
+
try {
|
|
95
|
+
connection = await this.getImapConnection();
|
|
96
|
+
await connection.openBox('INBOX');
|
|
97
|
+
const searchCriteria = ['UNSEEN'];
|
|
98
|
+
const fetchOptions = {
|
|
99
|
+
bodies: [''],
|
|
100
|
+
struct: true
|
|
101
|
+
};
|
|
102
|
+
const messages = await connection.search(searchCriteria, fetchOptions);
|
|
103
|
+
connection.end();
|
|
104
|
+
return messages.length;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const err = error;
|
|
108
|
+
console.error('[Dooray] Failed to get unread count:', err.message);
|
|
109
|
+
if (connection)
|
|
110
|
+
connection.end();
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* ģ½ģ§ ģģ ė©ģ¼ ėŖ©ė”
|
|
116
|
+
*/
|
|
117
|
+
async getUnreadMail(limit = 10) {
|
|
118
|
+
let connection;
|
|
119
|
+
try {
|
|
120
|
+
connection = await this.getImapConnection();
|
|
121
|
+
await connection.openBox('INBOX');
|
|
122
|
+
const searchCriteria = ['UNSEEN'];
|
|
123
|
+
const fetchOptions = {
|
|
124
|
+
bodies: ['HEADER', 'TEXT', ''],
|
|
125
|
+
struct: true,
|
|
126
|
+
markSeen: false
|
|
127
|
+
};
|
|
128
|
+
const messages = await connection.search(searchCriteria, fetchOptions);
|
|
129
|
+
// ģµģ ė©ģ¼ė¶ķ° limitź°ė§
|
|
130
|
+
const limitedMessages = messages.slice(0, limit);
|
|
131
|
+
const parsedMails = [];
|
|
132
|
+
for (const item of limitedMessages) {
|
|
133
|
+
const mail = await this.parseImapMessage(item);
|
|
134
|
+
if (mail)
|
|
135
|
+
parsedMails.push(mail);
|
|
136
|
+
}
|
|
137
|
+
connection.end();
|
|
138
|
+
return parsedMails;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const err = error;
|
|
142
|
+
console.error('[Dooray] Failed to get unread mail:', err.message);
|
|
143
|
+
if (connection)
|
|
144
|
+
connection.end();
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* ģµź·¼ ė©ģ¼ ź°ģ øģ¤źø° (ģ½ģ/ģģ½ģ ėŖØė)
|
|
150
|
+
*/
|
|
151
|
+
async getRecentMail(limit = 10) {
|
|
152
|
+
let connection;
|
|
153
|
+
try {
|
|
154
|
+
connection = await this.getImapConnection();
|
|
155
|
+
const box = await connection.openBox('INBOX');
|
|
156
|
+
// ė©ģ¼ķØģ ģ 첓 ė©ģģ§ ģ
|
|
157
|
+
const totalMessages = box.messages.total;
|
|
158
|
+
if (totalMessages === 0) {
|
|
159
|
+
connection.end();
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
// ģµź·¼ Nź°ģ UID ė²ģ ź³ģ°
|
|
163
|
+
const startSeq = Math.max(1, totalMessages - limit + 1);
|
|
164
|
+
const endSeq = totalMessages;
|
|
165
|
+
const searchCriteria = [[`${startSeq}:${endSeq}`]];
|
|
166
|
+
const fetchOptions = {
|
|
167
|
+
bodies: ['HEADER', 'TEXT', ''],
|
|
168
|
+
struct: true,
|
|
169
|
+
markSeen: false
|
|
170
|
+
};
|
|
171
|
+
const messages = await connection.search(searchCriteria, fetchOptions);
|
|
172
|
+
// ģµģ ė©ģ¼ė¶ķ° ģ ė ¬
|
|
173
|
+
const recentMessages = messages.reverse();
|
|
174
|
+
const parsedMails = [];
|
|
175
|
+
for (const item of recentMessages) {
|
|
176
|
+
const mail = await this.parseImapMessage(item);
|
|
177
|
+
if (mail)
|
|
178
|
+
parsedMails.push(mail);
|
|
179
|
+
}
|
|
180
|
+
connection.end();
|
|
181
|
+
return parsedMails;
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const err = error;
|
|
185
|
+
console.error('[Dooray] Failed to get recent mail:', err.message);
|
|
186
|
+
if (connection)
|
|
187
|
+
connection.end();
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* ė©ģ¼ IDė” ģ”°ķ
|
|
193
|
+
*/
|
|
194
|
+
async getMailById(mailId) {
|
|
195
|
+
let connection;
|
|
196
|
+
try {
|
|
197
|
+
connection = await this.getImapConnection();
|
|
198
|
+
await connection.openBox('INBOX');
|
|
199
|
+
const uid = parseInt(mailId);
|
|
200
|
+
const fetchOptions = {
|
|
201
|
+
bodies: ['HEADER', 'TEXT', ''],
|
|
202
|
+
struct: true
|
|
203
|
+
};
|
|
204
|
+
const messages = await connection.search([['UID', uid]], fetchOptions);
|
|
205
|
+
if (messages.length === 0) {
|
|
206
|
+
connection.end();
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
const mail = await this.parseImapMessage(messages[0]);
|
|
210
|
+
connection.end();
|
|
211
|
+
return mail;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
const err = error;
|
|
215
|
+
console.error('[Dooray] Failed to get mail by ID:', err.message);
|
|
216
|
+
if (connection)
|
|
217
|
+
connection.end();
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* ė©ģ¼ ė°ģ”
|
|
223
|
+
*/
|
|
224
|
+
async sendMail(params) {
|
|
225
|
+
try {
|
|
226
|
+
const recipients = Array.isArray(params.to) ? params.to : [params.to];
|
|
227
|
+
const mailOptions = {
|
|
228
|
+
from: this.config.email,
|
|
229
|
+
to: recipients.join(', '),
|
|
230
|
+
subject: params.subject,
|
|
231
|
+
text: params.html ? undefined : params.body,
|
|
232
|
+
html: params.html ? params.body : undefined,
|
|
233
|
+
cc: params.cc?.join(', '),
|
|
234
|
+
bcc: params.bcc?.join(', '),
|
|
235
|
+
attachments: params.attachments
|
|
236
|
+
};
|
|
237
|
+
const info = await this.smtpTransporter.sendMail(mailOptions);
|
|
238
|
+
console.log('[Dooray] Mail sent:', info.messageId);
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
mailId: info.messageId
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
const err = error;
|
|
246
|
+
console.error('[Dooray] Failed to send mail:', err.message);
|
|
247
|
+
return { success: false };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* ė©ģ¼ ģ½ģ ģ²ė¦¬
|
|
252
|
+
*/
|
|
253
|
+
async markAsRead(mailId) {
|
|
254
|
+
let connection;
|
|
255
|
+
try {
|
|
256
|
+
connection = await this.getImapConnection();
|
|
257
|
+
await connection.openBox('INBOX');
|
|
258
|
+
const uid = parseInt(mailId);
|
|
259
|
+
await connection.addFlags(uid, '\\Seen');
|
|
260
|
+
connection.end();
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
const err = error;
|
|
265
|
+
console.error('[Dooray] Failed to mark as read:', err.message);
|
|
266
|
+
if (connection)
|
|
267
|
+
connection.end();
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* ė©ģ¼ ź²ģ
|
|
273
|
+
*/
|
|
274
|
+
async searchMail(query, limit = 10) {
|
|
275
|
+
let connection;
|
|
276
|
+
try {
|
|
277
|
+
connection = await this.getImapConnection();
|
|
278
|
+
await connection.openBox('INBOX');
|
|
279
|
+
// IMAP SEARCH: ģ ėŖ©ģ“ė 본문ģģ ź²ģ
|
|
280
|
+
const searchCriteria = [
|
|
281
|
+
'OR',
|
|
282
|
+
['SUBJECT', query],
|
|
283
|
+
['BODY', query]
|
|
284
|
+
];
|
|
285
|
+
const fetchOptions = {
|
|
286
|
+
bodies: ['HEADER', 'TEXT', ''],
|
|
287
|
+
struct: true
|
|
288
|
+
};
|
|
289
|
+
const messages = await connection.search(searchCriteria, fetchOptions);
|
|
290
|
+
// ģµģ ė©ģ¼ė¶ķ° limitź°ė§
|
|
291
|
+
const limitedMessages = messages.slice(0, limit);
|
|
292
|
+
const parsedMails = [];
|
|
293
|
+
for (const item of limitedMessages) {
|
|
294
|
+
const mail = await this.parseImapMessage(item);
|
|
295
|
+
if (mail)
|
|
296
|
+
parsedMails.push(mail);
|
|
297
|
+
}
|
|
298
|
+
connection.end();
|
|
299
|
+
return parsedMails;
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
const err = error;
|
|
303
|
+
console.error('[Dooray] Failed to search mail:', err.message);
|
|
304
|
+
if (connection)
|
|
305
|
+
connection.end();
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* IMAP ė©ģģ§ ķģ±
|
|
311
|
+
*/
|
|
312
|
+
async parseImapMessage(item) {
|
|
313
|
+
try {
|
|
314
|
+
const all = item.parts.find((part) => part.which === '');
|
|
315
|
+
const uid = item.attributes.uid;
|
|
316
|
+
const flags = item.attributes.flags || [];
|
|
317
|
+
if (!all || !all.body) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const parsed = await (0, mailparser_1.simpleParser)(all.body);
|
|
321
|
+
return {
|
|
322
|
+
id: uid.toString(),
|
|
323
|
+
subject: parsed.subject || '(ģ ėŖ© ģģ)',
|
|
324
|
+
from: {
|
|
325
|
+
name: parsed.from?.value[0]?.name || parsed.from?.text || 'ģ ģ ģģ',
|
|
326
|
+
email: parsed.from?.value[0]?.address || ''
|
|
327
|
+
},
|
|
328
|
+
to: (parsed.to?.value || []).map((addr) => ({
|
|
329
|
+
name: addr.name || addr.address,
|
|
330
|
+
email: addr.address
|
|
331
|
+
})),
|
|
332
|
+
body: parsed.text || '',
|
|
333
|
+
bodyHtml: parsed.html || undefined,
|
|
334
|
+
receivedAt: parsed.date?.toISOString() || new Date().toISOString(),
|
|
335
|
+
isRead: flags.includes('\\Seen'),
|
|
336
|
+
hasAttachments: (parsed.attachments?.length || 0) > 0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const err = error;
|
|
341
|
+
console.error('[Dooray] Failed to parse message:', err.message);
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* ģ°ź²° ķ
ģ¤ķø
|
|
347
|
+
*/
|
|
348
|
+
async testConnection() {
|
|
349
|
+
try {
|
|
350
|
+
// SMTP ķ
ģ¤ķø
|
|
351
|
+
await this.smtpTransporter.verify();
|
|
352
|
+
console.log('[Dooray] SMTP connection verified');
|
|
353
|
+
// IMAP ķ
ģ¤ķø
|
|
354
|
+
const connection = await this.getImapConnection();
|
|
355
|
+
await connection.openBox('INBOX');
|
|
356
|
+
connection.end();
|
|
357
|
+
console.log('[Dooray] IMAP connection verified');
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
const err = error;
|
|
362
|
+
console.error('[Dooray] Connection test failed:', err.message);
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* 리ģģ¤ ģ 리
|
|
368
|
+
*/
|
|
369
|
+
async close() {
|
|
370
|
+
if (this.imapConnection) {
|
|
371
|
+
try {
|
|
372
|
+
this.imapConnection.end();
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
// Ignore
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
exports.DoorayMailClient = DoorayMailClient;
|
|
381
|
+
//# sourceMappingURL=mail-client.js.map
|
package/dist/security.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security Manager
|
|
4
|
+
*
|
|
5
|
+
* ė¹ė°ė²ķø ė° ėÆ¼ź°ģ 볓 ģķøķ/ė³µķøķ
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.SecurityManager = void 0;
|
|
45
|
+
const crypto = __importStar(require("crypto"));
|
|
46
|
+
const crypto_js_1 = __importDefault(require("crypto-js"));
|
|
47
|
+
class SecurityManager {
|
|
48
|
+
constructor(encryptionKey) {
|
|
49
|
+
// ģ¬ģ©ģ ģ ź³µ ķ¤ ėė 머ģ ė³ ģė ģģ±
|
|
50
|
+
this.encryptionKey = encryptionKey || this.generateMachineKey();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 머ģ ė³ ź³ ģ ģķøķ ķ¤ ģģ±
|
|
54
|
+
*/
|
|
55
|
+
generateMachineKey() {
|
|
56
|
+
const machineId = require('os').hostname() + require('os').platform();
|
|
57
|
+
return crypto.createHash('sha256').update(machineId).digest('hex');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* ė¹ė°ė²ķø/ķ ķ° ģķøķ
|
|
61
|
+
*/
|
|
62
|
+
async encryptToken(token) {
|
|
63
|
+
try {
|
|
64
|
+
const encrypted = crypto_js_1.default.AES.encrypt(token, this.encryptionKey).toString();
|
|
65
|
+
return encrypted;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const err = error;
|
|
69
|
+
console.error('[Dooray Security] Encryption failed:', err.message);
|
|
70
|
+
throw new Error('Failed to encrypt token');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* ė¹ė°ė²ķø/ķ ķ° ė³µķøķ
|
|
75
|
+
*/
|
|
76
|
+
async decryptToken(encryptedToken) {
|
|
77
|
+
try {
|
|
78
|
+
// ķģ
첓ķ¬
|
|
79
|
+
if (typeof encryptedToken !== 'string') {
|
|
80
|
+
console.warn('[Dooray Security] Token is not a string, returning as-is');
|
|
81
|
+
return String(encryptedToken);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// CryptoJS ģķøķ ķģ ķģø
|
|
85
|
+
if (!encryptedToken.includes('U2FsdGVkX1')) {
|
|
86
|
+
// ķ문ģ¼ė” 볓ģ
|
|
87
|
+
console.warn('[Dooray Security] Token appears unencrypted');
|
|
88
|
+
return encryptedToken;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const decrypted = crypto_js_1.default.AES.decrypt(encryptedToken, this.encryptionKey);
|
|
92
|
+
const token = decrypted.toString(crypto_js_1.default.enc.Utf8);
|
|
93
|
+
|
|
94
|
+
if (!token) {
|
|
95
|
+
throw new Error('Decryption resulted in empty token');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return token;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const err = error;
|
|
102
|
+
console.error('[Dooray Security] Decryption failed:', err.message);
|
|
103
|
+
// ė³µķøķ ģ¤ķØ ģ ģė³ø ė°ķ (ķė¬øģ¼ ź°ė„ģ±)
|
|
104
|
+
return String(encryptedToken);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* ģ“ė©ģ¼ ģ£¼ģ ź²ģ¦
|
|
109
|
+
*/
|
|
110
|
+
validateEmail(email) {
|
|
111
|
+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
112
|
+
return emailPattern.test(email);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 민ź°ģ 볓 ė”ź·ø ģ ģ
|
|
116
|
+
*/
|
|
117
|
+
sanitizeForLog(data) {
|
|
118
|
+
const sensitive = ['password', 'token', 'apiToken', 'authorization', 'pass'];
|
|
119
|
+
const sanitized = { ...data };
|
|
120
|
+
for (const key of sensitive) {
|
|
121
|
+
if (sanitized[key]) {
|
|
122
|
+
sanitized[key] = '***REDACTED***';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return sanitized;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* ė¹ė°ė²ķø ź°ė ķģø
|
|
129
|
+
*/
|
|
130
|
+
checkPasswordStrength(password) {
|
|
131
|
+
const feedback = [];
|
|
132
|
+
let score = 0;
|
|
133
|
+
if (password.length >= 8)
|
|
134
|
+
score++;
|
|
135
|
+
else
|
|
136
|
+
feedback.push('ė¹ė°ė²ķøė ģµģ 8ģ ģ“ģģ“ģ“ģ¼ ķ©ėė¤');
|
|
137
|
+
if (password.length >= 12)
|
|
138
|
+
score++;
|
|
139
|
+
if (/[a-z]/.test(password))
|
|
140
|
+
score++;
|
|
141
|
+
else
|
|
142
|
+
feedback.push('ģ문ģ넼 ķ¬ķØķ“ģ¼ ķ©ėė¤');
|
|
143
|
+
if (/[A-Z]/.test(password))
|
|
144
|
+
score++;
|
|
145
|
+
else
|
|
146
|
+
feedback.push('ė문ģ넼 ķ¬ķØķ“ģ¼ ķ©ėė¤');
|
|
147
|
+
if (/[0-9]/.test(password))
|
|
148
|
+
score++;
|
|
149
|
+
else
|
|
150
|
+
feedback.push('ģ«ģ넼 ķ¬ķØķ“ģ¼ ķ©ėė¤');
|
|
151
|
+
if (/[^a-zA-Z0-9]/.test(password))
|
|
152
|
+
score++;
|
|
153
|
+
else
|
|
154
|
+
feedback.push('ķ¹ģ문ģ넼 ķ¬ķØķ“ģ¼ ķ©ėė¤');
|
|
155
|
+
return {
|
|
156
|
+
strong: score >= 4,
|
|
157
|
+
score,
|
|
158
|
+
feedback
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.SecurityManager = SecurityManager;
|
|
163
|
+
//# sourceMappingURL=security.js.map
|
package/dooray-cli.js
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dooray IMAP/SMTP CLI
|
|
5
|
+
* Command-line interface for Dooray mail operations
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { DoorayMailClient } = require('./dist/mail-client');
|
|
9
|
+
const { SecurityManager } = require('./dist/security');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
const CONFIG_PATH = path.join(os.homedir(), '.dooray-config.json');
|
|
15
|
+
|
|
16
|
+
// CLI ėŖ
ė ¹ģ“ ķģ
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const command = args[0];
|
|
19
|
+
|
|
20
|
+
// ģ¤ģ ė”ė
|
|
21
|
+
function loadConfig() {
|
|
22
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
23
|
+
console.error('ā Configuration not found. Run: dooray-cli config');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ģ¤ģ ģ ģ„
|
|
30
|
+
function saveConfig(config) {
|
|
31
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ė©ģ¼ ķ“ė¼ģ“ģøķø ģģ±
|
|
35
|
+
async function createClient(config) {
|
|
36
|
+
const security = new SecurityManager();
|
|
37
|
+
const decryptedPassword = await security.decryptToken(config.password);
|
|
38
|
+
|
|
39
|
+
return new DoorayMailClient({
|
|
40
|
+
email: config.email,
|
|
41
|
+
password: decryptedPassword,
|
|
42
|
+
imap: config.imap || { host: 'imap.dooray.com', port: 993, secure: true },
|
|
43
|
+
smtp: config.smtp || { host: 'smtp.dooray.com', port: 465, secure: true }
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ėŖ
ė ¹ģ“ ķøė¤ė¬
|
|
48
|
+
async function handleCommand() {
|
|
49
|
+
switch (command) {
|
|
50
|
+
case 'config':
|
|
51
|
+
await configCommand();
|
|
52
|
+
break;
|
|
53
|
+
case 'list':
|
|
54
|
+
await listCommand();
|
|
55
|
+
break;
|
|
56
|
+
case 'recent':
|
|
57
|
+
await recentCommand();
|
|
58
|
+
break;
|
|
59
|
+
case 'read':
|
|
60
|
+
await readCommand(args[1]);
|
|
61
|
+
break;
|
|
62
|
+
case 'send':
|
|
63
|
+
await sendCommand(args.slice(1));
|
|
64
|
+
break;
|
|
65
|
+
case 'search':
|
|
66
|
+
await searchCommand(args.slice(1));
|
|
67
|
+
break;
|
|
68
|
+
case 'unread':
|
|
69
|
+
await unreadCountCommand();
|
|
70
|
+
break;
|
|
71
|
+
case 'test':
|
|
72
|
+
await testCommand();
|
|
73
|
+
break;
|
|
74
|
+
case 'help':
|
|
75
|
+
case '--help':
|
|
76
|
+
case '-h':
|
|
77
|
+
showHelp();
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
console.error(`ā Unknown command: ${command}`);
|
|
81
|
+
showHelp();
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// config: ģ¤ģ ģ ģ„
|
|
87
|
+
async function configCommand() {
|
|
88
|
+
const readline = require('readline').createInterface({
|
|
89
|
+
input: process.stdin,
|
|
90
|
+
output: process.stdout
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const question = (q) => new Promise(resolve => readline.question(q, resolve));
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
console.log('\nš§ Dooray Configuration Setup\n');
|
|
97
|
+
|
|
98
|
+
const email = await question('Email: ');
|
|
99
|
+
const password = await question('Password: ');
|
|
100
|
+
|
|
101
|
+
const security = new SecurityManager();
|
|
102
|
+
const encryptedPassword = await security.encryptToken(password);
|
|
103
|
+
|
|
104
|
+
const config = {
|
|
105
|
+
email,
|
|
106
|
+
password: encryptedPassword,
|
|
107
|
+
imap: { host: 'imap.dooray.com', port: 993, secure: true },
|
|
108
|
+
smtp: { host: 'smtp.dooray.com', port: 465, secure: true }
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
saveConfig(config);
|
|
112
|
+
console.log(`\nā
Configuration saved to ${CONFIG_PATH}`);
|
|
113
|
+
} finally {
|
|
114
|
+
readline.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// list: ģ½ģ§ ģģ ė©ģ¼ ėŖ©ė”
|
|
119
|
+
async function listCommand() {
|
|
120
|
+
const config = loadConfig();
|
|
121
|
+
const client = await createClient(config);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const mails = await client.getUnreadMail(10);
|
|
125
|
+
|
|
126
|
+
if (mails.length === 0) {
|
|
127
|
+
console.log('š No unread mails');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(`\nš¬ Unread Mails (${mails.length})\n`);
|
|
132
|
+
mails.forEach((mail, idx) => {
|
|
133
|
+
const fromText = typeof mail.from === 'object' ? `${mail.from.name} <${mail.from.email}>` : mail.from;
|
|
134
|
+
console.log(`${idx + 1}. [UID: ${mail.id}] ${fromText}`);
|
|
135
|
+
console.log(` Subject: ${mail.subject}`);
|
|
136
|
+
console.log(` Date: ${mail.receivedAt}\n`);
|
|
137
|
+
});
|
|
138
|
+
} finally {
|
|
139
|
+
await client.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// recent: ģµź·¼ ė©ģ¼ ėŖ©ė” (ģ½ģ/ģģ½ģ ėŖØė)
|
|
144
|
+
async function recentCommand() {
|
|
145
|
+
const config = loadConfig();
|
|
146
|
+
const client = await createClient(config);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const mails = await client.getRecentMail(10);
|
|
150
|
+
|
|
151
|
+
if (mails.length === 0) {
|
|
152
|
+
console.log('š No mails found');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(`\nš¬ Recent Mails (${mails.length})\n`);
|
|
157
|
+
mails.forEach((mail, idx) => {
|
|
158
|
+
const readStatus = mail.isRead ? 'ā' : 'ā';
|
|
159
|
+
const fromText = typeof mail.from === 'object' ? `${mail.from.name} <${mail.from.email}>` : mail.from;
|
|
160
|
+
console.log(`${idx + 1}. ${readStatus} [UID: ${mail.id}] ${fromText}`);
|
|
161
|
+
console.log(` Subject: ${mail.subject}`);
|
|
162
|
+
console.log(` Date: ${mail.receivedAt}\n`);
|
|
163
|
+
});
|
|
164
|
+
} finally {
|
|
165
|
+
await client.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// read: ķ¹ģ ė©ģ¼ ģ½źø°
|
|
170
|
+
async function readCommand(uid) {
|
|
171
|
+
if (!uid) {
|
|
172
|
+
console.error('ā Usage: dooray-cli read <uid>');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const config = loadConfig();
|
|
177
|
+
const client = await createClient(config);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const mail = await client.getMailById(uid);
|
|
181
|
+
|
|
182
|
+
if (!mail) {
|
|
183
|
+
console.error('ā Mail not found');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const fromText = typeof mail.from === 'object' ? `${mail.from.name} <${mail.from.email}>` : mail.from;
|
|
188
|
+
const toText = Array.isArray(mail.to)
|
|
189
|
+
? mail.to.map(t => typeof t === 'object' ? `${t.name} <${t.email}>` : t).join(', ')
|
|
190
|
+
: mail.to;
|
|
191
|
+
|
|
192
|
+
console.log('\nš§ Mail Details\n');
|
|
193
|
+
console.log(`From: ${fromText}`);
|
|
194
|
+
console.log(`To: ${toText}`);
|
|
195
|
+
console.log(`Subject: ${mail.subject}`);
|
|
196
|
+
console.log(`Date: ${mail.receivedAt}`);
|
|
197
|
+
console.log(`Read: ${mail.isRead ? 'Yes' : 'No'}`);
|
|
198
|
+
console.log(`Attachments: ${mail.hasAttachments ? 'Yes' : 'No'}\n`);
|
|
199
|
+
console.log('ā'.repeat(50));
|
|
200
|
+
console.log(mail.body || mail.bodyHtml || '(No content)');
|
|
201
|
+
console.log('ā'.repeat(50));
|
|
202
|
+
} finally {
|
|
203
|
+
await client.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// send: ė©ģ¼ ė°ģ”
|
|
208
|
+
async function sendCommand(args) {
|
|
209
|
+
// ė¹ģøķ°ėķ°ėø ėŖØė: --to, --subject, --body ģµģ
ģ¬ģ©
|
|
210
|
+
let to, subject, body;
|
|
211
|
+
|
|
212
|
+
for (let i = 0; i < args.length; i++) {
|
|
213
|
+
if (args[i] === '--to' && args[i + 1]) {
|
|
214
|
+
to = args[++i];
|
|
215
|
+
} else if (args[i] === '--subject' && args[i + 1]) {
|
|
216
|
+
subject = args[++i];
|
|
217
|
+
} else if (args[i] === '--body' && args[i + 1]) {
|
|
218
|
+
body = args[++i];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ė¹ģøķ°ėķ°ėø ėŖØėė” ģ ė¬ė ź²½ģ°
|
|
223
|
+
if (to && subject && body) {
|
|
224
|
+
const config = loadConfig();
|
|
225
|
+
const client = await createClient(config);
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await client.sendMail({ to, subject, text: body });
|
|
229
|
+
console.log('ā
Mail sent successfully');
|
|
230
|
+
console.log(` To: ${to}`);
|
|
231
|
+
console.log(` Subject: ${subject}`);
|
|
232
|
+
return;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('ā Error:', error.message);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
} finally {
|
|
237
|
+
await client.close();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ģøķ°ėķ°ėø ėŖØė
|
|
242
|
+
const readline = require('readline').createInterface({
|
|
243
|
+
input: process.stdin,
|
|
244
|
+
output: process.stdout
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const question = (q) => new Promise(resolve => readline.question(q, resolve));
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
to = to || await question('To: ');
|
|
251
|
+
subject = subject || await question('Subject: ');
|
|
252
|
+
|
|
253
|
+
if (!body) {
|
|
254
|
+
console.log('Body (type message, press Ctrl+D when done):');
|
|
255
|
+
|
|
256
|
+
body = '';
|
|
257
|
+
readline.on('line', (line) => {
|
|
258
|
+
body += line + '\n';
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await new Promise(resolve => readline.on('close', resolve));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const config = loadConfig();
|
|
265
|
+
const client = await createClient(config);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await client.sendMail({ to, subject, text: body.trim() });
|
|
269
|
+
console.log('\nā
Mail sent successfully');
|
|
270
|
+
} finally {
|
|
271
|
+
await client.close();
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error('ā Error:', error.message);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// search: ė©ģ¼ ź²ģ
|
|
280
|
+
async function searchCommand(keywords) {
|
|
281
|
+
if (keywords.length === 0) {
|
|
282
|
+
console.error('ā Usage: dooray-cli search <keyword1> [keyword2] ...');
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const config = loadConfig();
|
|
287
|
+
const client = await createClient(config);
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const criteria = keywords.map(k => ['SUBJECT', k]);
|
|
291
|
+
const mails = await client.searchMail(criteria);
|
|
292
|
+
|
|
293
|
+
if (mails.length === 0) {
|
|
294
|
+
console.log('š No matching mails found');
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log(`\nš Search Results (${mails.length})\n`);
|
|
299
|
+
mails.slice(0, 10).forEach((mail, idx) => {
|
|
300
|
+
console.log(`${idx + 1}. [UID: ${mail.uid}] ${mail.from}`);
|
|
301
|
+
console.log(` Subject: ${mail.subject}`);
|
|
302
|
+
console.log(` Date: ${mail.date}\n`);
|
|
303
|
+
});
|
|
304
|
+
} finally {
|
|
305
|
+
await client.close();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// unread: ģ½ģ§ ģģ ė©ģ¼ ź°ģ
|
|
310
|
+
async function unreadCountCommand() {
|
|
311
|
+
const config = loadConfig();
|
|
312
|
+
const client = await createClient(config);
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const count = await client.getUnreadCount();
|
|
316
|
+
console.log(`š¬ Unread mails: ${count}`);
|
|
317
|
+
} finally {
|
|
318
|
+
await client.close();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// test: ģ°ź²° ķ
ģ¤ķø
|
|
323
|
+
async function testCommand() {
|
|
324
|
+
const config = loadConfig();
|
|
325
|
+
const client = await createClient(config);
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const result = await client.testConnection();
|
|
329
|
+
|
|
330
|
+
if (result) {
|
|
331
|
+
console.log('\nš All connections successful!');
|
|
332
|
+
} else {
|
|
333
|
+
console.error('ā Connection test failed');
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
} finally {
|
|
337
|
+
await client.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// help: ėģė§
|
|
342
|
+
function showHelp() {
|
|
343
|
+
console.log(`
|
|
344
|
+
š§ Dooray CLI - IMAP/SMTP mail client for Dooray
|
|
345
|
+
|
|
346
|
+
Usage: dooray-cli <command> [options]
|
|
347
|
+
|
|
348
|
+
Commands:
|
|
349
|
+
config Set up email configuration
|
|
350
|
+
list List unread mails only
|
|
351
|
+
recent List recent mails (read + unread)
|
|
352
|
+
read <uid> Read a specific mail by UID
|
|
353
|
+
send Send a new mail (interactive)
|
|
354
|
+
send --to <email> --subject <subject> --body <body>
|
|
355
|
+
Send a mail (non-interactive)
|
|
356
|
+
search <keywords> Search mails by keywords
|
|
357
|
+
unread Show unread mail count
|
|
358
|
+
test Test IMAP/SMTP connection
|
|
359
|
+
help Show this help message
|
|
360
|
+
|
|
361
|
+
Examples:
|
|
362
|
+
dooray-cli config
|
|
363
|
+
dooray-cli list
|
|
364
|
+
dooray-cli recent
|
|
365
|
+
dooray-cli read 123
|
|
366
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --body "Test message"
|
|
367
|
+
dooray-cli search "meeting" "report"
|
|
368
|
+
dooray-cli unread
|
|
369
|
+
|
|
370
|
+
Configuration file: ${CONFIG_PATH}
|
|
371
|
+
`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ė©ģø ģ¤ķ
|
|
375
|
+
handleCommand().catch(error => {
|
|
376
|
+
console.error('ā Error:', error.message);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dooray-mail-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Dooray mail CLI for OpenClaw Skill - IMAP/SMTP integration",
|
|
5
|
+
"main": "./dist/mail-client.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dooray-cli": "dooray-cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"dooray-cli.js",
|
|
12
|
+
"SKILL.md",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"openclaw",
|
|
17
|
+
"dooray",
|
|
18
|
+
"mail",
|
|
19
|
+
"cli",
|
|
20
|
+
"skill",
|
|
21
|
+
"imap",
|
|
22
|
+
"smtp"
|
|
23
|
+
],
|
|
24
|
+
"author": "Anonymous",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"crypto-js": "^4.2.0",
|
|
28
|
+
"imap-simple": "^5.1.0",
|
|
29
|
+
"mailparser": "^3.6.0",
|
|
30
|
+
"nodemailer": "^6.9.0"
|
|
31
|
+
}
|
|
32
|
+
}
|