@sinoia/hubdoc-tools 1.3.1 → 1.3.3

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 (133) hide show
  1. package/dist/api/api/chunked-uploads-api.d.ts +214 -0
  2. package/dist/api/api/chunked-uploads-api.d.ts.map +1 -0
  3. package/dist/api/api/chunked-uploads-api.js +420 -0
  4. package/dist/api/api/chunked-uploads-api.js.map +1 -0
  5. package/dist/api/api.d.ts +1 -0
  6. package/dist/api/api.d.ts.map +1 -1
  7. package/dist/api/api.js +1 -0
  8. package/dist/api/api.js.map +1 -1
  9. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response-data.d.ts +19 -0
  10. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response-data.d.ts.map +1 -0
  11. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response-data.js +20 -0
  12. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response-data.js.map +1 -0
  13. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response.d.ts +17 -0
  14. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response.d.ts.map +1 -0
  15. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response.js +16 -0
  16. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete200-response.js.map +1 -0
  17. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete422-response.d.ts +16 -0
  18. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete422-response.d.ts.map +1 -0
  19. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete422-response.js +16 -0
  20. package/dist/api/models/api-v1-documents-chunked-uploads-id-cancel-delete422-response.js.map +1 -0
  21. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch200-response.d.ts +17 -0
  22. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch200-response.d.ts.map +1 -0
  23. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch200-response.js +16 -0
  24. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch200-response.js.map +1 -0
  25. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch400-response.d.ts +16 -0
  26. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch400-response.d.ts.map +1 -0
  27. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch400-response.js +16 -0
  28. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch400-response.js.map +1 -0
  29. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch410-response.d.ts +16 -0
  30. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch410-response.d.ts.map +1 -0
  31. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch410-response.js +16 -0
  32. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch410-response.js.map +1 -0
  33. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch422-response.d.ts +16 -0
  34. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch422-response.d.ts.map +1 -0
  35. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch422-response.js +16 -0
  36. package/dist/api/models/api-v1-documents-chunked-uploads-id-chunks-chunk-number-patch422-response.js.map +1 -0
  37. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post200-response.d.ts +17 -0
  38. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post200-response.d.ts.map +1 -0
  39. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post200-response.js +16 -0
  40. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post200-response.js.map +1 -0
  41. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post422-response.d.ts +16 -0
  42. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post422-response.d.ts.map +1 -0
  43. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post422-response.js +16 -0
  44. package/dist/api/models/api-v1-documents-chunked-uploads-id-complete-post422-response.js.map +1 -0
  45. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get200-response.d.ts +17 -0
  46. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get200-response.d.ts.map +1 -0
  47. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get200-response.js +16 -0
  48. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get200-response.js.map +1 -0
  49. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get404-response.d.ts +16 -0
  50. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get404-response.d.ts.map +1 -0
  51. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get404-response.js +16 -0
  52. package/dist/api/models/api-v1-documents-chunked-uploads-id-status-get404-response.js.map +1 -0
  53. package/dist/api/models/api-v1-documents-chunked-uploads-post201-response.d.ts +17 -0
  54. package/dist/api/models/api-v1-documents-chunked-uploads-post201-response.d.ts.map +1 -0
  55. package/dist/api/models/api-v1-documents-chunked-uploads-post201-response.js +16 -0
  56. package/dist/api/models/api-v1-documents-chunked-uploads-post201-response.js.map +1 -0
  57. package/dist/api/models/chunked-upload-chunk-response.d.ts +46 -0
  58. package/dist/api/models/chunked-upload-chunk-response.d.ts.map +1 -0
  59. package/dist/api/models/chunked-upload-chunk-response.js +21 -0
  60. package/dist/api/models/chunked-upload-chunk-response.js.map +1 -0
  61. package/dist/api/models/chunked-upload-complete-request.d.ts +21 -0
  62. package/dist/api/models/chunked-upload-complete-request.d.ts.map +1 -0
  63. package/dist/api/models/chunked-upload-complete-request.js +16 -0
  64. package/dist/api/models/chunked-upload-complete-request.js.map +1 -0
  65. package/dist/api/models/chunked-upload-complete-response.d.ts +37 -0
  66. package/dist/api/models/chunked-upload-complete-response.d.ts.map +1 -0
  67. package/dist/api/models/chunked-upload-complete-response.js +20 -0
  68. package/dist/api/models/chunked-upload-complete-response.js.map +1 -0
  69. package/dist/api/models/chunked-upload-mutation.d.ts +47 -0
  70. package/dist/api/models/chunked-upload-mutation.d.ts.map +1 -0
  71. package/dist/api/models/chunked-upload-mutation.js +16 -0
  72. package/dist/api/models/chunked-upload-mutation.js.map +1 -0
  73. package/dist/api/models/chunked-upload-session-response.d.ts +33 -0
  74. package/dist/api/models/chunked-upload-session-response.d.ts.map +1 -0
  75. package/dist/api/models/chunked-upload-session-response.js +16 -0
  76. package/dist/api/models/chunked-upload-session-response.js.map +1 -0
  77. package/dist/api/models/chunked-upload-status-response.d.ts +54 -0
  78. package/dist/api/models/chunked-upload-status-response.d.ts.map +1 -0
  79. package/dist/api/models/chunked-upload-status-response.js +25 -0
  80. package/dist/api/models/chunked-upload-status-response.js.map +1 -0
  81. package/dist/api/models/chunked-upload.d.ts +98 -0
  82. package/dist/api/models/chunked-upload.d.ts.map +1 -0
  83. package/dist/api/models/chunked-upload.js +25 -0
  84. package/dist/api/models/chunked-upload.js.map +1 -0
  85. package/dist/api/models/index.d.ts +19 -0
  86. package/dist/api/models/index.d.ts.map +1 -1
  87. package/dist/api/models/index.js +19 -0
  88. package/dist/api/models/index.js.map +1 -1
  89. package/dist/commands/import.d.ts.map +1 -1
  90. package/dist/commands/import.js +7 -3
  91. package/dist/commands/import.js.map +1 -1
  92. package/dist/services/chunked-uploader.d.ts +83 -0
  93. package/dist/services/chunked-uploader.d.ts.map +1 -0
  94. package/dist/services/chunked-uploader.js +321 -0
  95. package/dist/services/chunked-uploader.js.map +1 -0
  96. package/dist/services/hubdoc-api.d.ts +5 -2
  97. package/dist/services/hubdoc-api.d.ts.map +1 -1
  98. package/dist/services/hubdoc-api.js +49 -12
  99. package/dist/services/hubdoc-api.js.map +1 -1
  100. package/dist/types/index.d.ts +1 -0
  101. package/dist/types/index.d.ts.map +1 -1
  102. package/dist/utils/csv.d.ts +6 -1
  103. package/dist/utils/csv.d.ts.map +1 -1
  104. package/dist/utils/csv.js +30 -1
  105. package/dist/utils/csv.js.map +1 -1
  106. package/package.json +2 -1
  107. package/plugins/alfresco/index.ts +518 -0
  108. package/plugins/alfresco/plugin.json +12 -0
  109. package/plugins/aws-s3/index.ts +471 -0
  110. package/plugins/aws-s3/plugin.json +12 -0
  111. package/plugins/azure-blob/index.ts +420 -0
  112. package/plugins/azure-blob/plugin.json +12 -0
  113. package/plugins/box/index.ts +495 -0
  114. package/plugins/box/plugin.json +12 -0
  115. package/plugins/core/README.md +122 -0
  116. package/plugins/core/TESTING.md +155 -0
  117. package/plugins/core/index.ts +510 -0
  118. package/plugins/core/plugin.json +26 -0
  119. package/plugins/dropbox/index.ts +451 -0
  120. package/plugins/dropbox/plugin.json +12 -0
  121. package/plugins/filesystem/index.ts +360 -0
  122. package/plugins/filesystem/plugin.json +12 -0
  123. package/plugins/googledrive/index.ts +463 -0
  124. package/plugins/googledrive/plugin.json +12 -0
  125. package/plugins/nuxeo/index.ts +512 -0
  126. package/plugins/nuxeo/plugin.json +12 -0
  127. package/plugins/onedrive/TESTING.md +197 -0
  128. package/plugins/onedrive/index.ts +447 -0
  129. package/plugins/onedrive/plugin.json +12 -0
  130. package/plugins/opentext/index.ts +542 -0
  131. package/plugins/opentext/plugin.json +12 -0
  132. package/plugins/sharepoint/index.ts +509 -0
  133. package/plugins/sharepoint/plugin.json +12 -0
@@ -0,0 +1,451 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import {
5
+ DocumentSourcePlugin,
6
+ DocumentSource,
7
+ PluginConfig,
8
+ ScanResult,
9
+ PluginImportOptions,
10
+ PluginExportOptions,
11
+ ImportResult,
12
+ ExportResult
13
+ } from '../../src/types/plugins';
14
+
15
+ interface DropboxConfig extends PluginConfig {
16
+ accessToken: string;
17
+ appKey?: string;
18
+ appSecret?: string;
19
+ rootPath?: string; // e.g., '/Documents' or ''
20
+ }
21
+
22
+ interface DropboxFileMetadata {
23
+ '.tag': 'file' | 'folder';
24
+ name: string;
25
+ path_lower: string;
26
+ path_display: string;
27
+ id: string;
28
+ size?: number;
29
+ server_modified?: string;
30
+ client_modified?: string;
31
+ content_hash?: string;
32
+ }
33
+
34
+ export default class DropboxPlugin implements DocumentSourcePlugin {
35
+ readonly name = 'dropbox';
36
+ readonly version = '1.0.0';
37
+ readonly description = 'Dropbox document source';
38
+ readonly supportedOperations = ['import', 'export', 'both'] as const;
39
+
40
+ private config?: DropboxConfig;
41
+ private apiClient?: AxiosInstance;
42
+ private readonly apiUrl = 'https://api.dropboxapi.com/2';
43
+ private readonly contentUrl = 'https://content.dropboxapi.com/2';
44
+
45
+ async testConnection(config: PluginConfig): Promise<boolean> {
46
+ try {
47
+ const client = this.createApiClient(config as DropboxConfig);
48
+ const response = await client.post('/users/get_current_account');
49
+ return response.status === 200;
50
+ } catch (error: any) {
51
+ console.error(`Dropbox connection test failed: ${error.message}`);
52
+ return false;
53
+ }
54
+ }
55
+
56
+ async scan(config: PluginConfig, options?: PluginImportOptions): Promise<ScanResult> {
57
+ this.config = config as DropboxConfig;
58
+ this.apiClient = this.createApiClient(this.config);
59
+
60
+ const sources: DocumentSource[] = [];
61
+ const errors: string[] = [];
62
+ let totalSize = 0;
63
+
64
+ try {
65
+ const rootPath = this.config.rootPath || '';
66
+ const items = await this.scanFolder(rootPath, options);
67
+
68
+ for (const item of items) {
69
+ if (item['.tag'] === 'file') {
70
+ const source: DocumentSource = {
71
+ id: item.path_display,
72
+ name: item.name,
73
+ path: this.getRelativePath(item.path_display, rootPath),
74
+ size: item.size || 0,
75
+ mimeType: this.getMimeType(item.name),
76
+ lastModified: new Date(item.server_modified || item.client_modified || Date.now()),
77
+ metadata: {
78
+ dropboxId: item.id,
79
+ pathLower: item.path_lower,
80
+ contentHash: item.content_hash
81
+ }
82
+ };
83
+
84
+ // Apply filters
85
+ if (this.shouldIncludeSource(source, options)) {
86
+ sources.push(source);
87
+ totalSize += source.size;
88
+ }
89
+ }
90
+ }
91
+
92
+ return {
93
+ sources,
94
+ totalCount: sources.length,
95
+ totalSize,
96
+ errors
97
+ };
98
+ } catch (error: any) {
99
+ return {
100
+ sources: [],
101
+ totalCount: 0,
102
+ totalSize: 0,
103
+ errors: [`Dropbox scan failed: ${error.message}`]
104
+ };
105
+ }
106
+ }
107
+
108
+ private async scanFolder(folderPath: string, options?: PluginImportOptions): Promise<DropboxFileMetadata[]> {
109
+ if (!this.apiClient) throw new Error('API client not initialized');
110
+
111
+ const allItems: DropboxFileMetadata[] = [];
112
+ let cursor: string | undefined;
113
+
114
+ try {
115
+ // Initial request
116
+ const response = await this.apiClient.post('/files/list_folder', {
117
+ path: folderPath || '',
118
+ recursive: true,
119
+ limit: 2000,
120
+ include_media_info: false,
121
+ include_deleted: false,
122
+ include_has_explicit_shared_members: false
123
+ });
124
+
125
+ allItems.push(...response.data.entries);
126
+
127
+ // Handle pagination
128
+ while (response.data.has_more) {
129
+ cursor = response.data.cursor;
130
+ const continueResponse = await this.apiClient.post('/files/list_folder/continue', {
131
+ cursor
132
+ });
133
+
134
+ allItems.push(...continueResponse.data.entries);
135
+
136
+ if (!continueResponse.data.has_more) break;
137
+ response.data = continueResponse.data;
138
+ }
139
+
140
+ return allItems;
141
+ } catch (error: any) {
142
+ if (error.response?.data?.error?.['.tag'] === 'path_not_found') {
143
+ console.warn(`Warning: Folder not found: ${folderPath}`);
144
+ return [];
145
+ }
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ async import(
151
+ config: PluginConfig,
152
+ sources: DocumentSource[],
153
+ targetDir: string,
154
+ options?: PluginImportOptions
155
+ ): Promise<ImportResult[]> {
156
+ this.config = config as DropboxConfig;
157
+ this.apiClient = this.createApiClient(this.config);
158
+
159
+ const results: ImportResult[] = [];
160
+ const batchSize = options?.batchSize || 10;
161
+
162
+ // Process in batches
163
+ for (let i = 0; i < sources.length; i += batchSize) {
164
+ const batch = sources.slice(i, i + batchSize);
165
+
166
+ for (const source of batch) {
167
+ const result = await this.importSingle(source, targetDir);
168
+ results.push(result);
169
+
170
+ // Small delay to respect rate limits
171
+ await this.sleep(100);
172
+ }
173
+ }
174
+
175
+ return results;
176
+ }
177
+
178
+ private async importSingle(source: DocumentSource, targetDir: string): Promise<ImportResult> {
179
+ try {
180
+ if (!this.apiClient) throw new Error('API client not initialized');
181
+
182
+ const targetPath = path.join(targetDir, source.path);
183
+ const targetDirectory = path.dirname(targetPath);
184
+
185
+ await fs.ensureDir(targetDirectory);
186
+
187
+ // Download file from Dropbox
188
+ const downloadClient = axios.create({
189
+ baseURL: this.contentUrl,
190
+ headers: {
191
+ 'Authorization': `Bearer ${this.config!.accessToken}`,
192
+ 'Dropbox-API-Arg': JSON.stringify({
193
+ path: source.id
194
+ })
195
+ },
196
+ responseType: 'stream'
197
+ });
198
+
199
+ const response = await downloadClient.post('/files/download');
200
+
201
+ const writer = fs.createWriteStream(targetPath);
202
+ response.data.pipe(writer);
203
+
204
+ return new Promise((resolve) => {
205
+ writer.on('finish', () => {
206
+ resolve({
207
+ success: true,
208
+ source,
209
+ localPath: targetPath,
210
+ bytesTransferred: source.size
211
+ });
212
+ });
213
+
214
+ writer.on('error', (error) => {
215
+ resolve({
216
+ success: false,
217
+ source,
218
+ error: error.message
219
+ });
220
+ });
221
+ });
222
+ } catch (error: any) {
223
+ return {
224
+ success: false,
225
+ source,
226
+ error: error.message
227
+ };
228
+ }
229
+ }
230
+
231
+ async export?(
232
+ config: PluginConfig,
233
+ localSources: DocumentSource[],
234
+ options?: PluginExportOptions
235
+ ): Promise<ExportResult[]> {
236
+ this.config = config as DropboxConfig;
237
+ this.apiClient = this.createApiClient(this.config);
238
+
239
+ const results: ExportResult[] = [];
240
+
241
+ for (const source of localSources) {
242
+ try {
243
+ const targetPath = options?.preserveStructure
244
+ ? `${this.config.rootPath || ''}/${options.targetPath}/${source.path}`
245
+ : `${this.config.rootPath || ''}/${options?.targetPath || ''}/${source.name}`;
246
+
247
+ // Read file content
248
+ const fileContent = await fs.readFile(source.id);
249
+ const fileSize = fileContent.length;
250
+
251
+ // Upload file to Dropbox
252
+ const uploadClient = axios.create({
253
+ baseURL: this.contentUrl,
254
+ headers: {
255
+ 'Authorization': `Bearer ${this.config.accessToken}`,
256
+ 'Content-Type': 'application/octet-stream',
257
+ 'Dropbox-API-Arg': JSON.stringify({
258
+ path: targetPath,
259
+ mode: options?.overwrite ? 'overwrite' : 'add',
260
+ autorename: !options?.overwrite,
261
+ mute: false
262
+ })
263
+ }
264
+ });
265
+
266
+ // Use upload sessions for large files (>150MB)
267
+ if (fileSize > 150 * 1024 * 1024) {
268
+ await this.uploadLargeFile(uploadClient, fileContent, targetPath);
269
+ } else {
270
+ await uploadClient.post('/files/upload', fileContent);
271
+ }
272
+
273
+ results.push({
274
+ success: true,
275
+ targetPath,
276
+ source,
277
+ bytesTransferred: fileSize
278
+ });
279
+ } catch (error: any) {
280
+ results.push({
281
+ success: false,
282
+ targetPath: options?.targetPath || '',
283
+ source,
284
+ error: error.message
285
+ });
286
+ }
287
+ }
288
+
289
+ return results;
290
+ }
291
+
292
+ private async uploadLargeFile(uploadClient: AxiosInstance, content: Buffer, targetPath: string): Promise<void> {
293
+ const chunkSize = 4 * 1024 * 1024; // 4MB chunks
294
+ let offset = 0;
295
+ let sessionId: string | undefined;
296
+
297
+ while (offset < content.length) {
298
+ const chunk = content.slice(offset, offset + chunkSize);
299
+ const isLastChunk = offset + chunkSize >= content.length;
300
+
301
+ if (!sessionId) {
302
+ // Start upload session
303
+ const startResponse = await uploadClient.post('/files/upload_session/start', chunk, {
304
+ headers: {
305
+ 'Dropbox-API-Arg': JSON.stringify({ close: false })
306
+ }
307
+ });
308
+ sessionId = startResponse.data.session_id;
309
+ } else if (!isLastChunk) {
310
+ // Continue upload session
311
+ await uploadClient.post('/files/upload_session/append_v2', chunk, {
312
+ headers: {
313
+ 'Dropbox-API-Arg': JSON.stringify({
314
+ cursor: { session_id: sessionId, offset },
315
+ close: false
316
+ })
317
+ }
318
+ });
319
+ } else {
320
+ // Finish upload session
321
+ await uploadClient.post('/files/upload_session/finish', chunk, {
322
+ headers: {
323
+ 'Dropbox-API-Arg': JSON.stringify({
324
+ cursor: { session_id: sessionId, offset },
325
+ commit: {
326
+ path: targetPath,
327
+ mode: 'add',
328
+ autorename: true
329
+ }
330
+ })
331
+ }
332
+ });
333
+ }
334
+
335
+ offset += chunkSize;
336
+ }
337
+ }
338
+
339
+ getConfigSchema(): Record<string, any> {
340
+ return {
341
+ type: 'object',
342
+ properties: {
343
+ accessToken: {
344
+ type: 'string',
345
+ description: 'Dropbox Access Token',
346
+ required: true
347
+ },
348
+ appKey: {
349
+ type: 'string',
350
+ description: 'Dropbox App Key (optional)',
351
+ required: false
352
+ },
353
+ appSecret: {
354
+ type: 'string',
355
+ description: 'Dropbox App Secret (optional)',
356
+ required: false
357
+ },
358
+ rootPath: {
359
+ type: 'string',
360
+ description: 'Root path to scan (e.g., /Documents)',
361
+ default: ''
362
+ }
363
+ },
364
+ required: ['accessToken']
365
+ };
366
+ }
367
+
368
+ async initialize(config: PluginConfig): Promise<void> {
369
+ this.config = config as DropboxConfig;
370
+
371
+ if (!this.config.accessToken) {
372
+ throw new Error('Dropbox access token is required');
373
+ }
374
+
375
+ this.apiClient = this.createApiClient(this.config);
376
+ }
377
+
378
+ async destroy(): Promise<void> {
379
+ this.config = undefined;
380
+ this.apiClient = undefined;
381
+ }
382
+
383
+ private createApiClient(config: DropboxConfig): AxiosInstance {
384
+ return axios.create({
385
+ baseURL: this.apiUrl,
386
+ headers: {
387
+ 'Authorization': `Bearer ${config.accessToken}`,
388
+ 'Content-Type': 'application/json'
389
+ },
390
+ timeout: 30000
391
+ });
392
+ }
393
+
394
+ private getRelativePath(fullPath: string, rootPath: string): string {
395
+ if (!rootPath) return fullPath.startsWith('/') ? fullPath.substring(1) : fullPath;
396
+
397
+ const relativePath = fullPath.startsWith(rootPath)
398
+ ? fullPath.substring(rootPath.length)
399
+ : fullPath;
400
+
401
+ return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
402
+ }
403
+
404
+ private getMimeType(fileName: string): string {
405
+ const ext = path.extname(fileName).toLowerCase();
406
+ const mimeTypes: Record<string, string> = {
407
+ '.pdf': 'application/pdf',
408
+ '.doc': 'application/msword',
409
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
410
+ '.xls': 'application/vnd.ms-excel',
411
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
412
+ '.ppt': 'application/vnd.ms-powerpoint',
413
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
414
+ '.txt': 'text/plain',
415
+ '.csv': 'text/csv',
416
+ '.json': 'application/json',
417
+ '.jpg': 'image/jpeg',
418
+ '.jpeg': 'image/jpeg',
419
+ '.png': 'image/png',
420
+ '.gif': 'image/gif',
421
+ '.zip': 'application/zip'
422
+ };
423
+
424
+ return mimeTypes[ext] || 'application/octet-stream';
425
+ }
426
+
427
+ private shouldIncludeSource(source: DocumentSource, options?: PluginImportOptions): boolean {
428
+ // Apply size filter
429
+ if (options?.filters?.maxSize && source.size > options.filters.maxSize) {
430
+ return false;
431
+ }
432
+
433
+ // Apply date range filter
434
+ if (options?.filters?.dateRange) {
435
+ const { from, to } = options.filters.dateRange;
436
+ if (from && source.lastModified < from) return false;
437
+ if (to && source.lastModified > to) return false;
438
+ }
439
+
440
+ // Apply MIME type filter
441
+ if (options?.filters?.mimeTypes && !options.filters.mimeTypes.includes(source.mimeType)) {
442
+ return false;
443
+ }
444
+
445
+ return true;
446
+ }
447
+
448
+ private sleep(ms: number): Promise<void> {
449
+ return new Promise(resolve => setTimeout(resolve, ms));
450
+ }
451
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "dropbox",
3
+ "version": "1.0.0",
4
+ "description": "Dropbox document source plugin",
5
+ "author": "HubDoc Tools",
6
+ "main": "index.js",
7
+ "hubdocToolVersion": "^1.0.0",
8
+ "dependencies": {
9
+ "axios": "^1.5.0",
10
+ "fs-extra": "^11.1.0"
11
+ }
12
+ }