Comparing version
#!/usr/bin/env node | ||
import{a as o,b,c as w}from"./chunk-TDJJNDZB.js";import x from"cli-progress";import y from"meow";import $ from"fs";import v from"path";import d from"chalk";var m=y(` | ||
import{a as o,b,c as w}from"./chunk-FMOY4ZR7.js";import x from"cli-progress";import y from"meow";import $ from"fs";import v from"path";import d from"chalk";var m=y(` | ||
USAGE | ||
@@ -34,3 +34,3 @@ $ imgdl <url> ... [OPTIONS] | ||
$ imgdl https://example.com/image.jpg --header="User-Agent: Mozilla/5.0" --header="Cookie: foo=bar" | ||
`,{importMeta:import.meta,booleanDefault:void 0,flags:{dir:{shortFlag:"d",type:"string"},ext:{shortFlag:"e",type:"string"},header:{shortFlag:"H",type:"string",isMultiple:!0},increment:{shortFlag:"i",type:"boolean"},interval:{type:"number"},start:{type:"number"},end:{type:"number"},name:{shortFlag:"n",type:"string"},maxRetry:{type:"number"},silent:{type:"boolean"},step:{type:"number"},timeout:{shortFlag:"t",type:"number"},version:{shortFlag:"v",type:"boolean"}}}),S=d.bold.green,c=d.bold.red,T=d.yellow,s=d.dim;async function D(){let t=m.input,{flags:e}=m;if(e.version&&m.showVersion(),t.length||m.showHelp(),e.increment){if(t.length>1)throw new o("Only one URL is allowed in increment mode");let n=t[0];if(!n.includes("{i}"))throw new o("The URL must contain {i} placeholder for the index");if(!e.end)throw new o("The end index is required in increment mode");if(e.start&&e.start>e.end)throw new o("The start index cannot be greater than the end index");let{start:i=0,end:l}=e;t=[];for(let g=i;g<=l;g+=1)t.push(n.replace("{i}",g.toString()))}e.silent||console.log(` | ||
`,{importMeta:import.meta,booleanDefault:void 0,flags:{dir:{shortFlag:"d",type:"string"},ext:{shortFlag:"e",type:"string"},header:{shortFlag:"H",type:"string",isMultiple:!0},increment:{shortFlag:"i",type:"boolean"},interval:{type:"number"},start:{type:"number"},end:{type:"number"},name:{shortFlag:"n",type:"string"},maxRetry:{type:"number"},silent:{type:"boolean"},step:{type:"number"},timeout:{shortFlag:"t",type:"number"},version:{shortFlag:"v",type:"boolean"}}}),S=d.bold.green,c=d.bold.red,T=d.yellow,s=d.dim;async function D(){let t=m.input,{flags:e}=m;if(e.version&&m.showVersion(),t.length||m.showHelp(0),e.increment){if(t.length>1)throw new o("Only one URL is allowed in increment mode");let n=t[0];if(!n.includes("{i}"))throw new o("The URL must contain {i} placeholder for the index");if(!e.end)throw new o("The end index is required in increment mode");if(e.start&&e.start>e.end)throw new o("The start index cannot be greater than the end index");let{start:i=0,end:l}=e;t=[];for(let g=i;g<=l;g+=1)t.push(n.replace("{i}",g.toString()))}e.silent||console.log(` | ||
${s("Downloading...")} | ||
@@ -37,0 +37,0 @@ ${T("Press Ctrl+C to abort")}`);let h=s("|"),a=new x.SingleBar({format:`{percentage}% [{bar}] {value}/{total} ${h} ${S("\u2705 {success}")} ${h} ${c("\u274C {errorCount}")} ${h} ETA: {eta_formatted} ${s("/ {duration_formatted}")}`,hideCursor:null,barsize:24}),p=0,r=0;!e.silent&&t.length>1&&a.start(t.length,0,{success:p,errorCount:r});let u={};e.header&&e.header.forEach(n=>{let[i,l]=n.split(":");if(!i||!l)throw new o("Invalid header format");u[i.trim()]=l.trim()});let f=new AbortController;process.on("SIGINT",()=>{a.stop(),console.log(s(` |
@@ -1,2 +0,2 @@ | ||
type DownloadOptions = { | ||
type ImageOptions = { | ||
/** | ||
@@ -9,23 +9,11 @@ * The directory to save the image to. | ||
/** | ||
* The headers to send with the request. | ||
*/ | ||
headers?: Record<string, string | string[] | undefined>; | ||
/** | ||
* The name of the image file. | ||
* | ||
* You also can provide a function that returns the name. | ||
* The function will be called with the original name, if it exists in the URL. | ||
* If not provided, the default value will be the **original name** if it exists in the URL. | ||
* | ||
* The default value will be used if this value (or the function) returns an empty string. | ||
* | ||
* The default value will be the **original name** if it exists in the URL. | ||
* Otherwise, it will be **'image'**. | ||
*/ | ||
name?: string | ((original?: string) => string); | ||
/** | ||
* Set the maximum number of times to retry the request if it fails. | ||
* | ||
* @default 2 | ||
* If a name with same extension already exists, ` (1)`, ` (2)`, etc. will be added to the end of the name. | ||
*/ | ||
maxRetry?: number; | ||
name?: string; | ||
/** | ||
@@ -39,3 +27,15 @@ * The extension of the image. | ||
extension?: string; | ||
}; | ||
type DownloadOptions = { | ||
/** | ||
* The headers to send with the request. | ||
*/ | ||
headers?: Record<string, string | string[] | undefined>; | ||
/** | ||
* Set the maximum number of times to retry the request if it fails. | ||
* | ||
* @default 2 | ||
*/ | ||
maxRetry?: number; | ||
/** | ||
* Set timeout for each request in milliseconds. | ||
@@ -54,3 +54,3 @@ */ | ||
*/ | ||
url: string; | ||
url: URL; | ||
/** | ||
@@ -81,24 +81,6 @@ * The name of the image file, without the extension. | ||
}; | ||
type Options = Omit<DownloadOptions, 'name'> & { | ||
type Options = (ImageOptions & DownloadOptions) & { | ||
/** | ||
* The name of the image file. | ||
* Do something when an image is successfully downloaded. | ||
* | ||
* You also can provide a function that returns the name. | ||
* The function will be called with the original name, if it exists in the URL. | ||
* | ||
* The default value will be used if this value (or the function) returns an empty string. | ||
* | ||
* The default value will be the **original name** if it exists in the URL. | ||
* Otherwise, it will be **'image'**. | ||
* | ||
* When downloading multiple images, `-index` will be appended to the end of the name (suffix). | ||
* `index` will start from 1. | ||
*/ | ||
name?: string; | ||
/** | ||
* Do something when the image is successfully downloaded. | ||
* For example, counting the number of successful downloads. | ||
* | ||
* Only called when downloading multiple images. | ||
* | ||
* @param image The downloaded image. | ||
@@ -108,7 +90,4 @@ */ | ||
/** | ||
* Do something when the image download failed. | ||
* For example, counting the number of failed downloads. | ||
* Do something when an image fails to download. | ||
* | ||
* Only called when downloading multiple images. | ||
* | ||
* @param error The error that caused the download to fail. | ||
@@ -135,6 +114,4 @@ * @param url The URL of the image that failed to download. | ||
}; | ||
declare function imgdl(url: string, options?: Options): Promise<Image>; | ||
declare function imgdl(url: string[], options?: Options): Promise<Image[]>; | ||
declare function imgdl(url: string | string[], options?: Options): Promise<Image | Image[]>; | ||
declare function imgdl(url: string | string[], options?: Options): Promise<void>; | ||
export { type Image, type Options, imgdl as default }; |
@@ -1,2 +0,2 @@ | ||
import{c as a}from"./chunk-TDJJNDZB.js";export{a as default}; | ||
import{c as a}from"./chunk-FMOY4ZR7.js";export{a as default}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "img-dl", | ||
"version": "0.5.1", | ||
"version": "0.6.1", | ||
"description": "Download image(s), by command or programmatically", | ||
@@ -40,3 +40,3 @@ "type": "module", | ||
"engines": { | ||
"node": ">=18" | ||
"node": ">=20.9" | ||
}, | ||
@@ -47,11 +47,12 @@ "devDependencies": { | ||
"@types/node": "^20.12.8", | ||
"@vitest/coverage-v8": "^1.6.0", | ||
"@vitest/coverage-v8": "^2.0.2", | ||
"eslint": "^8.57.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"execa": "^8.0.1", | ||
"prettier": "^3.2.5", | ||
"tsup": "^8.0.2", | ||
"typescript": "^5.4.5", | ||
"execa": "^9.3.0", | ||
"msw": "^2.3.1", | ||
"prettier": "^3.3.2", | ||
"tsup": "^8.1.0", | ||
"typescript": "^5.5.2", | ||
"typescript-eslint": "^7.8.0", | ||
"vitest": "^1.6.0" | ||
"vitest": "^2.0.2" | ||
}, | ||
@@ -61,3 +62,3 @@ "dependencies": { | ||
"cli-progress": "^3.12.0", | ||
"got": "^13.0.0", | ||
"got": "^14.4.1", | ||
"meow": "^13.2.0", | ||
@@ -64,0 +65,0 @@ "p-queue": "^8.0.1", |
111
README.md
# img-dl | ||
Downloade image(s), by command or programmatically. The alternative for `image-downloader` package (see the [comparison](#comparison)). | ||
Downloade image(s), by command or programmatically. The alternative for `image-downloader` package (see the [features](#features)). | ||
@@ -10,6 +10,35 @@ [](https://github.com/fityannugroho/img-dl/blob/main/LICENSE) | ||
## Features | ||
| Features | **img-dl** | [image-downloader][p1] | | ||
| --------------------------- | :--------: | :--------------------: | | ||
| Single download | ✅ | ✅ | | ||
| Bulk download | ✅ | ❌ | | ||
| CLI | ✅ | ❌ | | ||
| Custom filename | ✅ | ✅ | | ||
| Custom extension | ✅ | ❌ | | ||
| Request timeout | ✅ | ✅ | | ||
| Retry failed request | ✅ | ❌ | | ||
| Abort request | ✅ | ❌ | | ||
| **Increment mode (in CLI)** | ✅ | ❌ | | ||
| **Overwrite prevention** | ✅ | ❌ | | ||
### Increment mode | ||
Download images with an url that contains `{i}` placeholder for the index, and specify the start and end index. | ||
### Overwrite prevention | ||
To prevent overwriting, ` (n)` will be appended to the name of the new file if the file with the same name already exists. | ||
The number will be incremented until the file name is unique in the directory, starting from 1 (e.g. `image (1).jpg`, `image (2).jpg`, etc.). | ||
Image with different extension will be considered as **different** file, so it will not be appended with ` (n)`. For example, `image.jpg` and `image.png` will not be considered as the same file. | ||
> This feature will work for both single and bulk download. | ||
## Prerequisites | ||
- Node.js 18 or later | ||
- npm 9 or later | ||
- Node.js 20.9 or later | ||
- npm 10 or later | ||
@@ -47,3 +76,3 @@ ## Installation | ||
In increment mode, the URL must contain {i} placeholder for the index, | ||
only one URL is allowed, and the 'end' flag is required. | ||
only one URL is allowed, and the '--end' is required. | ||
@@ -58,3 +87,3 @@ OPTIONS | ||
--interval=<number> The interval between each batch of requests in milliseconds | ||
-n, --name=<filename> The filename. Default: original filename or timestamp | ||
-n, --name=<filename> The filename. If not specified, the original filename will be used. Default: 'image' | ||
--max-retry=<number> Set the maximum number of times to retry the request if it fails | ||
@@ -82,3 +111,3 @@ --silent Disable logging | ||
#### Download multiple images | ||
#### Bulk download | ||
@@ -89,3 +118,3 @@ ```bash | ||
#### Download multiple images with increment mode | ||
#### Bulk download with increment mode | ||
@@ -103,7 +132,18 @@ ```bash | ||
const image = await imgdl('https://example.com/image.jpg'); | ||
const image = await new Promise((resolve, reject) => { | ||
imgdl('https://example.com/image.jpg', { | ||
onSuccess: resolve, | ||
onError: reject, | ||
}); | ||
}); | ||
console.log(image); | ||
/* | ||
{ | ||
url: 'https://example.com/image.jpg', | ||
url: { | ||
href: 'https://example.com/image.jpg', | ||
origin: 'https://example.com', | ||
protocol: 'https:', | ||
pathname: '/image.jpg', | ||
// ... | ||
}, | ||
name: 'image', | ||
@@ -119,3 +159,3 @@ extension: 'jpg', | ||
#### Download multiple images | ||
#### Bulk download | ||
@@ -125,6 +165,19 @@ ```js | ||
const images = await imgdl([ | ||
const urls = [ | ||
'https://example.com/image.jpg', | ||
'https://example.com/image2.jpg', | ||
]); | ||
]; | ||
await imgdl(urls, { | ||
onSuccess: (image) => { | ||
// Do something with the downloaded image | ||
console.log(image); | ||
}, | ||
onError: (error, url) => { | ||
// Do something when the image fails to download | ||
console.error(`Failed to download ${url}: ${error.message}`); | ||
}, | ||
}); | ||
console.log('Download completed'); | ||
``` | ||
@@ -136,2 +189,4 @@ | ||
Returns: `Promise<void>` | ||
Download image(s) from the given URL(s). | ||
@@ -184,3 +239,3 @@ | ||
The filename. If not specified, the original filename will be used. If the original filename is not available, 'image' will be used. <br>When downloading multiple images, `-index` will be appended to the end of the name (suffix). `index` will start from 1. For example: 'image-1' | ||
The filename. If not specified, the original filename will be used. If the original filename is not available, 'image' will be used. | ||
@@ -199,4 +254,14 @@ ##### `maxRetry` | ||
The callback function to be called when the image is successfully downloaded. Only available when downloading multiple images. | ||
The callback function to be called when the image is successfully downloaded. | ||
`Image` is an object containing the information of the downloaded image. `Image` has the following properties: | ||
- `url`: The instance of `URL` class of the image. See [`URL` Class](https://nodejs.org/api/url.html#class-url). | ||
- `originalName`: The original name of the image if available. Default: `undefined`. | ||
- `originalExtension`: The original extension of the image if available. Default: `undefined`. | ||
- `name`: The user-defined name of the image. If not specified and the original name is available, the original name will be used. If the original name is not available, `'image'` will be used. | ||
- `extension`: The user-defined extension of the image. If not specified and the original extension is available, the original extension will be used. If the original extension is not available, `'jpg'` will be used. | ||
- `directory`: The output directory of the image. | ||
- `path`: The path of the downloaded image. | ||
##### `onError` | ||
@@ -207,3 +272,3 @@ | ||
The callback function to be called when the image fails to download. Only available when downloading multiple images. | ||
The callback function to be called when the image fails to download. | ||
@@ -231,16 +296,2 @@ ##### `signal` | ||
## Comparison | ||
| Features | **img-dl** | [image-downloader][p1] | | ||
| ------------------------ | :--------: | :--------------------: | | ||
| Download single image | ✅ | ✅ | | ||
| Download multiple images | ✅ | ❌ | | ||
| CLI | ✅ | ❌ | | ||
| Increment download | ✅ | ❌ | | ||
| Custom filename | ✅ | ✅ | | ||
| Custom extension | ✅ | ❌ | | ||
| Request timeout | ✅ | ✅ | | ||
| Retry failed request | ✅ | ❌ | | ||
| Abort request | ✅ | ❌ | | ||
<!-- Project links --> | ||
@@ -254,2 +305,2 @@ | ||
You can support this project by donating via [GitHub Sponsors](https://github.com/sponsors/fityannugroho), [Trakteer](https://trakteer.id/fityannugroho/tip), or [Saweria](https://saweria.co/fityannugroho). | ||
Also please consider supporting this project with a **donation**. Your donation will help us maintain and develop this project and provide you with better support. |
Sorry, the diff of this file is not supported yet
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
49653
1.56%293
21.07%13
8.33%167
-11.17%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated