@divyacon/piece-gmail 0.0.0-stage → 0.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/LICENSE.MIT-AP +24 -0
- package/README.md +8 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/actions/ai-get-message-action.d.ts +7 -0
- package/dist/lib/actions/ai-get-message-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-get-message-action.js +24 -0
- package/dist/lib/actions/ai-get-message-action.js.map +1 -0
- package/dist/lib/actions/ai-reply-to-thread-action.d.ts +16 -0
- package/dist/lib/actions/ai-reply-to-thread-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-reply-to-thread-action.js +24 -0
- package/dist/lib/actions/ai-reply-to-thread-action.js.map +1 -0
- package/dist/lib/actions/ai-search-email-action.d.ts +21 -0
- package/dist/lib/actions/ai-search-email-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-search-email-action.js +24 -0
- package/dist/lib/actions/ai-search-email-action.js.map +1 -0
- package/dist/lib/actions/ai-send-email-action.d.ts +18 -0
- package/dist/lib/actions/ai-send-email-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-send-email-action.js +24 -0
- package/dist/lib/actions/ai-send-email-action.js.map +1 -0
- package/dist/lib/actions/create-draft-action.d.ts +16 -0
- package/dist/lib/actions/create-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/create-draft-action.js +137 -0
- package/dist/lib/actions/create-draft-action.js.map +1 -0
- package/dist/lib/actions/create-draft-reply-action.d.ts +17 -0
- package/dist/lib/actions/create-draft-reply-action.d.ts.map +1 -0
- package/dist/lib/actions/create-draft-reply-action.js +269 -0
- package/dist/lib/actions/create-draft-reply-action.js.map +1 -0
- package/dist/lib/actions/delete-draft-action.d.ts +7 -0
- package/dist/lib/actions/delete-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/delete-draft-action.js +62 -0
- package/dist/lib/actions/delete-draft-action.js.map +1 -0
- package/dist/lib/actions/forward-message-action.d.ts +12 -0
- package/dist/lib/actions/forward-message-action.d.ts.map +1 -0
- package/dist/lib/actions/forward-message-action.js +139 -0
- package/dist/lib/actions/forward-message-action.js.map +1 -0
- package/dist/lib/actions/get-attachment-action.d.ts +9 -0
- package/dist/lib/actions/get-attachment-action.d.ts.map +1 -0
- package/dist/lib/actions/get-attachment-action.js +80 -0
- package/dist/lib/actions/get-attachment-action.js.map +1 -0
- package/dist/lib/actions/get-draft-action.d.ts +7 -0
- package/dist/lib/actions/get-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/get-draft-action.js +56 -0
- package/dist/lib/actions/get-draft-action.js.map +1 -0
- package/dist/lib/actions/get-label-action.d.ts +7 -0
- package/dist/lib/actions/get-label-action.d.ts.map +1 -0
- package/dist/lib/actions/get-label-action.js +55 -0
- package/dist/lib/actions/get-label-action.js.map +1 -0
- package/dist/lib/actions/get-mail-action.d.ts +7 -0
- package/dist/lib/actions/get-mail-action.d.ts.map +1 -0
- package/dist/lib/actions/get-mail-action.js +44 -0
- package/dist/lib/actions/get-mail-action.js.map +1 -0
- package/dist/lib/actions/get-profile-action.d.ts +5 -0
- package/dist/lib/actions/get-profile-action.d.ts.map +1 -0
- package/dist/lib/actions/get-profile-action.js +43 -0
- package/dist/lib/actions/get-profile-action.js.map +1 -0
- package/dist/lib/actions/get-thread-action.d.ts +9 -0
- package/dist/lib/actions/get-thread-action.d.ts.map +1 -0
- package/dist/lib/actions/get-thread-action.js +53 -0
- package/dist/lib/actions/get-thread-action.js.map +1 -0
- package/dist/lib/actions/list-drafts-action.d.ts +8 -0
- package/dist/lib/actions/list-drafts-action.d.ts.map +1 -0
- package/dist/lib/actions/list-drafts-action.js +62 -0
- package/dist/lib/actions/list-drafts-action.js.map +1 -0
- package/dist/lib/actions/list-history-action.d.ts +9 -0
- package/dist/lib/actions/list-history-action.d.ts.map +1 -0
- package/dist/lib/actions/list-history-action.js +74 -0
- package/dist/lib/actions/list-history-action.js.map +1 -0
- package/dist/lib/actions/list-labels-action.d.ts +5 -0
- package/dist/lib/actions/list-labels-action.d.ts.map +1 -0
- package/dist/lib/actions/list-labels-action.js +47 -0
- package/dist/lib/actions/list-labels-action.js.map +1 -0
- package/dist/lib/actions/list-threads-action.d.ts +10 -0
- package/dist/lib/actions/list-threads-action.d.ts.map +1 -0
- package/dist/lib/actions/list-threads-action.js +74 -0
- package/dist/lib/actions/list-threads-action.js.map +1 -0
- package/dist/lib/actions/reply-to-email-action.d.ts +16 -0
- package/dist/lib/actions/reply-to-email-action.d.ts.map +1 -0
- package/dist/lib/actions/reply-to-email-action.js +202 -0
- package/dist/lib/actions/reply-to-email-action.js.map +1 -0
- package/dist/lib/actions/request-approval-in-email.d.ts +15 -0
- package/dist/lib/actions/request-approval-in-email.d.ts.map +1 -0
- package/dist/lib/actions/request-approval-in-email.js +174 -0
- package/dist/lib/actions/request-approval-in-email.js.map +1 -0
- package/dist/lib/actions/search-email-action.d.ts +22 -0
- package/dist/lib/actions/search-email-action.d.ts.map +1 -0
- package/dist/lib/actions/search-email-action.js +179 -0
- package/dist/lib/actions/search-email-action.js.map +1 -0
- package/dist/lib/actions/send-draft-action.d.ts +7 -0
- package/dist/lib/actions/send-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/send-draft-action.js +57 -0
- package/dist/lib/actions/send-draft-action.js.map +1 -0
- package/dist/lib/actions/send-email-action.d.ts +18 -0
- package/dist/lib/actions/send-email-action.d.ts.map +1 -0
- package/dist/lib/actions/send-email-action.js +196 -0
- package/dist/lib/actions/send-email-action.js.map +1 -0
- package/dist/lib/actions/stop-watch-action.d.ts +5 -0
- package/dist/lib/actions/stop-watch-action.d.ts.map +1 -0
- package/dist/lib/actions/stop-watch-action.js +41 -0
- package/dist/lib/actions/stop-watch-action.js.map +1 -0
- package/dist/lib/actions/update-draft-action.d.ts +17 -0
- package/dist/lib/actions/update-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/update-draft-action.js +193 -0
- package/dist/lib/actions/update-draft-action.js.map +1 -0
- package/dist/lib/auth.d.ts +12 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +108 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/common/data.d.ts +132 -0
- package/dist/lib/common/data.d.ts.map +1 -0
- package/dist/lib/common/data.js +209 -0
- package/dist/lib/common/data.js.map +1 -0
- package/dist/lib/common/mime.d.ts +29 -0
- package/dist/lib/common/mime.d.ts.map +1 -0
- package/dist/lib/common/mime.js +59 -0
- package/dist/lib/common/mime.js.map +1 -0
- package/dist/lib/common/models.d.ts +86 -0
- package/dist/lib/common/models.d.ts.map +1 -0
- package/dist/lib/common/models.js +26 -0
- package/dist/lib/common/models.js.map +1 -0
- package/dist/lib/common/props.d.ts +28 -0
- package/dist/lib/common/props.d.ts.map +1 -0
- package/dist/lib/common/props.js +231 -0
- package/dist/lib/common/props.js.map +1 -0
- package/dist/lib/output-schemas.d.ts +29 -0
- package/dist/lib/output-schemas.d.ts.map +1 -0
- package/dist/lib/output-schemas.js +1029 -0
- package/dist/lib/output-schemas.js.map +1 -0
- package/dist/lib/triggers/new-attachment.d.ts +144 -0
- package/dist/lib/triggers/new-attachment.d.ts.map +1 -0
- package/dist/lib/triggers/new-attachment.js +150 -0
- package/dist/lib/triggers/new-attachment.js.map +1 -0
- package/dist/lib/triggers/new-conversation.d.ts +59 -0
- package/dist/lib/triggers/new-conversation.d.ts.map +1 -0
- package/dist/lib/triggers/new-conversation.js +308 -0
- package/dist/lib/triggers/new-conversation.js.map +1 -0
- package/dist/lib/triggers/new-email.d.ts +52 -0
- package/dist/lib/triggers/new-email.d.ts.map +1 -0
- package/dist/lib/triggers/new-email.js +135 -0
- package/dist/lib/triggers/new-email.js.map +1 -0
- package/dist/lib/triggers/new-label.d.ts +15 -0
- package/dist/lib/triggers/new-label.d.ts.map +1 -0
- package/dist/lib/triggers/new-label.js +82 -0
- package/dist/lib/triggers/new-label.js.map +1 -0
- package/dist/lib/triggers/new-labeled-email.d.ts +35 -0
- package/dist/lib/triggers/new-labeled-email.d.ts.map +1 -0
- package/dist/lib/triggers/new-labeled-email.js +147 -0
- package/dist/lib/triggers/new-labeled-email.js.map +1 -0
- package/package.json +54 -4
- package/src/index.ts +107 -0
- package/src/lib/actions/ai-get-message-action.ts +20 -0
- package/src/lib/actions/ai-reply-to-thread-action.ts +20 -0
- package/src/lib/actions/ai-search-email-action.ts +21 -0
- package/src/lib/actions/ai-send-email-action.ts +20 -0
- package/src/lib/actions/create-draft-action.ts +148 -0
- package/src/lib/actions/create-draft-reply-action.ts +314 -0
- package/src/lib/actions/delete-draft-action.ts +57 -0
- package/src/lib/actions/forward-message-action.ts +159 -0
- package/src/lib/actions/get-attachment-action.ts +83 -0
- package/src/lib/actions/get-draft-action.ts +53 -0
- package/src/lib/actions/get-label-action.ts +53 -0
- package/src/lib/actions/get-mail-action.ts +52 -0
- package/src/lib/actions/get-profile-action.ts +39 -0
- package/src/lib/actions/get-thread-action.ts +47 -0
- package/src/lib/actions/list-drafts-action.ts +72 -0
- package/src/lib/actions/list-history-action.ts +83 -0
- package/src/lib/actions/list-labels-action.ts +43 -0
- package/src/lib/actions/list-threads-action.ts +89 -0
- package/src/lib/actions/reply-to-email-action.ts +228 -0
- package/src/lib/actions/request-approval-in-email.ts +197 -0
- package/src/lib/actions/search-email-action.ts +219 -0
- package/src/lib/actions/send-draft-action.ts +54 -0
- package/src/lib/actions/send-email-action.ts +213 -0
- package/src/lib/actions/stop-watch-action.ts +37 -0
- package/src/lib/actions/update-draft-action.ts +211 -0
- package/src/lib/auth.ts +116 -0
- package/src/lib/common/data.ts +268 -0
- package/src/lib/common/mime.ts +82 -0
- package/src/lib/common/models.ts +91 -0
- package/src/lib/common/props.ts +262 -0
- package/src/lib/output-schemas.ts +1061 -0
- package/src/lib/triggers/new-attachment.ts +203 -0
- package/src/lib/triggers/new-conversation.ts +413 -0
- package/src/lib/triggers/new-email.ts +173 -0
- package/src/lib/triggers/new-label.ts +83 -0
- package/src/lib/triggers/new-labeled-email.ts +197 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { FilesService } from '@agnocon/pieces-framework';
|
|
2
|
+
import {
|
|
3
|
+
GmailLabel,
|
|
4
|
+
GmailMessage,
|
|
5
|
+
GmailThread,
|
|
6
|
+
GmailMessageFormat,
|
|
7
|
+
GmailMessageResponse as GmailMessageList,
|
|
8
|
+
} from './models';
|
|
9
|
+
import {
|
|
10
|
+
AuthenticationType,
|
|
11
|
+
httpClient,
|
|
12
|
+
HttpMethod,
|
|
13
|
+
} from '@agnocon/pieces-common';
|
|
14
|
+
import { Attachment, ParsedMail, simpleParser } from 'mailparser';
|
|
15
|
+
import { GmailAuthValue, getAccessToken } from '../auth';
|
|
16
|
+
|
|
17
|
+
interface SearchMailProps {
|
|
18
|
+
access_token: string;
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
subject?: string;
|
|
22
|
+
label: GmailLabel;
|
|
23
|
+
category: string;
|
|
24
|
+
after?: number;
|
|
25
|
+
before?: number;
|
|
26
|
+
max_results?: number;
|
|
27
|
+
page_token?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface GetMailProps {
|
|
31
|
+
access_token: string;
|
|
32
|
+
message_id?: string;
|
|
33
|
+
thread_id?: string;
|
|
34
|
+
format: GmailMessageFormat;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const GmailRequests = {
|
|
38
|
+
getMail: async ({ access_token, format, message_id }: GetMailProps) => {
|
|
39
|
+
const response = await httpClient.sendRequest<GmailMessage>({
|
|
40
|
+
method: HttpMethod.GET,
|
|
41
|
+
url: `https://gmail.googleapis.com/gmail/v1/users/me/messages/${message_id}`,
|
|
42
|
+
authentication: {
|
|
43
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
44
|
+
token: access_token,
|
|
45
|
+
},
|
|
46
|
+
queryParams: {
|
|
47
|
+
format,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
let bodyHtml = '';
|
|
51
|
+
let bodyPlain = '';
|
|
52
|
+
const payload = response.body.payload;
|
|
53
|
+
let bodyParts = payload.parts || [];
|
|
54
|
+
const headers = payload.headers.reduce(
|
|
55
|
+
(obj: { [key: string]: string }, header) => {
|
|
56
|
+
obj[header.name.toLowerCase()] = header.value;
|
|
57
|
+
return obj;
|
|
58
|
+
},
|
|
59
|
+
{}
|
|
60
|
+
);
|
|
61
|
+
const alternateBodyPart = bodyParts.find((part) =>
|
|
62
|
+
part.mimeType.startsWith('multipart/alternative')
|
|
63
|
+
);
|
|
64
|
+
if (alternateBodyPart && alternateBodyPart?.parts) {
|
|
65
|
+
bodyParts = alternateBodyPart.parts;
|
|
66
|
+
}
|
|
67
|
+
const subject = headers['subject'] || '';
|
|
68
|
+
// Determine the content type of the message body
|
|
69
|
+
const contentType = headers['content-type'] || 'text/plain';
|
|
70
|
+
const isMultipart = contentType.startsWith('multipart/');
|
|
71
|
+
|
|
72
|
+
if (isMultipart) {
|
|
73
|
+
// If the message is multipart, extract the plain text and HTML parts
|
|
74
|
+
const textPart = bodyParts.find((part) => part.mimeType === 'text/plain');
|
|
75
|
+
const htmlPart = bodyParts.find((part) => part.mimeType === 'text/html');
|
|
76
|
+
|
|
77
|
+
// If the message is an "alternative" multipart, use the plain text part if it exists
|
|
78
|
+
const preferredPart = textPart || htmlPart;
|
|
79
|
+
bodyHtml = htmlPart ? decodeBase64(htmlPart.body.data) : '';
|
|
80
|
+
bodyPlain = preferredPart ? decodeBase64(preferredPart.body.data) : '';
|
|
81
|
+
} else {
|
|
82
|
+
// If the message is not multipart, use the body as-is
|
|
83
|
+
bodyPlain = decodeBase64(payload.body.data);
|
|
84
|
+
bodyHtml = '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
subject: subject,
|
|
89
|
+
body_html: bodyHtml,
|
|
90
|
+
body_plain: bodyPlain,
|
|
91
|
+
...response.body,
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
getThread: async ({ access_token, format, thread_id }: GetMailProps) => {
|
|
95
|
+
const response = await httpClient.sendRequest<GmailThread>({
|
|
96
|
+
method: HttpMethod.GET,
|
|
97
|
+
url: `https://gmail.googleapis.com/gmail/v1/users/me/threads/${thread_id}`,
|
|
98
|
+
authentication: {
|
|
99
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
100
|
+
token: access_token,
|
|
101
|
+
},
|
|
102
|
+
queryParams: {
|
|
103
|
+
format,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return response.body;
|
|
108
|
+
},
|
|
109
|
+
getLabels: async (authentication: GmailAuthValue) => {
|
|
110
|
+
return await httpClient.sendRequest<{ labels: GmailLabel[] }>({
|
|
111
|
+
method: HttpMethod.GET,
|
|
112
|
+
url: `https://gmail.googleapis.com/gmail/v1/users/me/labels`,
|
|
113
|
+
authentication: {
|
|
114
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
115
|
+
token: await getAccessToken(authentication),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
searchMail: async ({
|
|
120
|
+
access_token,
|
|
121
|
+
max_results = 1,
|
|
122
|
+
page_token: pageToken,
|
|
123
|
+
...mail
|
|
124
|
+
}: SearchMailProps) => {
|
|
125
|
+
const query = [];
|
|
126
|
+
|
|
127
|
+
if (mail.from) query.push(`from:(${mail.from})`);
|
|
128
|
+
if (mail.to) query.push(`to:(${mail.to})`);
|
|
129
|
+
if (mail.subject) query.push(`subject:(${mail.subject})`);
|
|
130
|
+
if (mail.label) query.push(`label:${mail.label.name}`);
|
|
131
|
+
if (mail.category) query.push(`category:${mail.category}`);
|
|
132
|
+
if (mail.after != null && mail.after > 0) query.push(`after:${mail.after}`);
|
|
133
|
+
if (mail.before != null) query.push(`before:${mail.before}`);
|
|
134
|
+
|
|
135
|
+
const response = await httpClient.sendRequest<GmailMessageList>({
|
|
136
|
+
method: HttpMethod.GET,
|
|
137
|
+
url: `https://www.googleapis.com/gmail/v1/users/me/messages`,
|
|
138
|
+
authentication: {
|
|
139
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
140
|
+
token: access_token,
|
|
141
|
+
},
|
|
142
|
+
queryParams: {
|
|
143
|
+
q: query.join(' '),
|
|
144
|
+
maxResults: `${max_results}`,
|
|
145
|
+
...(pageToken ? { pageToken } : {}),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (response.body.messages) {
|
|
150
|
+
const messages = await Promise.all(
|
|
151
|
+
response.body.messages.map(
|
|
152
|
+
async (message: { id: string; threadId: string }) => {
|
|
153
|
+
const mail = await GmailRequests.getMail({
|
|
154
|
+
access_token,
|
|
155
|
+
message_id: message.id,
|
|
156
|
+
format: GmailMessageFormat.FULL,
|
|
157
|
+
});
|
|
158
|
+
const thread = await GmailRequests.getThread({
|
|
159
|
+
access_token,
|
|
160
|
+
thread_id: message.threadId,
|
|
161
|
+
format: GmailMessageFormat.FULL,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
message: mail,
|
|
166
|
+
thread,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
messages,
|
|
174
|
+
resultSizeEstimate: response.body.resultSizeEstimate,
|
|
175
|
+
...(response?.body.nextPageToken
|
|
176
|
+
? { nextPageToken: response.body.nextPageToken }
|
|
177
|
+
: {}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return response.body;
|
|
182
|
+
},
|
|
183
|
+
getRecentMessages: async (
|
|
184
|
+
authentication: GmailAuthValue,
|
|
185
|
+
maxResults = 20
|
|
186
|
+
) => {
|
|
187
|
+
return await httpClient.sendRequest<GmailMessageList>({
|
|
188
|
+
method: HttpMethod.GET,
|
|
189
|
+
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages',
|
|
190
|
+
authentication: {
|
|
191
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
192
|
+
token: await getAccessToken(authentication),
|
|
193
|
+
},
|
|
194
|
+
queryParams: {
|
|
195
|
+
maxResults: maxResults.toString(),
|
|
196
|
+
q: 'in:inbox OR in:sent', // Get recent messages from inbox and sent
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
getRecentThreads: async (authentication: GmailAuthValue, maxResults = 15) => {
|
|
201
|
+
return await httpClient.sendRequest<{
|
|
202
|
+
threads: { id: string; snippet?: string }[];
|
|
203
|
+
}>({
|
|
204
|
+
method: HttpMethod.GET,
|
|
205
|
+
url: 'https://gmail.googleapis.com/gmail/v1/users/me/threads',
|
|
206
|
+
authentication: {
|
|
207
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
208
|
+
token: await getAccessToken(authentication),
|
|
209
|
+
},
|
|
210
|
+
queryParams: {
|
|
211
|
+
maxResults: maxResults.toString(),
|
|
212
|
+
q: 'in:inbox OR in:sent', // Get recent threads from inbox and sent
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
function decodeBase64(data: any) {
|
|
219
|
+
return Buffer.from(data, 'base64').toString();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function parseStream(stream: any) {
|
|
223
|
+
return new Promise<ParsedMail>((resolve, reject) => {
|
|
224
|
+
simpleParser(stream, (err, parsed) => {
|
|
225
|
+
if (err) {
|
|
226
|
+
reject(err);
|
|
227
|
+
} else {
|
|
228
|
+
resolve(parsed);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function convertAttachment(
|
|
235
|
+
attachments: Attachment[],
|
|
236
|
+
files: FilesService
|
|
237
|
+
) {
|
|
238
|
+
const promises = attachments.map(async (attachment) => {
|
|
239
|
+
try {
|
|
240
|
+
const fileName = attachment.filename ?? `attachment-${Date.now()}`;
|
|
241
|
+
return {
|
|
242
|
+
fileName,
|
|
243
|
+
mimeType: attachment.contentType,
|
|
244
|
+
size: attachment.size,
|
|
245
|
+
data: await files.write({
|
|
246
|
+
fileName: fileName,
|
|
247
|
+
data: attachment.content,
|
|
248
|
+
}),
|
|
249
|
+
};
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(
|
|
252
|
+
`Failed to process attachment: ${attachment.filename}`,
|
|
253
|
+
error
|
|
254
|
+
);
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
const results = await Promise.all(promises);
|
|
259
|
+
return results.filter((result) => result !== null);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function getFirstFiveOrAll(array: unknown[]) {
|
|
263
|
+
if (array.length <= 5) {
|
|
264
|
+
return array;
|
|
265
|
+
} else {
|
|
266
|
+
return array.slice(0, 5);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import mime from 'mime-types';
|
|
2
|
+
import MailComposer from 'nodemailer/lib/mail-composer';
|
|
3
|
+
import Mail, { Attachment } from 'nodemailer/lib/mailer';
|
|
4
|
+
import { ApFile } from '@agnocon/pieces-framework';
|
|
5
|
+
|
|
6
|
+
export interface RawMimeAttachment {
|
|
7
|
+
filename?: string;
|
|
8
|
+
content: Buffer;
|
|
9
|
+
contentType?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface BuildRawMessageProps {
|
|
13
|
+
to?: string[];
|
|
14
|
+
cc?: string[];
|
|
15
|
+
bcc?: string[];
|
|
16
|
+
from?: string;
|
|
17
|
+
subject: string;
|
|
18
|
+
bodyType: 'plain_text' | 'html';
|
|
19
|
+
body: string;
|
|
20
|
+
headers?: { key: string; value: string }[];
|
|
21
|
+
attachments?: ({ file: ApFile; name?: string } | RawMimeAttachment)[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isFileAttachment(
|
|
25
|
+
attachment: { file: ApFile; name?: string } | RawMimeAttachment
|
|
26
|
+
): attachment is { file: ApFile; name?: string } {
|
|
27
|
+
return 'file' in attachment;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function buildRawMessage(props: BuildRawMessageProps): Promise<string> {
|
|
31
|
+
const subjectBase64 = Buffer.from(props.subject).toString('base64');
|
|
32
|
+
const mailOptions: Mail.Options = {
|
|
33
|
+
to: props.to && props.to.length > 0 ? props.to.join(', ') : undefined,
|
|
34
|
+
cc: props.cc && props.cc.length > 0 ? props.cc.join(', ') : undefined,
|
|
35
|
+
bcc: props.bcc && props.bcc.length > 0 ? props.bcc.join(', ') : undefined,
|
|
36
|
+
from: props.from,
|
|
37
|
+
subject: `=?UTF-8?B?${subjectBase64}?=`,
|
|
38
|
+
text: props.bodyType === 'plain_text' ? props.body : undefined,
|
|
39
|
+
html: props.bodyType === 'html' ? props.body : undefined,
|
|
40
|
+
attachments: [],
|
|
41
|
+
headers: props.headers,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (props.attachments && props.attachments.length > 0) {
|
|
45
|
+
const attachmentOption: Attachment[] = props.attachments.map(
|
|
46
|
+
(attachment) => {
|
|
47
|
+
if (isFileAttachment(attachment)) {
|
|
48
|
+
const { file, name } = attachment;
|
|
49
|
+
const lookupResult = mime.lookup(
|
|
50
|
+
file.extension ? file.extension : ''
|
|
51
|
+
);
|
|
52
|
+
return {
|
|
53
|
+
filename: name ?? file.filename,
|
|
54
|
+
content: file?.base64,
|
|
55
|
+
contentType: lookupResult ? lookupResult : undefined,
|
|
56
|
+
encoding: 'base64',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
filename: attachment.filename,
|
|
61
|
+
content: attachment.content,
|
|
62
|
+
contentType: attachment.contentType,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
mailOptions.attachments = attachmentOption;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
const mail: any = new MailComposer(mailOptions).compile();
|
|
71
|
+
mail.keepBcc = true;
|
|
72
|
+
const mailBody = await mail.build();
|
|
73
|
+
|
|
74
|
+
return Buffer.from(mailBody)
|
|
75
|
+
.toString('base64')
|
|
76
|
+
.replace(/\+/g, '-')
|
|
77
|
+
.replace(/\//g, '_');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const GmailMime = {
|
|
81
|
+
buildRawMessage,
|
|
82
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export interface GmailLabel {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
messageListVisibility: GmailMessageListVisibility;
|
|
5
|
+
labelListVisibility: GmailLabelListVisibility;
|
|
6
|
+
type: GmailLabelType;
|
|
7
|
+
messagesTotal: number;
|
|
8
|
+
messagesUnread: number;
|
|
9
|
+
threadsTotal: number;
|
|
10
|
+
threadsUnread: number;
|
|
11
|
+
color: {
|
|
12
|
+
textColor: string;
|
|
13
|
+
backgroundColor: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GmailMessage {
|
|
18
|
+
id: string;
|
|
19
|
+
threadId: string;
|
|
20
|
+
labelIds: [string];
|
|
21
|
+
snippet: string;
|
|
22
|
+
historyId: string;
|
|
23
|
+
internalDate: number;
|
|
24
|
+
payload: {
|
|
25
|
+
partId: string;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
filename: string;
|
|
28
|
+
headers: {
|
|
29
|
+
name: string;
|
|
30
|
+
value: string;
|
|
31
|
+
}[];
|
|
32
|
+
body: {
|
|
33
|
+
data: any;
|
|
34
|
+
size: number;
|
|
35
|
+
};
|
|
36
|
+
parts: {
|
|
37
|
+
parts: any[];
|
|
38
|
+
partId: string;
|
|
39
|
+
mimeType: string;
|
|
40
|
+
filename: string;
|
|
41
|
+
headers: {
|
|
42
|
+
name: string;
|
|
43
|
+
value: string;
|
|
44
|
+
}[];
|
|
45
|
+
body: {
|
|
46
|
+
size: number;
|
|
47
|
+
data: string;
|
|
48
|
+
};
|
|
49
|
+
}[];
|
|
50
|
+
};
|
|
51
|
+
sizeEstimate: number;
|
|
52
|
+
raw: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface GmailThread {
|
|
56
|
+
id: string;
|
|
57
|
+
snippet: string;
|
|
58
|
+
historyId: string;
|
|
59
|
+
messages: GmailMessage[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface GmailMessageResponse {
|
|
63
|
+
messages: {
|
|
64
|
+
id: string;
|
|
65
|
+
threadId: string;
|
|
66
|
+
}[];
|
|
67
|
+
nextPageToken?: string;
|
|
68
|
+
resultSizeEstimate: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
enum GmailMessageListVisibility {
|
|
72
|
+
SHOW = 'show',
|
|
73
|
+
HIDE = 'hide',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
enum GmailLabelListVisibility {
|
|
77
|
+
SHOW = 'show',
|
|
78
|
+
HIDE = 'hide',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
enum GmailLabelType {
|
|
82
|
+
SYSTEM = 'system',
|
|
83
|
+
USER = 'user',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export enum GmailMessageFormat {
|
|
87
|
+
MINIMAL = 'minimal',
|
|
88
|
+
FULL = 'full',
|
|
89
|
+
RAW = 'raw',
|
|
90
|
+
METADATA = 'metadata',
|
|
91
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import { GmailRequests } from './data';
|
|
3
|
+
import { GmailLabel } from './models';
|
|
4
|
+
import { gmailAuth, createGoogleClient, GmailAuthValue } from '../auth';
|
|
5
|
+
import { gmail as googleGmail } from '@googleapis/gmail';
|
|
6
|
+
|
|
7
|
+
export const GmailProps = {
|
|
8
|
+
from: Property.ShortText({
|
|
9
|
+
displayName: 'Email sender',
|
|
10
|
+
description:
|
|
11
|
+
'Optional filteration, leave empty to filter based on the email sender',
|
|
12
|
+
required: false,
|
|
13
|
+
defaultValue: '',
|
|
14
|
+
}),
|
|
15
|
+
to: Property.ShortText({
|
|
16
|
+
displayName: 'Email recipient',
|
|
17
|
+
description:
|
|
18
|
+
'Optional filteration, leave empty to filter based on the email recipient',
|
|
19
|
+
required: false,
|
|
20
|
+
defaultValue: '',
|
|
21
|
+
}),
|
|
22
|
+
subject: Property.ShortText({
|
|
23
|
+
displayName: 'Email subject',
|
|
24
|
+
description: 'The email subject',
|
|
25
|
+
required: false,
|
|
26
|
+
defaultValue: '',
|
|
27
|
+
}),
|
|
28
|
+
category: Property.StaticDropdown({
|
|
29
|
+
displayName: 'Category',
|
|
30
|
+
description:
|
|
31
|
+
'Optional filteration, leave unselected to filter based on the email category',
|
|
32
|
+
required: false,
|
|
33
|
+
options: {
|
|
34
|
+
disabled: false,
|
|
35
|
+
options: [
|
|
36
|
+
{ label: 'Primary', value: 'primary' },
|
|
37
|
+
{ label: 'Social', value: 'social' },
|
|
38
|
+
{ label: 'Promotions', value: 'promotions' },
|
|
39
|
+
{ label: 'Updates', value: 'updates' },
|
|
40
|
+
{ label: 'Forums', value: 'forums' },
|
|
41
|
+
{ label: 'Reservations', value: 'reservations' },
|
|
42
|
+
{ label: 'Purchases', value: 'purchases' },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
label: <R extends boolean = false>(overrides: {
|
|
47
|
+
displayName?: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
required: R;
|
|
50
|
+
}) =>
|
|
51
|
+
Property.Dropdown<GmailLabel, R, typeof gmailAuth>({
|
|
52
|
+
auth: gmailAuth,
|
|
53
|
+
displayName: overrides.displayName ?? 'Label',
|
|
54
|
+
description:
|
|
55
|
+
overrides.description ??
|
|
56
|
+
'Optional filteration, leave unselected to filter based on the email label',
|
|
57
|
+
required: overrides.required,
|
|
58
|
+
defaultValue: '',
|
|
59
|
+
refreshers: [],
|
|
60
|
+
options: async ({ auth }) => {
|
|
61
|
+
if (!auth) {
|
|
62
|
+
return {
|
|
63
|
+
disabled: true,
|
|
64
|
+
options: [],
|
|
65
|
+
placeholder: 'please authenticate first',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const response = await GmailRequests.getLabels(auth);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
disabled: false,
|
|
73
|
+
options: response.body.labels.map((label) => ({
|
|
74
|
+
label: label.name,
|
|
75
|
+
value: label,
|
|
76
|
+
})),
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
unread: (required = false) =>
|
|
81
|
+
Property.Checkbox({
|
|
82
|
+
displayName: 'Is unread?',
|
|
83
|
+
description: 'Check if the email is unread or not',
|
|
84
|
+
required,
|
|
85
|
+
defaultValue: false,
|
|
86
|
+
}),
|
|
87
|
+
message: Property.Dropdown({
|
|
88
|
+
displayName: 'Message',
|
|
89
|
+
description:
|
|
90
|
+
'Select a message from the list or enter a message ID manually.',
|
|
91
|
+
required: true,
|
|
92
|
+
auth: gmailAuth,
|
|
93
|
+
refreshers: [],
|
|
94
|
+
options: async ({ auth }) => {
|
|
95
|
+
if (!auth) {
|
|
96
|
+
return {
|
|
97
|
+
disabled: true,
|
|
98
|
+
options: [],
|
|
99
|
+
placeholder: 'Please authenticate first',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const authValue = auth as GmailAuthValue;
|
|
105
|
+
const authClient = await createGoogleClient(authValue);
|
|
106
|
+
|
|
107
|
+
const gmail = googleGmail({ version: 'v1', auth: authClient });
|
|
108
|
+
|
|
109
|
+
const response = await GmailRequests.getRecentMessages(
|
|
110
|
+
authValue,
|
|
111
|
+
20 // Get last 20 messages
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (!response.body.messages || response.body.messages.length === 0) {
|
|
115
|
+
return {
|
|
116
|
+
disabled: false,
|
|
117
|
+
options: [],
|
|
118
|
+
placeholder:
|
|
119
|
+
'No recent messages found. You can enter a message ID manually.',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Get message details for better display
|
|
124
|
+
const messageDetails = await Promise.all(
|
|
125
|
+
response.body.messages
|
|
126
|
+
.slice(0, 10)
|
|
127
|
+
.map(async (msg: { id: string; threadId: string }) => {
|
|
128
|
+
try {
|
|
129
|
+
const details = await gmail.users.messages.get({
|
|
130
|
+
metadataHeaders: ['Subject'],
|
|
131
|
+
format: 'metadata',
|
|
132
|
+
id: msg.id,
|
|
133
|
+
userId: 'me',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const headers = details.data.payload?.headers || [];
|
|
137
|
+
const subject =
|
|
138
|
+
headers.find((h: any) => h.name === 'Subject')?.value ||
|
|
139
|
+
'No Subject';
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
id: msg.id,
|
|
143
|
+
subject:
|
|
144
|
+
subject.length > 50
|
|
145
|
+
? subject.substring(0, 50) + '...'
|
|
146
|
+
: subject,
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.log(error);
|
|
150
|
+
return {
|
|
151
|
+
id: msg.id,
|
|
152
|
+
subject: 'Unable to load details',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
disabled: false,
|
|
160
|
+
options: messageDetails.map((msg) => ({
|
|
161
|
+
label: msg.subject,
|
|
162
|
+
value: msg.id,
|
|
163
|
+
})),
|
|
164
|
+
};
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return {
|
|
167
|
+
disabled: false,
|
|
168
|
+
options: [],
|
|
169
|
+
placeholder:
|
|
170
|
+
'Error loading recent messages. You can enter a message ID manually.',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
thread: Property.Dropdown({
|
|
176
|
+
displayName: 'Thread',
|
|
177
|
+
description: 'Select a thread from the list or enter a thread ID manually',
|
|
178
|
+
required: true,
|
|
179
|
+
refreshers: [],
|
|
180
|
+
auth: gmailAuth,
|
|
181
|
+
options: async ({ auth }) => {
|
|
182
|
+
if (!auth) {
|
|
183
|
+
return {
|
|
184
|
+
disabled: true,
|
|
185
|
+
options: [],
|
|
186
|
+
placeholder: 'Please authenticate first',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const authValue = auth as GmailAuthValue;
|
|
192
|
+
const authClient = await createGoogleClient(authValue);
|
|
193
|
+
|
|
194
|
+
const gmail = googleGmail({ version: 'v1', auth: authClient });
|
|
195
|
+
|
|
196
|
+
const response = await GmailRequests.getRecentThreads(
|
|
197
|
+
authValue,
|
|
198
|
+
15 // Get last 15 threads
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!response.body.threads || response.body.threads.length === 0) {
|
|
202
|
+
return {
|
|
203
|
+
disabled: false,
|
|
204
|
+
options: [],
|
|
205
|
+
placeholder:
|
|
206
|
+
'No recent threads found. You can enter a thread ID manually.',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Get thread details for better display
|
|
211
|
+
const threadDetails = await Promise.all(
|
|
212
|
+
response.body.threads
|
|
213
|
+
.slice(0, 10)
|
|
214
|
+
.map(async (thread: { id: string; snippet?: string }) => {
|
|
215
|
+
try {
|
|
216
|
+
const details = await await gmail.users.threads.get({
|
|
217
|
+
metadataHeaders: ['Subject'],
|
|
218
|
+
format: 'metadata',
|
|
219
|
+
id: thread.id,
|
|
220
|
+
userId: 'me',
|
|
221
|
+
});
|
|
222
|
+
// Get the first message to extract subject and participants
|
|
223
|
+
const firstMessage = details.data.messages?.[0];
|
|
224
|
+
const headers = firstMessage?.payload?.headers || [];
|
|
225
|
+
const subject =
|
|
226
|
+
headers.find((h: any) => h.name === 'Subject')?.value ||
|
|
227
|
+
'No Subject';
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
id: thread.id,
|
|
231
|
+
subject:
|
|
232
|
+
subject.length > 50
|
|
233
|
+
? subject.substring(0, 50) + '...'
|
|
234
|
+
: subject,
|
|
235
|
+
};
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return {
|
|
238
|
+
id: thread.id,
|
|
239
|
+
subject: 'Unable to load details',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
disabled: false,
|
|
247
|
+
options: threadDetails.map((thread) => ({
|
|
248
|
+
label: thread.subject,
|
|
249
|
+
value: thread.id,
|
|
250
|
+
})),
|
|
251
|
+
};
|
|
252
|
+
} catch (error) {
|
|
253
|
+
return {
|
|
254
|
+
disabled: false,
|
|
255
|
+
options: [],
|
|
256
|
+
placeholder:
|
|
257
|
+
'Error loading recent threads. You can enter a thread ID manually.',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
};
|