mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-04 05:19:12 -04:00
feat(new tool) multi-link-downloader
This commit is contained in:
parent
0b1b98f93e
commit
7035eba246
8 changed files with 238 additions and 1 deletions
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…
Add table
Add a link
Reference in a new issue