@nghiavuive/random-image 1.0.2 → 1.2.0
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.
- package/README.md +216 -16
- package/dist/cli.js +346 -0
- package/dist/cli.mjs +377 -0
- package/dist/index.d.mts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,46 +1,196 @@
|
|
|
1
1
|
# random-image
|
|
2
2
|
|
|
3
|
-
A generic utility library to fetch random images from various providers like Unsplash and Pexels.
|
|
3
|
+
A generic utility library to fetch random images from various providers like Unsplash and Pexels, with built-in download capabilities and CLI support.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install random-image
|
|
8
|
+
npm install @nghiavuive/random-image
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## Usage
|
|
11
|
+
## CLI Usage
|
|
12
|
+
|
|
13
|
+
You can use the CLI without installation via `npx`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @nghiavuive/random-image fetch --query "nature" --download
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install globally:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g @nghiavuive/random-image
|
|
23
|
+
random-image fetch --query "mountains" --download
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### CLI Setup
|
|
27
|
+
|
|
28
|
+
Set environment variables for the providers you want to use:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export UNSPLASH_KEY="your_unsplash_access_key"
|
|
32
|
+
export PEXELS_KEY="your_pexels_api_key"
|
|
33
|
+
export PIXABAY_KEY="your_pixabay_api_key"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or create a `.env` file in your project:
|
|
37
|
+
|
|
38
|
+
```env
|
|
39
|
+
UNSPLASH_KEY=your_unsplash_access_key
|
|
40
|
+
PEXELS_KEY=your_pexels_api_key
|
|
41
|
+
PIXABAY_KEY=your_pixabay_api_key
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### CLI Commands
|
|
45
|
+
|
|
46
|
+
#### Fetch Command
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Basic usage - uses random provider from available API keys
|
|
50
|
+
random-image fetch
|
|
51
|
+
|
|
52
|
+
# Fetch with search query
|
|
53
|
+
random-image fetch --query "nature"
|
|
54
|
+
|
|
55
|
+
# Fetch and download
|
|
56
|
+
random-image fetch --query "mountains" --download
|
|
57
|
+
|
|
58
|
+
# Specify provider
|
|
59
|
+
random-image fetch --provider unsplash --query "ocean"
|
|
60
|
+
|
|
61
|
+
# Custom dimensions
|
|
62
|
+
random-image fetch --width 1920 --height 1080 --query "sunset"
|
|
63
|
+
|
|
64
|
+
# Download to custom directory
|
|
65
|
+
random-image fetch --query "cats" --download ./my-images
|
|
66
|
+
|
|
67
|
+
# Download with custom filename
|
|
68
|
+
random-image fetch --query "dogs" --download --filename "my-dog.jpg"
|
|
69
|
+
|
|
70
|
+
# Download with overwrite
|
|
71
|
+
random-image fetch --download --overwrite
|
|
72
|
+
|
|
73
|
+
# Generate random UUID filename
|
|
74
|
+
random-image fetch --download --no-keep-original-name
|
|
75
|
+
|
|
76
|
+
# Full example with all options
|
|
77
|
+
random-image fetch \
|
|
78
|
+
--provider pexels \
|
|
79
|
+
--query "abstract art" \
|
|
80
|
+
--width 2560 \
|
|
81
|
+
--height 1440 \
|
|
82
|
+
--orientation landscape \
|
|
83
|
+
--download ./wallpapers \
|
|
84
|
+
--filename "wallpaper.jpg" \
|
|
85
|
+
--overwrite
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### CLI Flags
|
|
89
|
+
|
|
90
|
+
- `-q, --query <search>`: Search query for the image
|
|
91
|
+
- `-w, --width <number>`: Width of the image
|
|
92
|
+
- `-h, --height <number>`: Height of the image
|
|
93
|
+
- `--quality <number>`: Quality of the image (0-100)
|
|
94
|
+
- `--orientation <type>`: Image orientation (`landscape` or `portrait`)
|
|
95
|
+
- `-p, --provider <name>`: Provider to use (`unsplash`, `pexels`, `pixabay`, or `random`)
|
|
96
|
+
- `-d, --download [path]`: Download the image (default: `./downloads`)
|
|
97
|
+
- `--filename <name>`: Custom filename for downloaded image
|
|
98
|
+
- `--overwrite`: Overwrite existing files
|
|
99
|
+
- `--no-keep-original-name`: Generate random UUID filename instead of keeping original
|
|
100
|
+
|
|
101
|
+
**Random Provider Mode**: If you don't specify `--provider` or use `--provider random`, the CLI will randomly select from providers that have API keys configured. This is useful for distributing requests across multiple services.
|
|
102
|
+
|
|
103
|
+
## Programmatic Usage
|
|
12
104
|
|
|
13
105
|
You need to obtain API keys from the respective providers:
|
|
14
106
|
- [Unsplash Developers](https://unsplash.com/developers)
|
|
15
107
|
- [Pexels API](https://www.pexels.com/api/)
|
|
16
108
|
|
|
109
|
+
### Basic Usage - Fetching Random Images
|
|
110
|
+
|
|
17
111
|
```typescript
|
|
18
|
-
import { RandomImage, UnsplashProvider, PexelsProvider } from 'random-image';
|
|
112
|
+
import { RandomImage, UnsplashProvider, PexelsProvider } from '@nghiavuive/random-image';
|
|
19
113
|
|
|
20
114
|
// Using Unsplash
|
|
21
115
|
const unsplash = new UnsplashProvider('YOUR_UNSPLASH_ACCESS_KEY');
|
|
22
116
|
const fetcher = new RandomImage(unsplash);
|
|
23
117
|
|
|
24
|
-
fetcher.getRandom({
|
|
118
|
+
const image = await fetcher.getRandom({
|
|
25
119
|
width: 1920,
|
|
26
120
|
height: 1080,
|
|
27
121
|
query: 'nature'
|
|
28
|
-
}).then(image => {
|
|
29
|
-
console.log(image.url); // URL to the image
|
|
30
|
-
console.log(image.author); // Photographer's name
|
|
31
122
|
});
|
|
32
123
|
|
|
124
|
+
console.log(image.url); // URL to the image
|
|
125
|
+
console.log(image.author); // Photographer's name
|
|
126
|
+
|
|
33
127
|
// Using Pexels
|
|
34
128
|
const pexels = new PexelsProvider('YOUR_PEXELS_API_KEY');
|
|
35
129
|
const pexelsFetcher = new RandomImage(pexels);
|
|
36
130
|
|
|
37
|
-
pexelsFetcher.getRandom({
|
|
131
|
+
const pexelsImage = await pexelsFetcher.getRandom({
|
|
38
132
|
width: 800,
|
|
39
133
|
height: 600,
|
|
40
134
|
query: 'cats'
|
|
41
|
-
}).then(image => {
|
|
42
|
-
console.log(image.url);
|
|
43
135
|
});
|
|
136
|
+
|
|
137
|
+
console.log(pexelsImage.url);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Downloading Images
|
|
141
|
+
|
|
142
|
+
The library now supports downloading images directly to your local filesystem:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { RandomImage, UnsplashProvider } from '@nghiavuive/random-image';
|
|
146
|
+
|
|
147
|
+
const provider = new UnsplashProvider('YOUR_API_KEY');
|
|
148
|
+
const fetcher = new RandomImage(provider);
|
|
149
|
+
|
|
150
|
+
// Fetch and download a random image
|
|
151
|
+
const image = await fetcher.getRandom({ query: 'mountains' });
|
|
152
|
+
|
|
153
|
+
// Download with auto-generated filename
|
|
154
|
+
const filePath = await fetcher.download(image, './downloads');
|
|
155
|
+
console.log(`Image saved to: ${filePath}`);
|
|
156
|
+
|
|
157
|
+
// Download with custom filename
|
|
158
|
+
const customPath = await fetcher.download(
|
|
159
|
+
image,
|
|
160
|
+
'./downloads',
|
|
161
|
+
{ filename: 'my-mountain.jpg' }
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Download with overwrite option
|
|
165
|
+
const overwritePath = await fetcher.download(
|
|
166
|
+
image,
|
|
167
|
+
'./downloads',
|
|
168
|
+
{
|
|
169
|
+
filename: 'mountain.jpg',
|
|
170
|
+
overwrite: true // Will replace existing file
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Download directly from URL
|
|
175
|
+
await fetcher.download(
|
|
176
|
+
'https://example.com/image.jpg',
|
|
177
|
+
'./downloads',
|
|
178
|
+
{ filename: 'direct-download.jpg' }
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Download with UUID-based random filename
|
|
182
|
+
await fetcher.download(
|
|
183
|
+
image,
|
|
184
|
+
'./downloads',
|
|
185
|
+
{ keepOriginalName: false }
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Download keeping original filename (default behavior)
|
|
189
|
+
await fetcher.download(
|
|
190
|
+
image,
|
|
191
|
+
'./downloads',
|
|
192
|
+
{ keepOriginalName: true }
|
|
193
|
+
);
|
|
44
194
|
```
|
|
45
195
|
|
|
46
196
|
## API
|
|
@@ -51,24 +201,74 @@ The main class to interact with.
|
|
|
51
201
|
```typescript
|
|
52
202
|
class RandomImage {
|
|
53
203
|
constructor(provider: ImageProvider);
|
|
204
|
+
|
|
205
|
+
// Fetch a random image
|
|
54
206
|
getRandom(options: ImageOptions): Promise<ImageResult>;
|
|
207
|
+
|
|
208
|
+
// Download an image to local filesystem
|
|
209
|
+
download(
|
|
210
|
+
imageUrl: string | ImageResult,
|
|
211
|
+
destinationPath: string,
|
|
212
|
+
options?: DownloadOptions
|
|
213
|
+
): Promise<string>;
|
|
55
214
|
}
|
|
56
215
|
```
|
|
57
216
|
|
|
58
217
|
### `ImageOptions`
|
|
59
|
-
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
218
|
+
Configuration for fetching random images:
|
|
219
|
+
- `width` (number, optional): Desired width of the image.
|
|
220
|
+
- `height` (number, optional): Desired height of the image.
|
|
221
|
+
- `quality` (number, optional): Quality (0-100) if supported by provider.
|
|
222
|
+
- `query` (string, optional): Search query (e.g. "nature", "city").
|
|
223
|
+
- `orientation` ("landscape" | "portrait", optional): Image orientation.
|
|
224
|
+
|
|
225
|
+
### `DownloadOptions`
|
|
226
|
+
Configuration for downloading images:
|
|
227
|
+
- `filename` (string, optional): Custom filename for the downloaded image. If provided, this takes precedence over `keepOriginalName`.
|
|
228
|
+
- `overwrite` (boolean, optional): Whether to overwrite existing files. Default is `false`. If `false` and file exists, an error will be thrown.
|
|
229
|
+
- `keepOriginalName` (boolean, optional): Controls filename generation when `filename` is not provided:
|
|
230
|
+
- `true` or `undefined` (default): Extract and use the original filename from the URL
|
|
231
|
+
- `false`: Generate a random UUID-based filename
|
|
63
232
|
|
|
64
233
|
### `ImageResult`
|
|
234
|
+
Result object containing image information:
|
|
65
235
|
- `url` (string): Direct URL to the image.
|
|
66
236
|
- `width` (number): Width of the image.
|
|
67
237
|
- `height` (number): Height of the image.
|
|
68
238
|
- `author` (string): Name of the photographer.
|
|
69
|
-
- `authorUrl` (string): URL to photographer's profile.
|
|
239
|
+
- `authorUrl` (string, optional): URL to photographer's profile.
|
|
70
240
|
- `originalUrl` (string): URL to the original photo page.
|
|
71
241
|
|
|
242
|
+
## Features
|
|
243
|
+
|
|
244
|
+
### CLI Features
|
|
245
|
+
- ✅ Command-line interface for quick image fetching
|
|
246
|
+
- ✅ Random provider selection when multiple API keys are available
|
|
247
|
+
- ✅ Environment variable support for API keys
|
|
248
|
+
- ✅ Direct download from command line
|
|
249
|
+
- ✅ All library features available via CLI flags
|
|
250
|
+
|
|
251
|
+
### Library Features
|
|
252
|
+
|
|
253
|
+
### Download Functionality
|
|
254
|
+
- ✅ Flexible filename options:
|
|
255
|
+
- Custom filename
|
|
256
|
+
- Keep original filename from URL (default)
|
|
257
|
+
- Generate random UUID-based filename
|
|
258
|
+
- ✅ Overwrite protection
|
|
259
|
+
- ✅ Automatic file extension detection
|
|
260
|
+
- ✅ Support for both HTTP and HTTPS
|
|
261
|
+
- ✅ Automatic redirect handling (up to 5 redirects)
|
|
262
|
+
- ✅ Stream-based downloading for memory efficiency
|
|
263
|
+
- ✅ Error handling with cleanup of partial downloads
|
|
264
|
+
|
|
265
|
+
## Supported Providers
|
|
266
|
+
|
|
267
|
+
- **Unsplash**: High-quality photos with attribution
|
|
268
|
+
- **Pexels**: Free stock photos and videos
|
|
269
|
+
- **Pixabay**: Free images and videos
|
|
270
|
+
|
|
72
271
|
## License
|
|
73
272
|
|
|
74
273
|
ISC
|
|
274
|
+
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
|
|
29
|
+
// src/providers/unsplash.ts
|
|
30
|
+
var import_axios = __toESM(require("axios"));
|
|
31
|
+
var UnsplashProvider = class {
|
|
32
|
+
constructor(accessKey) {
|
|
33
|
+
this.accessKey = accessKey;
|
|
34
|
+
}
|
|
35
|
+
async fetchRandomImage(options) {
|
|
36
|
+
const response = await import_axios.default.get("https://api.unsplash.com/photos/random", {
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: `Client-ID ${this.accessKey}`
|
|
39
|
+
},
|
|
40
|
+
params: {
|
|
41
|
+
query: options.query,
|
|
42
|
+
orientation: options.orientation
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const data = response.data;
|
|
46
|
+
const photo = Array.isArray(data) ? data[0] : data;
|
|
47
|
+
const baseUrl = photo.urls.raw;
|
|
48
|
+
const sizeParams = new URLSearchParams();
|
|
49
|
+
if (options.height || options.width) {
|
|
50
|
+
sizeParams.append("fit", "crop");
|
|
51
|
+
sizeParams.append("crop", "entropy");
|
|
52
|
+
}
|
|
53
|
+
if (options.width) sizeParams.append("w", options.width.toString());
|
|
54
|
+
if (options.height) sizeParams.append("h", options.height.toString());
|
|
55
|
+
if (options.quality) sizeParams.append("q", options.quality.toString());
|
|
56
|
+
const finalUrl = `${baseUrl}&${sizeParams.toString()}`;
|
|
57
|
+
return {
|
|
58
|
+
url: finalUrl,
|
|
59
|
+
width: options.width || photo.width,
|
|
60
|
+
height: options.height || photo.height,
|
|
61
|
+
author: photo.user.name,
|
|
62
|
+
authorUrl: photo.user.links.html,
|
|
63
|
+
originalUrl: photo.links.html
|
|
64
|
+
// Link to photo page
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/providers/pexels.ts
|
|
70
|
+
var import_axios2 = __toESM(require("axios"));
|
|
71
|
+
var PexelsProvider = class {
|
|
72
|
+
constructor(apiKey) {
|
|
73
|
+
this.apiKey = apiKey;
|
|
74
|
+
}
|
|
75
|
+
async fetchRandomImage(options) {
|
|
76
|
+
const endpoint = options.query ? "https://api.pexels.com/v1/search" : "https://api.pexels.com/v1/curated";
|
|
77
|
+
const randomPage = Math.floor(Math.random() * 100) + 1;
|
|
78
|
+
const params = {
|
|
79
|
+
per_page: 1,
|
|
80
|
+
page: randomPage
|
|
81
|
+
};
|
|
82
|
+
if (options.query) params.query = options.query;
|
|
83
|
+
const response = await import_axios2.default.get(endpoint, {
|
|
84
|
+
headers: {
|
|
85
|
+
Authorization: this.apiKey
|
|
86
|
+
},
|
|
87
|
+
params
|
|
88
|
+
});
|
|
89
|
+
const data = response.data;
|
|
90
|
+
if (!data.photos || data.photos.length === 0) {
|
|
91
|
+
throw new Error("No images found on Pexels");
|
|
92
|
+
}
|
|
93
|
+
const photo = data.photos[0];
|
|
94
|
+
const baseUrl = photo.src.original;
|
|
95
|
+
const sizeParams = new URLSearchParams();
|
|
96
|
+
sizeParams.append("auto", "compress");
|
|
97
|
+
sizeParams.append("cs", "tinysrgb");
|
|
98
|
+
if (options.width) sizeParams.append("w", options.width.toString());
|
|
99
|
+
if (options.height) sizeParams.append("h", options.height.toString());
|
|
100
|
+
const finalUrl = `${baseUrl}?${sizeParams.toString()}`;
|
|
101
|
+
return {
|
|
102
|
+
url: finalUrl,
|
|
103
|
+
width: options.width || photo.width,
|
|
104
|
+
height: options.height || photo.height,
|
|
105
|
+
author: photo.photographer,
|
|
106
|
+
authorUrl: photo.photographer_url,
|
|
107
|
+
originalUrl: photo.url
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/providers/pixabay.ts
|
|
113
|
+
var import_axios3 = __toESM(require("axios"));
|
|
114
|
+
var PixabayProvider = class {
|
|
115
|
+
constructor(apiKey) {
|
|
116
|
+
this.apiKey = apiKey;
|
|
117
|
+
}
|
|
118
|
+
async fetchRandomImage(options) {
|
|
119
|
+
const params = {
|
|
120
|
+
key: this.apiKey,
|
|
121
|
+
q: options.query || "",
|
|
122
|
+
per_page: 20
|
|
123
|
+
// Fetch a few to pick randomly
|
|
124
|
+
};
|
|
125
|
+
if (options.orientation) {
|
|
126
|
+
params.orientation = options.orientation === "portrait" ? "vertical" : "horizontal";
|
|
127
|
+
}
|
|
128
|
+
const response = await import_axios3.default.get("https://pixabay.com/api/", {
|
|
129
|
+
params
|
|
130
|
+
});
|
|
131
|
+
const hits = response.data.hits;
|
|
132
|
+
if (!hits || hits.length === 0) {
|
|
133
|
+
throw new Error("No images found");
|
|
134
|
+
}
|
|
135
|
+
const randomHit = hits[Math.floor(Math.random() * hits.length)];
|
|
136
|
+
return {
|
|
137
|
+
url: randomHit.largeImageURL || randomHit.webformatURL,
|
|
138
|
+
width: randomHit.imageWidth || randomHit.webformatWidth,
|
|
139
|
+
height: randomHit.imageHeight || randomHit.webformatHeight,
|
|
140
|
+
author: randomHit.user,
|
|
141
|
+
authorUrl: `https://pixabay.com/users/${randomHit.user}-${randomHit.user_id}/`,
|
|
142
|
+
originalUrl: randomHit.pageURL
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/image-fetcher.ts
|
|
148
|
+
var fs = __toESM(require("fs"));
|
|
149
|
+
var path = __toESM(require("path"));
|
|
150
|
+
var import_axios4 = __toESM(require("axios"));
|
|
151
|
+
var import_uuid = require("uuid");
|
|
152
|
+
var RandomImage = class {
|
|
153
|
+
constructor(provider) {
|
|
154
|
+
this.provider = provider;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Fetches a random image based on the provided options.
|
|
158
|
+
* @param options - Configuration options for the image (width, height, query, etc.)
|
|
159
|
+
* @returns A promise that resolves to an ImageResult object.
|
|
160
|
+
*/
|
|
161
|
+
async getRandom(options = {}) {
|
|
162
|
+
return this.provider.fetchRandomImage(options);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Downloads an image to a specified directory.
|
|
166
|
+
* @param imageUrl - The URL of the image to download (can be a string or ImageResult object)
|
|
167
|
+
* @param destinationPath - The directory path where the image will be saved
|
|
168
|
+
* @param options - Download options (filename, overwrite, etc.)
|
|
169
|
+
* @returns A promise that resolves to the full path of the downloaded file
|
|
170
|
+
*/
|
|
171
|
+
async download(imageUrl, destinationPath, options = {}) {
|
|
172
|
+
const url = typeof imageUrl === "string" ? imageUrl : imageUrl.url;
|
|
173
|
+
if (!fs.existsSync(destinationPath)) {
|
|
174
|
+
fs.mkdirSync(destinationPath, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
let finalFilename;
|
|
177
|
+
if (options.filename) {
|
|
178
|
+
finalFilename = options.filename;
|
|
179
|
+
} else if (options.keepOriginalName) {
|
|
180
|
+
const urlPath = new URL(url).pathname;
|
|
181
|
+
const urlFilename = path.basename(urlPath);
|
|
182
|
+
if (urlFilename && urlFilename.length > 0 && urlFilename !== "/") {
|
|
183
|
+
finalFilename = urlFilename;
|
|
184
|
+
} else {
|
|
185
|
+
const ext = this.getExtensionFromUrl(url) || ".jpg";
|
|
186
|
+
finalFilename = `${(0, import_uuid.v7)()}${ext}`;
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
const ext = this.getExtensionFromUrl(url) || ".jpg";
|
|
190
|
+
finalFilename = `${(0, import_uuid.v7)()}${ext}`;
|
|
191
|
+
}
|
|
192
|
+
if (!path.extname(finalFilename)) {
|
|
193
|
+
finalFilename += ".jpg";
|
|
194
|
+
}
|
|
195
|
+
const fullPath = path.join(destinationPath, finalFilename);
|
|
196
|
+
if (fs.existsSync(fullPath) && !options.overwrite) {
|
|
197
|
+
throw new Error(`File already exists: ${fullPath}. Set overwrite: true to replace it.`);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const response = await import_axios4.default.get(url, {
|
|
201
|
+
responseType: "stream",
|
|
202
|
+
maxRedirects: 5
|
|
203
|
+
// Automatically handle redirects
|
|
204
|
+
});
|
|
205
|
+
const writer = fs.createWriteStream(fullPath);
|
|
206
|
+
response.data.pipe(writer);
|
|
207
|
+
return new Promise((resolve2, reject) => {
|
|
208
|
+
writer.on("finish", () => {
|
|
209
|
+
resolve2(fullPath);
|
|
210
|
+
});
|
|
211
|
+
writer.on("error", (err) => {
|
|
212
|
+
fs.unlink(fullPath, () => {
|
|
213
|
+
});
|
|
214
|
+
reject(err);
|
|
215
|
+
});
|
|
216
|
+
response.data.on("error", (err) => {
|
|
217
|
+
writer.close();
|
|
218
|
+
fs.unlink(fullPath, () => {
|
|
219
|
+
});
|
|
220
|
+
reject(err);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (import_axios4.default.isAxiosError(error)) {
|
|
225
|
+
throw new Error(`Failed to download image: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Helper method to extract file extension from URL
|
|
232
|
+
* @param url - The URL to extract extension from
|
|
233
|
+
* @returns The file extension (e.g., '.jpg', '.png') or null
|
|
234
|
+
*/
|
|
235
|
+
getExtensionFromUrl(url) {
|
|
236
|
+
try {
|
|
237
|
+
const urlPath = new URL(url).pathname;
|
|
238
|
+
const ext = path.extname(urlPath);
|
|
239
|
+
return ext || null;
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/cli.ts
|
|
247
|
+
var path2 = __toESM(require("path"));
|
|
248
|
+
var dotenv = __toESM(require("dotenv"));
|
|
249
|
+
var fs2 = __toESM(require("fs"));
|
|
250
|
+
var possibleEnvPaths = [
|
|
251
|
+
path2.resolve(process.cwd(), ".env"),
|
|
252
|
+
// Current working directory
|
|
253
|
+
path2.resolve(__dirname, ".env"),
|
|
254
|
+
// Same directory as the CLI script (dist/)
|
|
255
|
+
path2.resolve(__dirname, "..", ".env")
|
|
256
|
+
// Parent directory (root of project)
|
|
257
|
+
];
|
|
258
|
+
for (const envPath of possibleEnvPaths) {
|
|
259
|
+
if (fs2.existsSync(envPath)) {
|
|
260
|
+
dotenv.config({ path: envPath });
|
|
261
|
+
console.log(`Loaded environment from: ${envPath}`);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
var program = new import_commander.Command();
|
|
266
|
+
program.name("random-image").description("CLI tool to fetch random images from various providers").version("1.2.0");
|
|
267
|
+
program.command("fetch").description("Fetch a random image from image providers").option("-q, --query <search>", "Search query for the image").option("-w, --width <number>", "Width of the image", parseInt).option("-h, --height <number>", "Height of the image", parseInt).option("--quality <number>", "Quality of the image (0-100)", parseInt).option("--orientation <type>", "Image orientation (landscape or portrait)").option("-p, --provider <name>", "Provider to use (unsplash, pexels, pixabay, or random)", "random").option("-d, --download [path]", "Download the image to specified directory (default: ./downloads)").option("--filename <name>", "Custom filename for downloaded image").option("--overwrite", "Overwrite existing files", false).option("--no-keep-original-name", "Generate random UUID filename instead of keeping original").action(async (options) => {
|
|
268
|
+
try {
|
|
269
|
+
const providers = {
|
|
270
|
+
unsplash: process.env.UNSPLASH_KEY,
|
|
271
|
+
pexels: process.env.PEXELS_KEY,
|
|
272
|
+
pixabay: process.env.PIXABAY_KEY
|
|
273
|
+
};
|
|
274
|
+
let providerName = options.provider.toLowerCase();
|
|
275
|
+
if (providerName === "random") {
|
|
276
|
+
const availableProviders = Object.entries(providers).filter(([_, key]) => key).map(([name]) => name);
|
|
277
|
+
if (availableProviders.length === 0) {
|
|
278
|
+
console.error("Error: No API keys found in environment variables.");
|
|
279
|
+
console.error("Please set at least one of: UNSPLASH_KEY, PEXELS_KEY, PIXABAY_KEY");
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
providerName = availableProviders[Math.floor(Math.random() * availableProviders.length)];
|
|
283
|
+
console.log(`Using random provider: ${providerName}`);
|
|
284
|
+
}
|
|
285
|
+
let provider;
|
|
286
|
+
const apiKey = providers[providerName];
|
|
287
|
+
if (!apiKey) {
|
|
288
|
+
console.error(`Error: API key for ${providerName} not found.`);
|
|
289
|
+
console.error(`Please set ${providerName.toUpperCase()}_KEY environment variable.`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
switch (providerName) {
|
|
293
|
+
case "unsplash":
|
|
294
|
+
provider = new UnsplashProvider(apiKey);
|
|
295
|
+
break;
|
|
296
|
+
case "pexels":
|
|
297
|
+
provider = new PexelsProvider(apiKey);
|
|
298
|
+
break;
|
|
299
|
+
case "pixabay":
|
|
300
|
+
provider = new PixabayProvider(apiKey);
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
console.error(`Error: Unknown provider "${providerName}"`);
|
|
304
|
+
console.error("Available providers: unsplash, pexels, pixabay, random");
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
const fetcher = new RandomImage(provider);
|
|
308
|
+
const fetchOptions = {};
|
|
309
|
+
if (options.query) fetchOptions.query = options.query;
|
|
310
|
+
if (options.width) fetchOptions.width = options.width;
|
|
311
|
+
if (options.height) fetchOptions.height = options.height;
|
|
312
|
+
if (options.quality) fetchOptions.quality = options.quality;
|
|
313
|
+
if (options.orientation) fetchOptions.orientation = options.orientation;
|
|
314
|
+
console.log("Fetching random image...");
|
|
315
|
+
const image = await fetcher.getRandom(fetchOptions);
|
|
316
|
+
console.log("\n\u2713 Image fetched successfully!");
|
|
317
|
+
console.log(` URL: ${image.url}`);
|
|
318
|
+
console.log(` Size: ${image.width}x${image.height}`);
|
|
319
|
+
console.log(` Author: ${image.author}`);
|
|
320
|
+
if (image.authorUrl) console.log(` Author URL: ${image.authorUrl}`);
|
|
321
|
+
console.log(` Original: ${image.originalUrl}`);
|
|
322
|
+
if (options.download !== void 0) {
|
|
323
|
+
const downloadPath = typeof options.download === "string" ? options.download : "./downloads";
|
|
324
|
+
console.log(`
|
|
325
|
+
Downloading to ${path2.resolve(downloadPath)}...`);
|
|
326
|
+
const downloadOptions = {
|
|
327
|
+
overwrite: options.overwrite,
|
|
328
|
+
keepOriginalName: options.keepOriginalName
|
|
329
|
+
};
|
|
330
|
+
if (options.filename) {
|
|
331
|
+
downloadOptions.filename = options.filename;
|
|
332
|
+
}
|
|
333
|
+
const filePath = await fetcher.download(image, downloadPath, downloadOptions);
|
|
334
|
+
console.log(`\u2713 Downloaded: ${filePath}`);
|
|
335
|
+
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (error instanceof Error) {
|
|
338
|
+
console.error(`
|
|
339
|
+
Error: ${error.message}`);
|
|
340
|
+
} else {
|
|
341
|
+
console.error("\nAn unexpected error occurred");
|
|
342
|
+
}
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
program.parse(process.argv);
|