@hasna/connectors 1.1.5 → 1.1.6

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.
Files changed (37) hide show
  1. package/bin/index.js +1 -1
  2. package/bin/mcp.js +1 -1
  3. package/connectors/connect-gmail/src/api/attachments.ts +143 -0
  4. package/connectors/connect-gmail/src/api/bulk.ts +713 -0
  5. package/connectors/connect-gmail/src/api/client.ts +131 -0
  6. package/connectors/connect-gmail/src/api/drafts.ts +198 -0
  7. package/connectors/connect-gmail/src/api/export.ts +312 -0
  8. package/connectors/connect-gmail/src/api/filters.ts +127 -0
  9. package/connectors/connect-gmail/src/api/index.ts +63 -0
  10. package/connectors/connect-gmail/src/api/labels.ts +123 -0
  11. package/connectors/connect-gmail/src/api/messages.ts +527 -0
  12. package/connectors/connect-gmail/src/api/profile.ts +72 -0
  13. package/connectors/connect-gmail/src/api/threads.ts +85 -0
  14. package/connectors/connect-gmail/src/cli/index.ts +2389 -0
  15. package/connectors/connect-gmail/src/index.ts +30 -0
  16. package/connectors/connect-gmail/src/types/index.ts +202 -0
  17. package/connectors/connect-gmail/src/utils/auth.ts +256 -0
  18. package/connectors/connect-gmail/src/utils/config.ts +466 -0
  19. package/connectors/connect-gmail/src/utils/contacts.ts +147 -0
  20. package/connectors/connect-gmail/src/utils/markdown.ts +119 -0
  21. package/connectors/connect-gmail/src/utils/output.ts +119 -0
  22. package/connectors/connect-gmail/src/utils/settings.ts +87 -0
  23. package/connectors/connect-gmail/tsconfig.json +16 -0
  24. package/connectors/connect-telegram/src/api/bot.ts +223 -0
  25. package/connectors/connect-telegram/src/api/chats.ts +290 -0
  26. package/connectors/connect-telegram/src/api/client.ts +134 -0
  27. package/connectors/connect-telegram/src/api/index.ts +66 -0
  28. package/connectors/connect-telegram/src/api/inline.ts +63 -0
  29. package/connectors/connect-telegram/src/api/messages.ts +781 -0
  30. package/connectors/connect-telegram/src/api/updates.ts +97 -0
  31. package/connectors/connect-telegram/src/cli/index.ts +690 -0
  32. package/connectors/connect-telegram/src/index.ts +22 -0
  33. package/connectors/connect-telegram/src/types/index.ts +617 -0
  34. package/connectors/connect-telegram/src/utils/config.ts +197 -0
  35. package/connectors/connect-telegram/src/utils/output.ts +119 -0
  36. package/connectors/connect-telegram/tsconfig.json +16 -0
  37. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ // Gmail API Connector
2
+ // A TypeScript wrapper for Gmail with OAuth2 authentication
3
+
4
+ export { Gmail } from './api';
5
+ export * from './types';
6
+
7
+ // Re-export individual API classes for advanced usage
8
+ export {
9
+ GmailClient,
10
+ MessagesApi,
11
+ LabelsApi,
12
+ ThreadsApi,
13
+ ProfileApi,
14
+ } from './api';
15
+
16
+ // Export auth utilities
17
+ export {
18
+ getAuthUrl,
19
+ startCallbackServer,
20
+ refreshAccessToken,
21
+ getValidAccessToken,
22
+ } from './utils/auth';
23
+
24
+ // Export config utilities
25
+ export {
26
+ isAuthenticated,
27
+ loadTokens,
28
+ saveTokens,
29
+ clearConfig,
30
+ } from './utils/config';
@@ -0,0 +1,202 @@
1
+ // Gmail API Connector Types
2
+
3
+ // ============================================
4
+ // Configuration
5
+ // ============================================
6
+
7
+ export interface GmailConfig {
8
+ clientId: string;
9
+ clientSecret: string;
10
+ accessToken?: string;
11
+ refreshToken?: string;
12
+ }
13
+
14
+ export interface OAuth2Tokens {
15
+ accessToken: string;
16
+ refreshToken: string;
17
+ expiresAt: number;
18
+ tokenType: string;
19
+ scope: string;
20
+ }
21
+
22
+ export interface CliConfig {
23
+ clientId?: string;
24
+ clientSecret?: string;
25
+ tokens?: OAuth2Tokens;
26
+ userEmail?: string;
27
+ userName?: string; // Display name for sending emails
28
+ }
29
+
30
+ // ============================================
31
+ // Common Types
32
+ // ============================================
33
+
34
+ export type OutputFormat = 'json' | 'table' | 'pretty';
35
+
36
+ // ============================================
37
+ // Gmail Message Types
38
+ // ============================================
39
+
40
+ export interface GmailMessage {
41
+ id: string;
42
+ threadId: string;
43
+ labelIds: string[];
44
+ snippet: string;
45
+ historyId: string;
46
+ internalDate: string;
47
+ payload?: MessagePart;
48
+ sizeEstimate: number;
49
+ raw?: string;
50
+ }
51
+
52
+ export interface MessagePart {
53
+ partId: string;
54
+ mimeType: string;
55
+ filename: string;
56
+ headers: MessageHeader[];
57
+ body: MessagePartBody;
58
+ parts?: MessagePart[];
59
+ }
60
+
61
+ export interface MessageHeader {
62
+ name: string;
63
+ value: string;
64
+ }
65
+
66
+ export interface MessagePartBody {
67
+ attachmentId?: string;
68
+ size: number;
69
+ data?: string;
70
+ }
71
+
72
+ export interface MessageListResponse {
73
+ messages: Array<{ id: string; threadId: string }>;
74
+ nextPageToken?: string;
75
+ resultSizeEstimate: number;
76
+ }
77
+
78
+ // ============================================
79
+ // Gmail Label Types
80
+ // ============================================
81
+
82
+ export interface GmailLabel {
83
+ id: string;
84
+ name: string;
85
+ messageListVisibility?: 'show' | 'hide';
86
+ labelListVisibility?: 'labelShow' | 'labelShowIfUnread' | 'labelHide';
87
+ type: 'system' | 'user';
88
+ messagesTotal?: number;
89
+ messagesUnread?: number;
90
+ threadsTotal?: number;
91
+ threadsUnread?: number;
92
+ color?: LabelColor;
93
+ }
94
+
95
+ export interface LabelColor {
96
+ textColor: string;
97
+ backgroundColor: string;
98
+ }
99
+
100
+ // ============================================
101
+ // Gmail Thread Types
102
+ // ============================================
103
+
104
+ export interface GmailThread {
105
+ id: string;
106
+ historyId: string;
107
+ messages: GmailMessage[];
108
+ snippet?: string;
109
+ }
110
+
111
+ export interface ThreadListResponse {
112
+ threads: Array<{ id: string; snippet: string; historyId: string }>;
113
+ nextPageToken?: string;
114
+ resultSizeEstimate: number;
115
+ }
116
+
117
+ // ============================================
118
+ // Gmail Draft Types
119
+ // ============================================
120
+
121
+ export interface GmailDraft {
122
+ id: string;
123
+ message: GmailMessage;
124
+ }
125
+
126
+ // ============================================
127
+ // Send Message Types
128
+ // ============================================
129
+
130
+ export interface SendMessageOptions {
131
+ to: string | string[];
132
+ cc?: string | string[];
133
+ bcc?: string | string[];
134
+ subject: string;
135
+ body: string;
136
+ isHtml?: boolean;
137
+ threadId?: string;
138
+ attachments?: Attachment[];
139
+ }
140
+
141
+ export interface Attachment {
142
+ filename: string;
143
+ mimeType: string;
144
+ data: string; // base64 encoded
145
+ }
146
+
147
+ // ============================================
148
+ // Search/Query Types
149
+ // ============================================
150
+
151
+ export interface ListMessagesOptions {
152
+ maxResults?: number;
153
+ pageToken?: string;
154
+ q?: string; // Gmail search query
155
+ labelIds?: string[];
156
+ includeSpamTrash?: boolean;
157
+ }
158
+
159
+ export interface ListThreadsOptions {
160
+ maxResults?: number;
161
+ pageToken?: string;
162
+ q?: string;
163
+ labelIds?: string[];
164
+ includeSpamTrash?: boolean;
165
+ }
166
+
167
+ // ============================================
168
+ // Profile Types
169
+ // ============================================
170
+
171
+ export interface GmailProfile {
172
+ emailAddress: string;
173
+ messagesTotal: number;
174
+ threadsTotal: number;
175
+ historyId: string;
176
+ }
177
+
178
+ // ============================================
179
+ // API Error Types
180
+ // ============================================
181
+
182
+ export interface GmailError {
183
+ code: number;
184
+ message: string;
185
+ errors?: Array<{
186
+ domain: string;
187
+ reason: string;
188
+ message: string;
189
+ }>;
190
+ }
191
+
192
+ export class GmailApiError extends Error {
193
+ public readonly statusCode: number;
194
+ public readonly errors?: GmailError['errors'];
195
+
196
+ constructor(message: string, statusCode: number, errors?: GmailError['errors']) {
197
+ super(message);
198
+ this.name = 'GmailApiError';
199
+ this.statusCode = statusCode;
200
+ this.errors = errors;
201
+ }
202
+ }
@@ -0,0 +1,256 @@
1
+ import { createServer } from 'http';
2
+ import type { OAuth2Tokens } from '../types';
3
+ import { saveTokens, getClientId, getClientSecret, loadTokens } from './config';
4
+
5
+ // Google OAuth2 endpoints
6
+ const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
7
+ const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
8
+
9
+ // Gmail API scopes
10
+ const GMAIL_SCOPES = [
11
+ 'https://www.googleapis.com/auth/gmail.readonly',
12
+ 'https://www.googleapis.com/auth/gmail.send',
13
+ 'https://www.googleapis.com/auth/gmail.compose',
14
+ 'https://www.googleapis.com/auth/gmail.modify',
15
+ 'https://www.googleapis.com/auth/gmail.labels',
16
+ 'https://www.googleapis.com/auth/gmail.settings.basic', // Filter management
17
+ 'https://mail.google.com/', // Full access
18
+ ].join(' ');
19
+
20
+ const REDIRECT_PORT = 8089;
21
+ // For Desktop/Installed apps, use loopback IP without path
22
+ const REDIRECT_URI = `http://127.0.0.1:${REDIRECT_PORT}`;
23
+
24
+ export interface AuthResult {
25
+ success: boolean;
26
+ tokens?: OAuth2Tokens;
27
+ error?: string;
28
+ }
29
+
30
+ /**
31
+ * Generate the OAuth2 authorization URL
32
+ */
33
+ export function getAuthUrl(): string {
34
+ const clientId = getClientId();
35
+ if (!clientId) {
36
+ throw new Error('Client ID not configured. Run "connect-gmail config set-credentials" first.');
37
+ }
38
+
39
+ const params = new URLSearchParams({
40
+ client_id: clientId,
41
+ redirect_uri: REDIRECT_URI,
42
+ response_type: 'code',
43
+ scope: GMAIL_SCOPES,
44
+ access_type: 'offline',
45
+ prompt: 'consent', // Force consent to get refresh token
46
+ });
47
+
48
+ return `${GOOGLE_AUTH_URL}?${params.toString()}`;
49
+ }
50
+
51
+ /**
52
+ * Exchange authorization code for tokens
53
+ */
54
+ export async function exchangeCodeForTokens(code: string): Promise<OAuth2Tokens> {
55
+ const clientId = getClientId();
56
+ const clientSecret = getClientSecret();
57
+
58
+ if (!clientId || !clientSecret) {
59
+ throw new Error('OAuth credentials not configured');
60
+ }
61
+
62
+ const response = await fetch(GOOGLE_TOKEN_URL, {
63
+ method: 'POST',
64
+ headers: {
65
+ 'Content-Type': 'application/x-www-form-urlencoded',
66
+ },
67
+ body: new URLSearchParams({
68
+ code,
69
+ client_id: clientId,
70
+ client_secret: clientSecret,
71
+ redirect_uri: REDIRECT_URI,
72
+ grant_type: 'authorization_code',
73
+ }),
74
+ });
75
+
76
+ if (!response.ok) {
77
+ const errorText = await response.text();
78
+ let errorMessage = `Token exchange failed: ${response.status} ${response.statusText}`;
79
+ try {
80
+ const error = JSON.parse(errorText);
81
+ errorMessage = `Token exchange failed: ${error.error_description || error.error || response.statusText}`;
82
+ console.error('Token exchange error details:', error);
83
+ } catch {
84
+ errorMessage = `Token exchange failed: ${errorText || response.statusText}`;
85
+ }
86
+ throw new Error(errorMessage);
87
+ }
88
+
89
+ const data = await response.json();
90
+
91
+ const tokens: OAuth2Tokens = {
92
+ accessToken: data.access_token,
93
+ refreshToken: data.refresh_token,
94
+ expiresAt: Date.now() + data.expires_in * 1000,
95
+ tokenType: data.token_type,
96
+ scope: data.scope,
97
+ };
98
+
99
+ // Don't save here - let CLI save after determining correct profile from email
100
+ return tokens;
101
+ }
102
+
103
+ /**
104
+ * Refresh the access token using the refresh token
105
+ */
106
+ export async function refreshAccessToken(): Promise<OAuth2Tokens> {
107
+ const clientId = getClientId();
108
+ const clientSecret = getClientSecret();
109
+ const currentTokens = loadTokens();
110
+
111
+ if (!clientId || !clientSecret) {
112
+ throw new Error('OAuth credentials not configured');
113
+ }
114
+
115
+ if (!currentTokens?.refreshToken) {
116
+ throw new Error('No refresh token available. Please login again.');
117
+ }
118
+
119
+ const response = await fetch(GOOGLE_TOKEN_URL, {
120
+ method: 'POST',
121
+ headers: {
122
+ 'Content-Type': 'application/x-www-form-urlencoded',
123
+ },
124
+ body: new URLSearchParams({
125
+ client_id: clientId,
126
+ client_secret: clientSecret,
127
+ refresh_token: currentTokens.refreshToken,
128
+ grant_type: 'refresh_token',
129
+ }),
130
+ });
131
+
132
+ if (!response.ok) {
133
+ let errorMessage = `Token refresh failed: ${response.status} ${response.statusText}`;
134
+ try {
135
+ const errorBody = await response.json();
136
+ const detail = errorBody.error_description || errorBody.error;
137
+ if (detail) {
138
+ errorMessage = `Token refresh failed: ${detail}. Please run "connect-gmail auth login" again.`;
139
+ }
140
+ } catch {
141
+ // Response was not JSON; keep the status-based message
142
+ }
143
+ throw new Error(errorMessage);
144
+ }
145
+
146
+ const data = await response.json();
147
+
148
+ const tokens: OAuth2Tokens = {
149
+ accessToken: data.access_token,
150
+ refreshToken: currentTokens.refreshToken, // Keep the original refresh token
151
+ expiresAt: Date.now() + data.expires_in * 1000,
152
+ tokenType: data.token_type,
153
+ scope: data.scope || currentTokens.scope,
154
+ };
155
+
156
+ saveTokens(tokens);
157
+ return tokens;
158
+ }
159
+
160
+ /**
161
+ * Start a local HTTP server to receive the OAuth callback
162
+ */
163
+ export function startCallbackServer(): Promise<AuthResult> {
164
+ return new Promise((resolve) => {
165
+ const server = createServer(async (req, res) => {
166
+ const url = new URL(req.url || '', `http://127.0.0.1:${REDIRECT_PORT}`);
167
+
168
+ // Accept both root path and /callback for flexibility
169
+ if (url.pathname === '/' || url.pathname === '/callback') {
170
+ const code = url.searchParams.get('code');
171
+ const error = url.searchParams.get('error');
172
+
173
+ if (error) {
174
+ res.writeHead(200, { 'Content-Type': 'text/html' });
175
+ res.end(`
176
+ <html>
177
+ <body style="font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
178
+ <div style="text-align: center;">
179
+ <h1 style="color: #dc3545;">Authentication Failed</h1>
180
+ <p>Error: ${error}</p>
181
+ <p>You can close this window.</p>
182
+ </div>
183
+ </body>
184
+ </html>
185
+ `);
186
+ server.close();
187
+ resolve({ success: false, error });
188
+ return;
189
+ }
190
+
191
+ if (code) {
192
+ try {
193
+ const tokens = await exchangeCodeForTokens(code);
194
+ res.writeHead(200, { 'Content-Type': 'text/html' });
195
+ res.end(`
196
+ <html>
197
+ <body style="font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
198
+ <div style="text-align: center;">
199
+ <h1 style="color: #28a745;">Authentication Successful!</h1>
200
+ <p>You can close this window and return to the terminal.</p>
201
+ </div>
202
+ </body>
203
+ </html>
204
+ `);
205
+ server.close();
206
+ resolve({ success: true, tokens });
207
+ } catch (err) {
208
+ res.writeHead(200, { 'Content-Type': 'text/html' });
209
+ res.end(`
210
+ <html>
211
+ <body style="font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
212
+ <div style="text-align: center;">
213
+ <h1 style="color: #dc3545;">Authentication Failed</h1>
214
+ <p>Error: ${String(err)}</p>
215
+ <p>You can close this window.</p>
216
+ </div>
217
+ </body>
218
+ </html>
219
+ `);
220
+ server.close();
221
+ resolve({ success: false, error: String(err) });
222
+ }
223
+ }
224
+ }
225
+ });
226
+
227
+ server.listen(REDIRECT_PORT, () => {
228
+ // Server is ready
229
+ });
230
+
231
+ // Timeout after 5 minutes
232
+ setTimeout(() => {
233
+ server.close();
234
+ resolve({ success: false, error: 'Authentication timed out' });
235
+ }, 5 * 60 * 1000);
236
+ });
237
+ }
238
+
239
+ /**
240
+ * Get a valid access token, refreshing if necessary
241
+ */
242
+ export async function getValidAccessToken(): Promise<string> {
243
+ const tokens = loadTokens();
244
+
245
+ if (!tokens) {
246
+ throw new Error('Not authenticated. Run "connect-gmail auth login" first.');
247
+ }
248
+
249
+ // Check if token is expired or will expire in next 5 minutes
250
+ if (Date.now() >= tokens.expiresAt - 5 * 60 * 1000) {
251
+ const newTokens = await refreshAccessToken();
252
+ return newTokens.accessToken;
253
+ }
254
+
255
+ return tokens.accessToken;
256
+ }