mailpit-api 1.0.6 → 1.1.1
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/dist/cjs/index.d.ts +53 -98
- package/dist/cjs/index.js +84 -40
- package/dist/mjs/index.d.ts +53 -98
- package/dist/mjs/index.js +49 -36
- package/package.json +8 -9
- package/src/index.ts +136 -139
- package/tsconfig-base.json +1 -1
package/dist/mjs/index.d.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
interface Address {
|
|
2
|
+
Address: string;
|
|
3
|
+
Name: string;
|
|
4
|
+
}
|
|
5
|
+
interface Email {
|
|
6
|
+
Email: string;
|
|
7
|
+
Name: string;
|
|
8
|
+
}
|
|
9
|
+
interface Attachment {
|
|
10
|
+
ContentID: string;
|
|
11
|
+
ContentType: string;
|
|
12
|
+
FileName: string;
|
|
13
|
+
PartID: string;
|
|
14
|
+
Size: number;
|
|
15
|
+
}
|
|
16
|
+
interface ChaosTrigger {
|
|
17
|
+
ErrorCode: number;
|
|
18
|
+
Probability: number;
|
|
19
|
+
}
|
|
1
20
|
export interface MailpitInfoResponse {
|
|
2
21
|
Database: string;
|
|
3
22
|
DatabaseSize: number;
|
|
@@ -30,49 +49,22 @@ export interface MailpitConfigurationResponse {
|
|
|
30
49
|
};
|
|
31
50
|
}
|
|
32
51
|
export interface MailpitMessageSummaryResponse {
|
|
33
|
-
Attachments:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
FileName: string;
|
|
37
|
-
PartID: string;
|
|
38
|
-
Size: number;
|
|
39
|
-
}[];
|
|
40
|
-
Bcc: {
|
|
41
|
-
Address: string;
|
|
42
|
-
Name: string;
|
|
43
|
-
}[];
|
|
44
|
-
Cc: {
|
|
45
|
-
Address: string;
|
|
46
|
-
Name: string;
|
|
47
|
-
}[];
|
|
52
|
+
Attachments: Attachment[];
|
|
53
|
+
Bcc: Address[];
|
|
54
|
+
Cc: Address[];
|
|
48
55
|
Date: string;
|
|
49
|
-
From:
|
|
50
|
-
Address: string;
|
|
51
|
-
Name: string;
|
|
52
|
-
};
|
|
56
|
+
From: Address;
|
|
53
57
|
HTML: string;
|
|
54
58
|
ID: string;
|
|
55
|
-
Inline:
|
|
56
|
-
ContentID: string;
|
|
57
|
-
ContentType: string;
|
|
58
|
-
FileName: string;
|
|
59
|
-
PartID: string;
|
|
60
|
-
Size: number;
|
|
61
|
-
}[];
|
|
59
|
+
Inline: Attachment[];
|
|
62
60
|
MessageID: string;
|
|
63
|
-
ReplyTo:
|
|
64
|
-
Address: string;
|
|
65
|
-
Name: string;
|
|
66
|
-
}[];
|
|
61
|
+
ReplyTo: Address[];
|
|
67
62
|
ReturnPath: string;
|
|
68
63
|
Size: number;
|
|
69
64
|
Subject: string;
|
|
70
65
|
Tags: string[];
|
|
71
66
|
Text: string;
|
|
72
|
-
To:
|
|
73
|
-
Address: string;
|
|
74
|
-
Name: string;
|
|
75
|
-
}[];
|
|
67
|
+
To: Address[];
|
|
76
68
|
}
|
|
77
69
|
export interface MailpitMessagesSummaryResponse {
|
|
78
70
|
messages: {
|
|
@@ -84,26 +76,11 @@ export interface MailpitMessagesSummaryResponse {
|
|
|
84
76
|
ID: string;
|
|
85
77
|
MessageID: string;
|
|
86
78
|
Read: boolean;
|
|
87
|
-
Bcc:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Address: string;
|
|
93
|
-
Name: string;
|
|
94
|
-
}[];
|
|
95
|
-
From: {
|
|
96
|
-
Address: string;
|
|
97
|
-
Name: string;
|
|
98
|
-
};
|
|
99
|
-
ReplyTo: {
|
|
100
|
-
Address: string;
|
|
101
|
-
Name: string;
|
|
102
|
-
}[];
|
|
103
|
-
To: {
|
|
104
|
-
Address: string;
|
|
105
|
-
Name: string;
|
|
106
|
-
}[];
|
|
79
|
+
Bcc: Address[];
|
|
80
|
+
Cc: Address[];
|
|
81
|
+
From: Address;
|
|
82
|
+
ReplyTo: Address[];
|
|
83
|
+
To: Address[];
|
|
107
84
|
}[];
|
|
108
85
|
messages_count: number;
|
|
109
86
|
start: number;
|
|
@@ -120,29 +97,17 @@ export interface MailpitSendRequest {
|
|
|
120
97
|
Filename: string;
|
|
121
98
|
}[];
|
|
122
99
|
Bcc: string[];
|
|
123
|
-
Cc:
|
|
124
|
-
|
|
125
|
-
Name: string;
|
|
126
|
-
}[];
|
|
127
|
-
From: {
|
|
128
|
-
Email: string;
|
|
129
|
-
Name: string;
|
|
130
|
-
};
|
|
100
|
+
Cc: Email[];
|
|
101
|
+
From: Email;
|
|
131
102
|
HTML: string;
|
|
132
103
|
Headers: {
|
|
133
104
|
[key: string]: string;
|
|
134
105
|
};
|
|
135
|
-
ReplyTo:
|
|
136
|
-
Email: string;
|
|
137
|
-
Name: string;
|
|
138
|
-
}[];
|
|
106
|
+
ReplyTo: Email[];
|
|
139
107
|
Subject: string;
|
|
140
108
|
Tags: string[];
|
|
141
109
|
Text: string;
|
|
142
|
-
To:
|
|
143
|
-
Email: string;
|
|
144
|
-
Name: string;
|
|
145
|
-
}[];
|
|
110
|
+
To: Email[];
|
|
146
111
|
}
|
|
147
112
|
export interface MailpitSendMessageConfirmationResponse {
|
|
148
113
|
ID: string;
|
|
@@ -225,44 +190,33 @@ export interface MailpitSetTagsRequest {
|
|
|
225
190
|
Tags: string[];
|
|
226
191
|
}
|
|
227
192
|
export interface ChaosTriggersRequest {
|
|
228
|
-
Authentication?:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
};
|
|
232
|
-
Recipient?: {
|
|
233
|
-
ErrorCode: number;
|
|
234
|
-
Probability: number;
|
|
235
|
-
};
|
|
236
|
-
Sender?: {
|
|
237
|
-
ErrorCode: number;
|
|
238
|
-
Probability: number;
|
|
239
|
-
};
|
|
193
|
+
Authentication?: ChaosTrigger;
|
|
194
|
+
Recipient?: ChaosTrigger;
|
|
195
|
+
Sender?: ChaosTrigger;
|
|
240
196
|
}
|
|
241
197
|
export interface ChaosTriggersResponse {
|
|
242
|
-
Authentication:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
};
|
|
250
|
-
Sender?: {
|
|
251
|
-
ErrorCode: number;
|
|
252
|
-
Probability: number;
|
|
253
|
-
};
|
|
198
|
+
Authentication: ChaosTrigger;
|
|
199
|
+
Recipient?: ChaosTrigger;
|
|
200
|
+
Sender?: ChaosTrigger;
|
|
201
|
+
}
|
|
202
|
+
interface AttachmentResponse {
|
|
203
|
+
data: ArrayBuffer;
|
|
204
|
+
contentType: string;
|
|
254
205
|
}
|
|
255
206
|
export declare class MailpitClient {
|
|
256
207
|
private axiosInstance;
|
|
257
|
-
constructor(baseURL: string
|
|
208
|
+
constructor(baseURL: string, auth?: {
|
|
209
|
+
username: string;
|
|
210
|
+
password: string;
|
|
211
|
+
});
|
|
258
212
|
private handleRequest;
|
|
259
213
|
getInfo(): Promise<MailpitInfoResponse>;
|
|
260
214
|
getConfiguration(): Promise<MailpitConfigurationResponse>;
|
|
261
215
|
getMessageSummary(id?: string): Promise<MailpitMessageSummaryResponse>;
|
|
262
216
|
getMessageHeaders(id?: string): Promise<MailpitMessageHeadersResponse>;
|
|
263
|
-
getMessageAttachment(id: string, partID: string): Promise<
|
|
217
|
+
getMessageAttachment(id: string, partID: string): Promise<AttachmentResponse>;
|
|
264
218
|
getMessageSource(id?: string): Promise<string>;
|
|
265
|
-
getAttachmentThumbnail(id: string, partID: string): Promise<
|
|
219
|
+
getAttachmentThumbnail(id: string, partID: string): Promise<AttachmentResponse>;
|
|
266
220
|
releaseMessage(id: string, releaseRequest: {
|
|
267
221
|
To: string[];
|
|
268
222
|
}): Promise<string>;
|
|
@@ -279,8 +233,9 @@ export declare class MailpitClient {
|
|
|
279
233
|
setTags(request: MailpitSetTagsRequest): Promise<string>;
|
|
280
234
|
renameTag(tag: string, newTagName: string): Promise<string>;
|
|
281
235
|
deleteTag(tag: string): Promise<string>;
|
|
282
|
-
renderMessageHTML(id?: string): Promise<string>;
|
|
236
|
+
renderMessageHTML(id?: string, embed?: 1): Promise<string>;
|
|
283
237
|
renderMessageText(id?: string): Promise<string>;
|
|
284
238
|
getChaosTriggers(): Promise<ChaosTriggersResponse>;
|
|
285
239
|
setChaosTriggers(triggers?: ChaosTriggersRequest): Promise<ChaosTriggersResponse>;
|
|
286
240
|
}
|
|
241
|
+
export {};
|
package/dist/mjs/index.js
CHANGED
|
@@ -1,128 +1,141 @@
|
|
|
1
|
-
import axios from "axios";
|
|
1
|
+
import axios, { isAxiosError, } from "axios";
|
|
2
2
|
export class MailpitClient {
|
|
3
3
|
axiosInstance;
|
|
4
|
-
constructor(baseURL) {
|
|
4
|
+
constructor(baseURL, auth) {
|
|
5
5
|
this.axiosInstance = axios.create({
|
|
6
|
-
baseURL
|
|
6
|
+
baseURL,
|
|
7
|
+
auth,
|
|
7
8
|
validateStatus: function (status) {
|
|
8
9
|
return status === 200;
|
|
9
10
|
},
|
|
10
11
|
});
|
|
11
12
|
}
|
|
12
|
-
async handleRequest(request) {
|
|
13
|
+
async handleRequest(request, options = { fullResponse: false }) {
|
|
13
14
|
try {
|
|
14
15
|
const response = await request();
|
|
15
|
-
return response.data;
|
|
16
|
+
return options.fullResponse ? response : response.data;
|
|
16
17
|
}
|
|
17
18
|
catch (error) {
|
|
18
|
-
if (
|
|
19
|
+
if (isAxiosError(error)) {
|
|
20
|
+
const url = error.config?.url || "UNKNOWN URL";
|
|
21
|
+
const method = error.config?.method?.toUpperCase() || "UNKNOWN METHOD";
|
|
19
22
|
if (error.response) {
|
|
20
23
|
// Server responded with a status other than 2xx
|
|
21
|
-
throw new Error(`Mailpit API Error: ${error.response.status} ${error.response.statusText}: ${JSON.stringify(error.response.data)}`);
|
|
24
|
+
throw new Error(`Mailpit API Error: ${error.response.status.toString()} ${error.response.statusText} at ${method} ${url}: ${JSON.stringify(error.response.data)}`);
|
|
22
25
|
}
|
|
23
26
|
else if (error.request) {
|
|
24
27
|
// Request was made but no response was received
|
|
25
|
-
throw new Error(
|
|
28
|
+
throw new Error(`Mailpit API Error: No response received from server at ${method} ${url}`);
|
|
26
29
|
}
|
|
27
30
|
else {
|
|
28
31
|
// Something happened in setting up the request
|
|
29
|
-
throw new Error(`Mailpit API Error: ${error.
|
|
32
|
+
throw new Error(`Mailpit API Error: ${error.toString()} at ${method} ${url}`);
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
else {
|
|
33
|
-
throw new Error(
|
|
36
|
+
throw new Error(`Unexpected Error: ${error}`);
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
// Application
|
|
38
41
|
async getInfo() {
|
|
39
|
-
return this.handleRequest(() => this.axiosInstance.get("/api/v1/info"));
|
|
42
|
+
return await this.handleRequest(() => this.axiosInstance.get("/api/v1/info"));
|
|
40
43
|
}
|
|
41
44
|
async getConfiguration() {
|
|
42
|
-
return this.handleRequest(() => this.axiosInstance.get("/api/v1/webui"));
|
|
45
|
+
return await this.handleRequest(() => this.axiosInstance.get("/api/v1/webui"));
|
|
43
46
|
}
|
|
44
47
|
// Message
|
|
45
48
|
async getMessageSummary(id = "latest") {
|
|
46
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}`));
|
|
49
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}`));
|
|
47
50
|
}
|
|
48
51
|
async getMessageHeaders(id = "latest") {
|
|
49
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/headers`));
|
|
52
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/headers`));
|
|
50
53
|
}
|
|
51
54
|
async getMessageAttachment(id, partID) {
|
|
52
|
-
|
|
55
|
+
const response = await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/part/${partID}`, { responseType: "arraybuffer" }), { fullResponse: true });
|
|
56
|
+
return {
|
|
57
|
+
data: response.data,
|
|
58
|
+
contentType: response.headers["content-type"],
|
|
59
|
+
};
|
|
53
60
|
}
|
|
54
61
|
async getMessageSource(id = "latest") {
|
|
55
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/raw`));
|
|
62
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/raw`));
|
|
56
63
|
}
|
|
57
64
|
async getAttachmentThumbnail(id, partID) {
|
|
58
|
-
|
|
65
|
+
const response = await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/part/${partID}/thumb`, {
|
|
66
|
+
responseType: "arraybuffer",
|
|
67
|
+
}), { fullResponse: true });
|
|
68
|
+
return {
|
|
69
|
+
data: response.data,
|
|
70
|
+
contentType: response.headers["content-type"],
|
|
71
|
+
};
|
|
59
72
|
}
|
|
60
73
|
async releaseMessage(id, releaseRequest) {
|
|
61
|
-
return this.handleRequest(() => this.axiosInstance.post(`/api/v1/message/${id}/release`, releaseRequest));
|
|
74
|
+
return await this.handleRequest(() => this.axiosInstance.post(`/api/v1/message/${id}/release`, releaseRequest));
|
|
62
75
|
}
|
|
63
76
|
async sendMessage(sendReqest) {
|
|
64
|
-
return this.handleRequest(() => this.axiosInstance.post(`/api/v1/send`, sendReqest));
|
|
77
|
+
return await this.handleRequest(() => this.axiosInstance.post(`/api/v1/send`, sendReqest));
|
|
65
78
|
}
|
|
66
79
|
// Other
|
|
67
80
|
async htmlCheck(id = "latest") {
|
|
68
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/html-check`));
|
|
81
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/html-check`));
|
|
69
82
|
}
|
|
70
83
|
async linkCheck(id = "latest", follow = "false") {
|
|
71
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/link-check`, { params: { follow } }));
|
|
84
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/link-check`, { params: { follow } }));
|
|
72
85
|
}
|
|
73
86
|
async spamAssassinCheck(id = "latest") {
|
|
74
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/sa-check`));
|
|
87
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/message/${id}/sa-check`));
|
|
75
88
|
}
|
|
76
89
|
// Messages
|
|
77
90
|
async listMessages(start = 0, limit = 50) {
|
|
78
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/messages`, { params: { start, limit } }));
|
|
91
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/messages`, { params: { start, limit } }));
|
|
79
92
|
}
|
|
80
93
|
async setReadStatus(readStatus) {
|
|
81
|
-
return this.handleRequest(() => this.axiosInstance.put(`/api/v1/messages`, readStatus));
|
|
94
|
+
return await this.handleRequest(() => this.axiosInstance.put(`/api/v1/messages`, readStatus));
|
|
82
95
|
}
|
|
83
96
|
async deleteMessages(deleteRequest) {
|
|
84
|
-
return this.handleRequest(() => this.axiosInstance.delete(`/api/v1/messages`, {
|
|
97
|
+
return await this.handleRequest(() => this.axiosInstance.delete(`/api/v1/messages`, {
|
|
85
98
|
data: deleteRequest,
|
|
86
99
|
}));
|
|
87
100
|
}
|
|
88
101
|
// See https://mailpit.axllent.org/docs/usage/search-filters/
|
|
89
102
|
async searchMessages(search) {
|
|
90
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/search`, {
|
|
103
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/search`, {
|
|
91
104
|
params: search,
|
|
92
105
|
}));
|
|
93
106
|
}
|
|
94
107
|
// See https://mailpit.axllent.org/docs/usage/search-filters/
|
|
95
108
|
async deleteMessagesBySearch(search) {
|
|
96
|
-
return this.handleRequest(() => this.axiosInstance.delete(`/api/v1/search`, { params: search }));
|
|
109
|
+
return await this.handleRequest(() => this.axiosInstance.delete(`/api/v1/search`, { params: search }));
|
|
97
110
|
}
|
|
98
111
|
// Tags
|
|
99
112
|
async getTags() {
|
|
100
|
-
return this.handleRequest(() => this.axiosInstance.get(`/api/v1/tags`));
|
|
113
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/api/v1/tags`));
|
|
101
114
|
}
|
|
102
115
|
async setTags(request) {
|
|
103
|
-
return this.handleRequest(() => this.axiosInstance.put(`/api/v1/tags`, request));
|
|
116
|
+
return await this.handleRequest(() => this.axiosInstance.put(`/api/v1/tags`, request));
|
|
104
117
|
}
|
|
105
118
|
async renameTag(tag, newTagName) {
|
|
106
119
|
const encodedTag = encodeURI(tag);
|
|
107
|
-
return this.handleRequest(() => this.axiosInstance.put(`/api/v1/tags/${encodedTag}`, {
|
|
120
|
+
return await this.handleRequest(() => this.axiosInstance.put(`/api/v1/tags/${encodedTag}`, {
|
|
108
121
|
Name: newTagName,
|
|
109
122
|
}));
|
|
110
123
|
}
|
|
111
124
|
async deleteTag(tag) {
|
|
112
125
|
const encodedTag = encodeURI(tag);
|
|
113
|
-
return this.handleRequest(() => this.axiosInstance.delete(`/api/v1/tags/${encodedTag}`));
|
|
126
|
+
return await this.handleRequest(() => this.axiosInstance.delete(`/api/v1/tags/${encodedTag}`));
|
|
114
127
|
}
|
|
115
128
|
// Testing
|
|
116
|
-
async renderMessageHTML(id = "latest") {
|
|
117
|
-
return this.handleRequest(() => this.axiosInstance.get(`/view/${id}.html
|
|
129
|
+
async renderMessageHTML(id = "latest", embed) {
|
|
130
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/view/${id}.html`, { params: { embed } }));
|
|
118
131
|
}
|
|
119
132
|
async renderMessageText(id = "latest") {
|
|
120
|
-
return this.handleRequest(() => this.axiosInstance.get(`/view/${id}.txt`));
|
|
133
|
+
return await this.handleRequest(() => this.axiosInstance.get(`/view/${id}.txt`));
|
|
121
134
|
}
|
|
122
135
|
async getChaosTriggers() {
|
|
123
|
-
return this.handleRequest(() => this.axiosInstance.get("/api/v1/chaos"));
|
|
136
|
+
return await this.handleRequest(() => this.axiosInstance.get("/api/v1/chaos"));
|
|
124
137
|
}
|
|
125
138
|
async setChaosTriggers(triggers = {}) {
|
|
126
|
-
return this.handleRequest(() => this.axiosInstance.put("/api/v1/chaos", triggers));
|
|
139
|
+
return await this.handleRequest(() => this.axiosInstance.put("/api/v1/chaos", triggers));
|
|
127
140
|
}
|
|
128
141
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailpit-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A NodeJS client library, written in TypeScript, to interact with the Mailpit API.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "echo \"TODO: Add tests\" && exit 0",
|
|
19
19
|
"build": "rm -fr dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./fixup_type",
|
|
20
|
-
"pretty": "npx prettier . --write"
|
|
20
|
+
"pretty": "npx prettier . --write",
|
|
21
|
+
"lint": "npx eslint --fix src"
|
|
21
22
|
},
|
|
22
23
|
"keywords": [
|
|
23
24
|
"mailpit-api",
|
|
@@ -36,16 +37,14 @@
|
|
|
36
37
|
"axios": "^1.7.9"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@eslint/js": "^8.57.
|
|
40
|
+
"@eslint/js": "^8.57.1",
|
|
41
|
+
"@types/eslint__js": "^8.42.3",
|
|
40
42
|
"@types/node": "^20.14.15",
|
|
41
|
-
"
|
|
42
|
-
"@typescript-eslint/parser": "^7.18.0",
|
|
43
|
-
"eslint": "^8.57.0",
|
|
44
|
-
"globals": "^15.14.0",
|
|
43
|
+
"eslint": "^9.20.1",
|
|
45
44
|
"jest": "^29.7.0",
|
|
46
45
|
"prettier": "3.4.2",
|
|
47
|
-
"tsx": "^4.19.
|
|
46
|
+
"tsx": "^4.19.3",
|
|
48
47
|
"typescript": "^5.7.3",
|
|
49
|
-
"typescript-eslint": "^
|
|
48
|
+
"typescript-eslint": "^8.24.0"
|
|
50
49
|
}
|
|
51
50
|
}
|