feat(new tool) multi-link-downloader
This commit is contained in:
		
							parent
							
								
									0b1b98f93e
								
							
						
					
					
						commit
						7035eba246
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -129,6 +129,7 @@ declare module '@vue/runtime-core' { | |||||||
|     MenuLayout: typeof import('./src/components/MenuLayout.vue')['default'] |     MenuLayout: typeof import('./src/components/MenuLayout.vue')['default'] | ||||||
|     MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] |     MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] | ||||||
|     MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] |     MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] | ||||||
|  |     MultiLinkDownloader: typeof import('./src/tools/multi-link-downloader/multi-link-downloader.vue')['default'] | ||||||
|     NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] |     NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] | ||||||
|     NCheckbox: typeof import('naive-ui')['NCheckbox'] |     NCheckbox: typeof import('naive-ui')['NCheckbox'] | ||||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] |     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||||
|  | |||||||
| @ -392,3 +392,7 @@ tools: | |||||||
|   text-to-binary: |   text-to-binary: | ||||||
|     title: Text to ASCII binary |     title: Text to ASCII binary | ||||||
|     description: Convert text to its ASCII binary representation and vice-versa. |     description: Convert text to its ASCII binary representation and vice-versa. | ||||||
|  | 
 | ||||||
|  |   multi-link-downloader: | ||||||
|  |     title: Multi link downloader | ||||||
|  |     description: Asynchronously downloads from multiple links into a zip file while a single link downloads directly. (Requires an internet connection) | ||||||
| @ -71,6 +71,7 @@ | |||||||
|     "ibantools": "^4.3.3", |     "ibantools": "^4.3.3", | ||||||
|     "js-base64": "^3.7.6", |     "js-base64": "^3.7.6", | ||||||
|     "json5": "^2.2.3", |     "json5": "^2.2.3", | ||||||
|  |     "jszip": "^3.10.1", | ||||||
|     "jwt-decode": "^3.1.2", |     "jwt-decode": "^3.1.2", | ||||||
|     "libphonenumber-js": "^1.10.28", |     "libphonenumber-js": "^1.10.28", | ||||||
|     "lodash": "^4.17.21", |     "lodash": "^4.17.21", | ||||||
|  | |||||||
							
								
								
									
										54
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										54
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @ -110,6 +110,9 @@ dependencies: | |||||||
|   json5: |   json5: | ||||||
|     specifier: ^2.2.3 |     specifier: ^2.2.3 | ||||||
|     version: 2.2.3 |     version: 2.2.3 | ||||||
|  |   jszip: | ||||||
|  |     specifier: ^3.10.1 | ||||||
|  |     version: 3.10.1 | ||||||
|   jwt-decode: |   jwt-decode: | ||||||
|     specifier: ^3.1.2 |     specifier: ^3.1.2 | ||||||
|     version: 3.1.2 |     version: 3.1.2 | ||||||
| @ -4674,6 +4677,9 @@ packages: | |||||||
|       browserslist: 4.22.1 |       browserslist: 4.22.1 | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /core-util-is@1.0.3: | ||||||
|  |     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} | ||||||
|  | 
 | ||||||
|   /country-code-lookup@0.1.0: |   /country-code-lookup@0.1.0: | ||||||
|     resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==} |     resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==} | ||||||
|     dev: false |     dev: false | ||||||
| @ -6195,6 +6201,9 @@ packages: | |||||||
|     dev: true |     dev: true | ||||||
|     optional: true |     optional: true | ||||||
| 
 | 
 | ||||||
|  |   /immediate@3.0.6: | ||||||
|  |     resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} | ||||||
|  | 
 | ||||||
|   /import-fresh@3.3.0: |   /import-fresh@3.3.0: | ||||||
|     resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} |     resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} | ||||||
|     engines: {node: '>=6'} |     engines: {node: '>=6'} | ||||||
| @ -6501,6 +6510,9 @@ packages: | |||||||
|       is-docker: 2.2.1 |       is-docker: 2.2.1 | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /isarray@1.0.0: | ||||||
|  |     resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} | ||||||
|  | 
 | ||||||
|   /isarray@2.0.5: |   /isarray@2.0.5: | ||||||
|     resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} |     resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} | ||||||
|     dev: true |     dev: true | ||||||
| @ -6695,6 +6707,14 @@ packages: | |||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /jszip@3.10.1: | ||||||
|  |     resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} | ||||||
|  |     dependencies: | ||||||
|  |       lie: 3.3.0 | ||||||
|  |       pako: 1.0.11 | ||||||
|  |       readable-stream: 2.3.8 | ||||||
|  |       setimmediate: 1.0.5 | ||||||
|  | 
 | ||||||
|   /kolorist@1.8.0: |   /kolorist@1.8.0: | ||||||
|     resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} |     resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} | ||||||
|     dev: true |     dev: true | ||||||
| @ -6735,6 +6755,11 @@ packages: | |||||||
|     resolution: {integrity: sha512-1eAgjLrZA0+2Wgw4hs+4Q/kEBycxQo8ZLYnmOvZ3AlM8ImAVAJgDPlZtISLEzD1vunc2q8s2Pn7XwB7I8U3Kzw==} |     resolution: {integrity: sha512-1eAgjLrZA0+2Wgw4hs+4Q/kEBycxQo8ZLYnmOvZ3AlM8ImAVAJgDPlZtISLEzD1vunc2q8s2Pn7XwB7I8U3Kzw==} | ||||||
|     dev: false |     dev: false | ||||||
| 
 | 
 | ||||||
|  |   /lie@3.3.0: | ||||||
|  |     resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} | ||||||
|  |     dependencies: | ||||||
|  |       immediate: 3.0.6 | ||||||
|  | 
 | ||||||
|   /lines-and-columns@1.2.4: |   /lines-and-columns@1.2.4: | ||||||
|     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} |     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} | ||||||
|     dev: true |     dev: true | ||||||
| @ -7341,6 +7366,9 @@ packages: | |||||||
|     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} |     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} | ||||||
|     engines: {node: '>=6'} |     engines: {node: '>=6'} | ||||||
| 
 | 
 | ||||||
|  |   /pako@1.0.11: | ||||||
|  |     resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} | ||||||
|  | 
 | ||||||
|   /param-case@2.1.1: |   /param-case@2.1.1: | ||||||
|     resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} |     resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} | ||||||
|     dependencies: |     dependencies: | ||||||
| @ -7580,6 +7608,7 @@ packages: | |||||||
|     engines: {node: ^14.13.1 || >=16.0.0} |     engines: {node: ^14.13.1 || >=16.0.0} | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|   /pretty-format@29.6.2: |   /pretty-format@29.6.2: | ||||||
|     resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==} |     resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==} | ||||||
|     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} |     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} | ||||||
| @ -7589,6 +7618,9 @@ packages: | |||||||
|       react-is: 18.2.0 |       react-is: 18.2.0 | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /process-nextick-args@2.0.1: | ||||||
|  |     resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} | ||||||
|  | 
 | ||||||
|   /prosemirror-changeset@2.2.1: |   /prosemirror-changeset@2.2.1: | ||||||
|     resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==} |     resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==} | ||||||
|     dependencies: |     dependencies: | ||||||
| @ -7841,6 +7873,17 @@ packages: | |||||||
|       type-fest: 0.6.0 |       type-fest: 0.6.0 | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /readable-stream@2.3.8: | ||||||
|  |     resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} | ||||||
|  |     dependencies: | ||||||
|  |       core-util-is: 1.0.3 | ||||||
|  |       inherits: 2.0.4 | ||||||
|  |       isarray: 1.0.0 | ||||||
|  |       process-nextick-args: 2.0.1 | ||||||
|  |       safe-buffer: 5.1.2 | ||||||
|  |       string_decoder: 1.1.1 | ||||||
|  |       util-deprecate: 1.0.2 | ||||||
|  | 
 | ||||||
|   /readable-stream@3.6.2: |   /readable-stream@3.6.2: | ||||||
|     resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} |     resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} | ||||||
|     engines: {node: '>= 6'} |     engines: {node: '>= 6'} | ||||||
| @ -8047,6 +8090,9 @@ packages: | |||||||
|       isarray: 2.0.5 |       isarray: 2.0.5 | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /safe-buffer@5.1.2: | ||||||
|  |     resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} | ||||||
|  | 
 | ||||||
|   /safe-buffer@5.2.1: |   /safe-buffer@5.2.1: | ||||||
|     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} |     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} | ||||||
|     dev: true |     dev: true | ||||||
| @ -8170,6 +8216,9 @@ packages: | |||||||
|       is-primitive: 3.0.1 |       is-primitive: 3.0.1 | ||||||
|     dev: false |     dev: false | ||||||
| 
 | 
 | ||||||
|  |   /setimmediate@1.0.5: | ||||||
|  |     resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} | ||||||
|  | 
 | ||||||
|   /shebang-command@2.0.0: |   /shebang-command@2.0.0: | ||||||
|     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} |     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} | ||||||
|     engines: {node: '>=8'} |     engines: {node: '>=8'} | ||||||
| @ -8376,6 +8425,11 @@ packages: | |||||||
|       es-abstract: 1.22.3 |       es-abstract: 1.22.3 | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /string_decoder@1.1.1: | ||||||
|  |     resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} | ||||||
|  |     dependencies: | ||||||
|  |       safe-buffer: 5.1.2 | ||||||
|  | 
 | ||||||
|   /string_decoder@1.3.0: |   /string_decoder@1.3.0: | ||||||
|     resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} |     resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} | ||||||
|     dependencies: |     dependencies: | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { tool as base64FileConverter } from './base64-file-converter'; | import { tool as base64FileConverter } from './base64-file-converter'; | ||||||
| import { tool as base64StringConverter } from './base64-string-converter'; | import { tool as base64StringConverter } from './base64-string-converter'; | ||||||
| import { tool as basicAuthGenerator } from './basic-auth-generator'; | import { tool as basicAuthGenerator } from './basic-auth-generator'; | ||||||
|  | import { tool as multiLinkDownloader } from './multi-link-downloader'; | ||||||
| import { tool as emailNormalizer } from './email-normalizer'; | import { tool as emailNormalizer } from './email-normalizer'; | ||||||
| 
 | 
 | ||||||
| import { tool as asciiTextDrawer } from './ascii-text-drawer'; | import { tool as asciiTextDrawer } from './ascii-text-drawer'; | ||||||
| @ -188,7 +189,11 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Data', |     name: 'Data', | ||||||
|     components: [phoneParserAndFormatter, ibanValidatorAndParser], |     components: [ | ||||||
|  |       phoneParserAndFormatter, | ||||||
|  |       ibanValidatorAndParser, | ||||||
|  |       multiLinkDownloader, | ||||||
|  |     ], | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								src/tools/multi-link-downloader/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/multi-link-downloader/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | import { IconFileDownload } from '@tabler/icons-vue'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'Multi link downloader', | ||||||
|  |   path: '/multi-link-downloader', | ||||||
|  |   description: '', | ||||||
|  |   keywords: ['multi', 'link', 'downloader'], | ||||||
|  |   component: () => import('./multi-link-downloader.vue'), | ||||||
|  |   icon: IconFileDownload, | ||||||
|  |   createdAt: new Date('2024-10-18'), | ||||||
|  | }); | ||||||
							
								
								
									
										108
									
								
								src/tools/multi-link-downloader/multi-link-downloader.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/tools/multi-link-downloader/multi-link-downloader.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | |||||||
|  | import JSZip from 'jszip'; | ||||||
|  | 
 | ||||||
|  | export async function downloadLinks(links: string): Promise<void> { | ||||||
|  |   // Split links by newline and filter out empty ones
 | ||||||
|  |   const linksArray: string[] = links.split('\n').filter(link => link.trim() !== ''); | ||||||
|  | 
 | ||||||
|  |   // Helper function to handle duplicate filenames
 | ||||||
|  |   function getUniqueFileName(existingNames: Set<string>, originalName: string): string { | ||||||
|  |     let fileName = originalName; | ||||||
|  |     let fileExtension = ''; | ||||||
|  | 
 | ||||||
|  |     // Split filename and extension (if any)
 | ||||||
|  |     const lastDotIndex = originalName.lastIndexOf('.'); | ||||||
|  |     if (lastDotIndex !== -1) { | ||||||
|  |       fileName = originalName.substring(0, lastDotIndex); | ||||||
|  |       fileExtension = originalName.substring(lastDotIndex); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let counter = 1; | ||||||
|  |     let uniqueName = originalName; | ||||||
|  | 
 | ||||||
|  |     // Append a counter to the filename if it already exists in the map
 | ||||||
|  |     while (existingNames.has(uniqueName)) { | ||||||
|  |       uniqueName = `${fileName} (${counter})${fileExtension}`; | ||||||
|  |       counter++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     existingNames.add(uniqueName); | ||||||
|  |     return uniqueName; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (linksArray.length === 1) { | ||||||
|  |     // Single link: download directly
 | ||||||
|  |     const linkUrl: string = linksArray[0]; | ||||||
|  |     try { | ||||||
|  |       const response: Response = await fetch(linkUrl); | ||||||
|  |       if (!response.ok) { | ||||||
|  |         throw new Error(`Failed to fetch ${linkUrl}`); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Get file as blob
 | ||||||
|  |       const blob: Blob = await response.blob(); | ||||||
|  | 
 | ||||||
|  |       // Extract filename from URL
 | ||||||
|  |       const fileName: string = linkUrl.split('/').pop() || 'downloaded_file'; | ||||||
|  | 
 | ||||||
|  |       // Trigger download
 | ||||||
|  |       const a: HTMLAnchorElement = document.createElement('a'); | ||||||
|  |       const downloadUrl: string = window.URL.createObjectURL(blob); | ||||||
|  |       a.href = downloadUrl; | ||||||
|  |       a.download = fileName; | ||||||
|  |       document.body.appendChild(a); | ||||||
|  |       a.click(); | ||||||
|  | 
 | ||||||
|  |       // Clean up
 | ||||||
|  |       document.body.removeChild(a); | ||||||
|  |       window.URL.revokeObjectURL(downloadUrl); | ||||||
|  |     } | ||||||
|  |     catch (error) { | ||||||
|  |       console.error('Error downloading the file:', error); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   else if (linksArray.length > 1) { | ||||||
|  |     // Multiple links: create a zip file
 | ||||||
|  |     const zip = new JSZip(); | ||||||
|  |     const fileNamesSet = new Set<string>(); // To track file names for duplicates
 | ||||||
|  | 
 | ||||||
|  |     await Promise.all( | ||||||
|  |       linksArray.map(async (linkUrl: string) => { | ||||||
|  |         try { | ||||||
|  |           const response: Response = await fetch(linkUrl); | ||||||
|  |           if (!response.ok) { | ||||||
|  |             throw new Error(`Failed to fetch ${linkUrl}`); | ||||||
|  |           } | ||||||
|  |           const blob: Blob = await response.blob(); | ||||||
|  | 
 | ||||||
|  |           // Extract filename from URL
 | ||||||
|  |           let fileName: string = linkUrl.split('/').pop() || 'file'; | ||||||
|  | 
 | ||||||
|  |           // Get unique filename if duplicate exists
 | ||||||
|  |           fileName = getUniqueFileName(fileNamesSet, fileName); | ||||||
|  | 
 | ||||||
|  |           // Add file to the zip
 | ||||||
|  |           zip.file(fileName, blob); | ||||||
|  |         } | ||||||
|  |         catch (error) { | ||||||
|  |           console.error(`Error downloading file from ${linkUrl}:`, error); | ||||||
|  |         } | ||||||
|  |       }), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     // Generate the zip file and trigger download
 | ||||||
|  |     zip.generateAsync({ type: 'blob' }).then((zipBlob: Blob) => { | ||||||
|  |       const downloadUrl: string = window.URL.createObjectURL(zipBlob); | ||||||
|  | 
 | ||||||
|  |       // Trigger download of the zip file
 | ||||||
|  |       const a: HTMLAnchorElement = document.createElement('a'); | ||||||
|  |       a.href = downloadUrl; | ||||||
|  |       a.download = 'downloaded_files.zip'; | ||||||
|  |       document.body.appendChild(a); | ||||||
|  |       a.click(); | ||||||
|  | 
 | ||||||
|  |       // Clean up
 | ||||||
|  |       document.body.removeChild(a); | ||||||
|  |       window.URL.revokeObjectURL(downloadUrl); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								src/tools/multi-link-downloader/multi-link-downloader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/tools/multi-link-downloader/multi-link-downloader.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent, ref } from 'vue'; | ||||||
|  | import { downloadLinks } from './multi-link-downloader.service'; | ||||||
|  | 
 | ||||||
|  | export default defineComponent({ | ||||||
|  |   setup() { | ||||||
|  |     const links = ref<string>(''); | ||||||
|  |     const downloadMultiLinks = () => { | ||||||
|  |       if (links.value) { | ||||||
|  |         downloadLinks(links.value); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     const clearInput = () => { | ||||||
|  |       links.value = ''; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return { | ||||||
|  |       links, | ||||||
|  |       downloadMultiLinks, | ||||||
|  |       clearInput, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <c-card> | ||||||
|  |     <div class="mb-4 flex justify-between"> | ||||||
|  |       <c-button | ||||||
|  |         class="mr-2" | ||||||
|  |         :disabled="!links" | ||||||
|  |         @click="downloadMultiLinks" | ||||||
|  |       > | ||||||
|  |         Start Download | ||||||
|  |       </c-button> | ||||||
|  |       <c-button | ||||||
|  |         class="ml-2" | ||||||
|  |         @click="clearInput" | ||||||
|  |       > | ||||||
|  |         Clear | ||||||
|  |       </c-button> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <c-input-text | ||||||
|  |       v-model:value="links" | ||||||
|  |       placeholder="Add links separated by new lines..." | ||||||
|  |       multiline | ||||||
|  |       :rows="20" | ||||||
|  |     /> | ||||||
|  |   </c-card> | ||||||
|  | </template> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user