@nghiavuive/random-image 1.1.0 → 1.2.1
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 +103 -3
- package/dist/cli.js +465 -0
- package/dist/cli.mjs +527 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# random-image
|
|
2
2
|
|
|
3
|
-
A generic utility library to fetch random images from various providers like Unsplash and Pexels, with built-in download capabilities.
|
|
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
|
|
|
@@ -8,7 +8,99 @@ A generic utility library to fetch random images from various providers like Uns
|
|
|
8
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)
|
|
@@ -149,8 +241,16 @@ Result object containing image information:
|
|
|
149
241
|
|
|
150
242
|
## Features
|
|
151
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
|
+
|
|
152
253
|
### Download Functionality
|
|
153
|
-
- ✅ Automatic directory creation
|
|
154
254
|
- ✅ Flexible filename options:
|
|
155
255
|
- Custom filename
|
|
156
256
|
- Keep original filename from URL (default)
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
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((resolve3, reject) => {
|
|
208
|
+
writer.on("finish", () => {
|
|
209
|
+
resolve3(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 import_chalk3 = __toESM(require("chalk"));
|
|
248
|
+
|
|
249
|
+
// src/cli/env-loader.ts
|
|
250
|
+
var path2 = __toESM(require("path"));
|
|
251
|
+
var dotenv = __toESM(require("dotenv"));
|
|
252
|
+
var fs2 = __toESM(require("fs"));
|
|
253
|
+
function loadEnvironment() {
|
|
254
|
+
const possibleEnvPaths = [
|
|
255
|
+
path2.resolve(process.cwd(), ".env"),
|
|
256
|
+
// Current working directory
|
|
257
|
+
path2.resolve(__dirname, ".env"),
|
|
258
|
+
// Same directory as the CLI script (dist/)
|
|
259
|
+
path2.resolve(__dirname, "..", ".env")
|
|
260
|
+
// Parent directory (root of project)
|
|
261
|
+
];
|
|
262
|
+
for (const envPath of possibleEnvPaths) {
|
|
263
|
+
if (fs2.existsSync(envPath)) {
|
|
264
|
+
dotenv.config({ path: envPath });
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function getApiKeys() {
|
|
270
|
+
return {
|
|
271
|
+
unsplash: process.env.UNSPLASH_KEY,
|
|
272
|
+
pexels: process.env.PEXELS_KEY,
|
|
273
|
+
pixabay: process.env.PIXABAY_KEY
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/cli/display.ts
|
|
278
|
+
var import_chalk = __toESM(require("chalk"));
|
|
279
|
+
var import_boxen = __toESM(require("boxen"));
|
|
280
|
+
function displayError(message) {
|
|
281
|
+
console.error(import_chalk.default.red(`
|
|
282
|
+
\u274C ${message}`));
|
|
283
|
+
}
|
|
284
|
+
function displayInfo(message) {
|
|
285
|
+
console.log(import_chalk.default.blue(`
|
|
286
|
+
\u2139\uFE0F ${message}`));
|
|
287
|
+
}
|
|
288
|
+
function displayImageInfo(image, providerName) {
|
|
289
|
+
const content = [
|
|
290
|
+
`${import_chalk.default.bold("\u{1F3A8} Provider:")} ${import_chalk.default.cyan(providerName)}`,
|
|
291
|
+
`${import_chalk.default.bold("\u{1F4D0} Size:")} ${import_chalk.default.yellow(`${image.width}x${image.height}`)}`,
|
|
292
|
+
`${import_chalk.default.bold("\u{1F464} Author:")} ${import_chalk.default.magenta(image.author)}`,
|
|
293
|
+
image.authorUrl ? `${import_chalk.default.bold("\u{1F517} Author URL:")} ${import_chalk.default.gray(image.authorUrl)}` : "",
|
|
294
|
+
`${import_chalk.default.bold("\u{1F310} Original:")} ${import_chalk.default.gray(image.originalUrl)}`,
|
|
295
|
+
`${import_chalk.default.bold("\u{1F4F7} Image URL:")} ${import_chalk.default.gray(image.url)}`
|
|
296
|
+
].filter(Boolean).join("\n");
|
|
297
|
+
console.log("\n" + (0, import_boxen.default)(content, {
|
|
298
|
+
padding: 1,
|
|
299
|
+
margin: 1,
|
|
300
|
+
borderStyle: "round",
|
|
301
|
+
borderColor: "green",
|
|
302
|
+
title: "\u{1F5BC}\uFE0F Image Details",
|
|
303
|
+
titleAlignment: "center"
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
function displayDownloadSuccess(filePath) {
|
|
307
|
+
const content = `${import_chalk.default.bold("\u{1F4C1} Saved to:")} ${import_chalk.default.cyan(filePath)}`;
|
|
308
|
+
console.log("\n" + (0, import_boxen.default)(content, {
|
|
309
|
+
padding: 1,
|
|
310
|
+
margin: 1,
|
|
311
|
+
borderStyle: "round",
|
|
312
|
+
borderColor: "blue",
|
|
313
|
+
title: "\u{1F4BE} Download Complete",
|
|
314
|
+
titleAlignment: "center"
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/cli/provider-manager.ts
|
|
319
|
+
function selectRandomProvider(providers) {
|
|
320
|
+
const availableProviders = Object.entries(providers).filter(([_, key]) => key).map(([name]) => name);
|
|
321
|
+
if (availableProviders.length === 0) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
return availableProviders[Math.floor(Math.random() * availableProviders.length)];
|
|
325
|
+
}
|
|
326
|
+
function createProvider(providerName, apiKey) {
|
|
327
|
+
switch (providerName) {
|
|
328
|
+
case "unsplash":
|
|
329
|
+
return new UnsplashProvider(apiKey);
|
|
330
|
+
case "pexels":
|
|
331
|
+
return new PexelsProvider(apiKey);
|
|
332
|
+
case "pixabay":
|
|
333
|
+
return new PixabayProvider(apiKey);
|
|
334
|
+
default:
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function getProviderInstance(providerName, providers) {
|
|
339
|
+
const name = providerName.toLowerCase();
|
|
340
|
+
const apiKey = providers[name];
|
|
341
|
+
if (!apiKey) {
|
|
342
|
+
displayError(`API key for ${providerName} not found.`);
|
|
343
|
+
console.log(`Please set ${providerName.toUpperCase()}_KEY environment variable.
|
|
344
|
+
`);
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
const provider = createProvider(name, apiKey);
|
|
348
|
+
if (!provider) {
|
|
349
|
+
displayError(`Unknown provider "${providerName}"`);
|
|
350
|
+
console.log("Available providers: unsplash, pexels, pixabay, random\n");
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return { provider, name };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/cli/options-builder.ts
|
|
357
|
+
function buildFetchOptions(options) {
|
|
358
|
+
const fetchOptions = {};
|
|
359
|
+
if (options.query) fetchOptions.query = options.query;
|
|
360
|
+
if (options.width) fetchOptions.width = options.width;
|
|
361
|
+
if (options.height) fetchOptions.height = options.height;
|
|
362
|
+
if (options.quality) fetchOptions.quality = options.quality;
|
|
363
|
+
if (options.orientation) fetchOptions.orientation = options.orientation;
|
|
364
|
+
return fetchOptions;
|
|
365
|
+
}
|
|
366
|
+
function buildDownloadOptions(options) {
|
|
367
|
+
const downloadOptions = {
|
|
368
|
+
overwrite: options.overwrite,
|
|
369
|
+
keepOriginalName: options.keepOriginalName
|
|
370
|
+
};
|
|
371
|
+
if (options.filename) {
|
|
372
|
+
downloadOptions.filename = options.filename;
|
|
373
|
+
}
|
|
374
|
+
return downloadOptions;
|
|
375
|
+
}
|
|
376
|
+
function getDownloadPath(options) {
|
|
377
|
+
return typeof options.download === "string" ? options.download : "./downloads";
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/cli/actions.ts
|
|
381
|
+
var import_ora = __toESM(require("ora"));
|
|
382
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
383
|
+
var path3 = __toESM(require("path"));
|
|
384
|
+
async function fetchImage(fetcher, providerName, fetchOptions) {
|
|
385
|
+
const spinner = (0, import_ora.default)({
|
|
386
|
+
text: import_chalk2.default.cyan("\u{1F50D} Searching for the perfect image..."),
|
|
387
|
+
spinner: "dots"
|
|
388
|
+
}).start();
|
|
389
|
+
try {
|
|
390
|
+
const image = await fetcher.getRandom(fetchOptions);
|
|
391
|
+
spinner.succeed(import_chalk2.default.green("\u2728 Image found!"));
|
|
392
|
+
return image;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
spinner.fail(import_chalk2.default.red("Failed to fetch image"));
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async function downloadImage(fetcher, image, downloadPath, downloadOptions) {
|
|
399
|
+
const resolvedPath = path3.resolve(downloadPath);
|
|
400
|
+
displayInfo(`Downloading to: ${import_chalk2.default.cyan(resolvedPath)}`);
|
|
401
|
+
const spinner = (0, import_ora.default)({
|
|
402
|
+
text: import_chalk2.default.cyan("\u{1F4BE} Downloading image..."),
|
|
403
|
+
spinner: "dots"
|
|
404
|
+
}).start();
|
|
405
|
+
try {
|
|
406
|
+
const filePath = await fetcher.download(image, downloadPath, downloadOptions);
|
|
407
|
+
spinner.succeed(import_chalk2.default.green("\u{1F4E6} Download complete!"));
|
|
408
|
+
return filePath;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
spinner.fail(import_chalk2.default.red("Download failed"));
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/cli.ts
|
|
416
|
+
loadEnvironment();
|
|
417
|
+
var program = new import_commander.Command();
|
|
418
|
+
program.name("random-image").description("\u{1F3A8} CLI tool to fetch random images from various providers").version("1.2.0");
|
|
419
|
+
program.command("fetch").description("\u{1F5BC}\uFE0F 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) => {
|
|
420
|
+
try {
|
|
421
|
+
const providers = getApiKeys();
|
|
422
|
+
let providerName = options.provider.toLowerCase();
|
|
423
|
+
if (providerName === "random") {
|
|
424
|
+
const selectedProvider = selectRandomProvider(providers);
|
|
425
|
+
if (!selectedProvider) {
|
|
426
|
+
displayError("No API keys found in environment variables.");
|
|
427
|
+
console.log(import_chalk3.default.yellow("\n\u{1F4A1} Please set at least one of:"));
|
|
428
|
+
console.log(import_chalk3.default.cyan(" \u2022 UNSPLASH_KEY"));
|
|
429
|
+
console.log(import_chalk3.default.cyan(" \u2022 PEXELS_KEY"));
|
|
430
|
+
console.log(import_chalk3.default.cyan(" \u2022 PIXABAY_KEY\n"));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
providerName = selectedProvider;
|
|
434
|
+
displayInfo(`Using random provider: ${import_chalk3.default.bold(providerName)}`);
|
|
435
|
+
}
|
|
436
|
+
const result = getProviderInstance(providerName, providers);
|
|
437
|
+
if (!result) {
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
const { provider, name } = result;
|
|
441
|
+
const fetcher = new RandomImage(provider);
|
|
442
|
+
const fetchOptions = buildFetchOptions(options);
|
|
443
|
+
const image = await fetchImage(fetcher, name, fetchOptions);
|
|
444
|
+
displayImageInfo(image, name);
|
|
445
|
+
if (options.download !== void 0) {
|
|
446
|
+
const downloadPath = getDownloadPath(options);
|
|
447
|
+
const downloadOptions = buildDownloadOptions(options);
|
|
448
|
+
const filePath = await downloadImage(
|
|
449
|
+
fetcher,
|
|
450
|
+
image,
|
|
451
|
+
downloadPath,
|
|
452
|
+
downloadOptions
|
|
453
|
+
);
|
|
454
|
+
displayDownloadSuccess(filePath);
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (error instanceof Error) {
|
|
458
|
+
displayError(error.message);
|
|
459
|
+
} else {
|
|
460
|
+
displayError("An unexpected error occurred");
|
|
461
|
+
}
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
program.parse(process.argv);
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
7
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/types.ts
|
|
11
|
+
var init_types = __esm({
|
|
12
|
+
"src/types.ts"() {
|
|
13
|
+
"use strict";
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// src/providers/unsplash.ts
|
|
18
|
+
import axios from "axios";
|
|
19
|
+
var UnsplashProvider;
|
|
20
|
+
var init_unsplash = __esm({
|
|
21
|
+
"src/providers/unsplash.ts"() {
|
|
22
|
+
"use strict";
|
|
23
|
+
UnsplashProvider = class {
|
|
24
|
+
constructor(accessKey) {
|
|
25
|
+
this.accessKey = accessKey;
|
|
26
|
+
}
|
|
27
|
+
async fetchRandomImage(options) {
|
|
28
|
+
const response = await axios.get("https://api.unsplash.com/photos/random", {
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Client-ID ${this.accessKey}`
|
|
31
|
+
},
|
|
32
|
+
params: {
|
|
33
|
+
query: options.query,
|
|
34
|
+
orientation: options.orientation
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const data = response.data;
|
|
38
|
+
const photo = Array.isArray(data) ? data[0] : data;
|
|
39
|
+
const baseUrl = photo.urls.raw;
|
|
40
|
+
const sizeParams = new URLSearchParams();
|
|
41
|
+
if (options.height || options.width) {
|
|
42
|
+
sizeParams.append("fit", "crop");
|
|
43
|
+
sizeParams.append("crop", "entropy");
|
|
44
|
+
}
|
|
45
|
+
if (options.width) sizeParams.append("w", options.width.toString());
|
|
46
|
+
if (options.height) sizeParams.append("h", options.height.toString());
|
|
47
|
+
if (options.quality) sizeParams.append("q", options.quality.toString());
|
|
48
|
+
const finalUrl = `${baseUrl}&${sizeParams.toString()}`;
|
|
49
|
+
return {
|
|
50
|
+
url: finalUrl,
|
|
51
|
+
width: options.width || photo.width,
|
|
52
|
+
height: options.height || photo.height,
|
|
53
|
+
author: photo.user.name,
|
|
54
|
+
authorUrl: photo.user.links.html,
|
|
55
|
+
originalUrl: photo.links.html
|
|
56
|
+
// Link to photo page
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// src/providers/pexels.ts
|
|
64
|
+
import axios2 from "axios";
|
|
65
|
+
var PexelsProvider;
|
|
66
|
+
var init_pexels = __esm({
|
|
67
|
+
"src/providers/pexels.ts"() {
|
|
68
|
+
"use strict";
|
|
69
|
+
PexelsProvider = class {
|
|
70
|
+
constructor(apiKey) {
|
|
71
|
+
this.apiKey = apiKey;
|
|
72
|
+
}
|
|
73
|
+
async fetchRandomImage(options) {
|
|
74
|
+
const endpoint = options.query ? "https://api.pexels.com/v1/search" : "https://api.pexels.com/v1/curated";
|
|
75
|
+
const randomPage = Math.floor(Math.random() * 100) + 1;
|
|
76
|
+
const params = {
|
|
77
|
+
per_page: 1,
|
|
78
|
+
page: randomPage
|
|
79
|
+
};
|
|
80
|
+
if (options.query) params.query = options.query;
|
|
81
|
+
const response = await axios2.get(endpoint, {
|
|
82
|
+
headers: {
|
|
83
|
+
Authorization: this.apiKey
|
|
84
|
+
},
|
|
85
|
+
params
|
|
86
|
+
});
|
|
87
|
+
const data = response.data;
|
|
88
|
+
if (!data.photos || data.photos.length === 0) {
|
|
89
|
+
throw new Error("No images found on Pexels");
|
|
90
|
+
}
|
|
91
|
+
const photo = data.photos[0];
|
|
92
|
+
const baseUrl = photo.src.original;
|
|
93
|
+
const sizeParams = new URLSearchParams();
|
|
94
|
+
sizeParams.append("auto", "compress");
|
|
95
|
+
sizeParams.append("cs", "tinysrgb");
|
|
96
|
+
if (options.width) sizeParams.append("w", options.width.toString());
|
|
97
|
+
if (options.height) sizeParams.append("h", options.height.toString());
|
|
98
|
+
const finalUrl = `${baseUrl}?${sizeParams.toString()}`;
|
|
99
|
+
return {
|
|
100
|
+
url: finalUrl,
|
|
101
|
+
width: options.width || photo.width,
|
|
102
|
+
height: options.height || photo.height,
|
|
103
|
+
author: photo.photographer,
|
|
104
|
+
authorUrl: photo.photographer_url,
|
|
105
|
+
originalUrl: photo.url
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// src/providers/pixabay.ts
|
|
113
|
+
import axios3 from "axios";
|
|
114
|
+
var PixabayProvider;
|
|
115
|
+
var init_pixabay = __esm({
|
|
116
|
+
"src/providers/pixabay.ts"() {
|
|
117
|
+
"use strict";
|
|
118
|
+
PixabayProvider = class {
|
|
119
|
+
constructor(apiKey) {
|
|
120
|
+
this.apiKey = apiKey;
|
|
121
|
+
}
|
|
122
|
+
async fetchRandomImage(options) {
|
|
123
|
+
const params = {
|
|
124
|
+
key: this.apiKey,
|
|
125
|
+
q: options.query || "",
|
|
126
|
+
per_page: 20
|
|
127
|
+
// Fetch a few to pick randomly
|
|
128
|
+
};
|
|
129
|
+
if (options.orientation) {
|
|
130
|
+
params.orientation = options.orientation === "portrait" ? "vertical" : "horizontal";
|
|
131
|
+
}
|
|
132
|
+
const response = await axios3.get("https://pixabay.com/api/", {
|
|
133
|
+
params
|
|
134
|
+
});
|
|
135
|
+
const hits = response.data.hits;
|
|
136
|
+
if (!hits || hits.length === 0) {
|
|
137
|
+
throw new Error("No images found");
|
|
138
|
+
}
|
|
139
|
+
const randomHit = hits[Math.floor(Math.random() * hits.length)];
|
|
140
|
+
return {
|
|
141
|
+
url: randomHit.largeImageURL || randomHit.webformatURL,
|
|
142
|
+
width: randomHit.imageWidth || randomHit.webformatWidth,
|
|
143
|
+
height: randomHit.imageHeight || randomHit.webformatHeight,
|
|
144
|
+
author: randomHit.user,
|
|
145
|
+
authorUrl: `https://pixabay.com/users/${randomHit.user}-${randomHit.user_id}/`,
|
|
146
|
+
originalUrl: randomHit.pageURL
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// src/image-fetcher.ts
|
|
154
|
+
import * as fs from "fs";
|
|
155
|
+
import * as path from "path";
|
|
156
|
+
import axios4 from "axios";
|
|
157
|
+
import { v7 as uuidv4 } from "uuid";
|
|
158
|
+
var RandomImage;
|
|
159
|
+
var init_image_fetcher = __esm({
|
|
160
|
+
"src/image-fetcher.ts"() {
|
|
161
|
+
"use strict";
|
|
162
|
+
RandomImage = class {
|
|
163
|
+
constructor(provider) {
|
|
164
|
+
this.provider = provider;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Fetches a random image based on the provided options.
|
|
168
|
+
* @param options - Configuration options for the image (width, height, query, etc.)
|
|
169
|
+
* @returns A promise that resolves to an ImageResult object.
|
|
170
|
+
*/
|
|
171
|
+
async getRandom(options = {}) {
|
|
172
|
+
return this.provider.fetchRandomImage(options);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Downloads an image to a specified directory.
|
|
176
|
+
* @param imageUrl - The URL of the image to download (can be a string or ImageResult object)
|
|
177
|
+
* @param destinationPath - The directory path where the image will be saved
|
|
178
|
+
* @param options - Download options (filename, overwrite, etc.)
|
|
179
|
+
* @returns A promise that resolves to the full path of the downloaded file
|
|
180
|
+
*/
|
|
181
|
+
async download(imageUrl, destinationPath, options = {}) {
|
|
182
|
+
const url = typeof imageUrl === "string" ? imageUrl : imageUrl.url;
|
|
183
|
+
if (!fs.existsSync(destinationPath)) {
|
|
184
|
+
fs.mkdirSync(destinationPath, { recursive: true });
|
|
185
|
+
}
|
|
186
|
+
let finalFilename;
|
|
187
|
+
if (options.filename) {
|
|
188
|
+
finalFilename = options.filename;
|
|
189
|
+
} else if (options.keepOriginalName) {
|
|
190
|
+
const urlPath = new URL(url).pathname;
|
|
191
|
+
const urlFilename = path.basename(urlPath);
|
|
192
|
+
if (urlFilename && urlFilename.length > 0 && urlFilename !== "/") {
|
|
193
|
+
finalFilename = urlFilename;
|
|
194
|
+
} else {
|
|
195
|
+
const ext = this.getExtensionFromUrl(url) || ".jpg";
|
|
196
|
+
finalFilename = `${uuidv4()}${ext}`;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
const ext = this.getExtensionFromUrl(url) || ".jpg";
|
|
200
|
+
finalFilename = `${uuidv4()}${ext}`;
|
|
201
|
+
}
|
|
202
|
+
if (!path.extname(finalFilename)) {
|
|
203
|
+
finalFilename += ".jpg";
|
|
204
|
+
}
|
|
205
|
+
const fullPath = path.join(destinationPath, finalFilename);
|
|
206
|
+
if (fs.existsSync(fullPath) && !options.overwrite) {
|
|
207
|
+
throw new Error(`File already exists: ${fullPath}. Set overwrite: true to replace it.`);
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const response = await axios4.get(url, {
|
|
211
|
+
responseType: "stream",
|
|
212
|
+
maxRedirects: 5
|
|
213
|
+
// Automatically handle redirects
|
|
214
|
+
});
|
|
215
|
+
const writer = fs.createWriteStream(fullPath);
|
|
216
|
+
response.data.pipe(writer);
|
|
217
|
+
return new Promise((resolve3, reject) => {
|
|
218
|
+
writer.on("finish", () => {
|
|
219
|
+
resolve3(fullPath);
|
|
220
|
+
});
|
|
221
|
+
writer.on("error", (err) => {
|
|
222
|
+
fs.unlink(fullPath, () => {
|
|
223
|
+
});
|
|
224
|
+
reject(err);
|
|
225
|
+
});
|
|
226
|
+
response.data.on("error", (err) => {
|
|
227
|
+
writer.close();
|
|
228
|
+
fs.unlink(fullPath, () => {
|
|
229
|
+
});
|
|
230
|
+
reject(err);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (axios4.isAxiosError(error)) {
|
|
235
|
+
throw new Error(`Failed to download image: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Helper method to extract file extension from URL
|
|
242
|
+
* @param url - The URL to extract extension from
|
|
243
|
+
* @returns The file extension (e.g., '.jpg', '.png') or null
|
|
244
|
+
*/
|
|
245
|
+
getExtensionFromUrl(url) {
|
|
246
|
+
try {
|
|
247
|
+
const urlPath = new URL(url).pathname;
|
|
248
|
+
const ext = path.extname(urlPath);
|
|
249
|
+
return ext || null;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// src/index.ts
|
|
259
|
+
var init_index = __esm({
|
|
260
|
+
"src/index.ts"() {
|
|
261
|
+
"use strict";
|
|
262
|
+
init_types();
|
|
263
|
+
init_unsplash();
|
|
264
|
+
init_pexels();
|
|
265
|
+
init_pixabay();
|
|
266
|
+
init_image_fetcher();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// src/cli/env-loader.ts
|
|
271
|
+
import * as path2 from "path";
|
|
272
|
+
import * as dotenv from "dotenv";
|
|
273
|
+
import * as fs2 from "fs";
|
|
274
|
+
function loadEnvironment() {
|
|
275
|
+
const possibleEnvPaths = [
|
|
276
|
+
path2.resolve(process.cwd(), ".env"),
|
|
277
|
+
// Current working directory
|
|
278
|
+
path2.resolve(__dirname, ".env"),
|
|
279
|
+
// Same directory as the CLI script (dist/)
|
|
280
|
+
path2.resolve(__dirname, "..", ".env")
|
|
281
|
+
// Parent directory (root of project)
|
|
282
|
+
];
|
|
283
|
+
for (const envPath of possibleEnvPaths) {
|
|
284
|
+
if (fs2.existsSync(envPath)) {
|
|
285
|
+
dotenv.config({ path: envPath });
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function getApiKeys() {
|
|
291
|
+
return {
|
|
292
|
+
unsplash: process.env.UNSPLASH_KEY,
|
|
293
|
+
pexels: process.env.PEXELS_KEY,
|
|
294
|
+
pixabay: process.env.PIXABAY_KEY
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
var init_env_loader = __esm({
|
|
298
|
+
"src/cli/env-loader.ts"() {
|
|
299
|
+
"use strict";
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// src/cli/display.ts
|
|
304
|
+
import chalk from "chalk";
|
|
305
|
+
import boxen from "boxen";
|
|
306
|
+
function displayError(message) {
|
|
307
|
+
console.error(chalk.red(`
|
|
308
|
+
\u274C ${message}`));
|
|
309
|
+
}
|
|
310
|
+
function displayInfo(message) {
|
|
311
|
+
console.log(chalk.blue(`
|
|
312
|
+
\u2139\uFE0F ${message}`));
|
|
313
|
+
}
|
|
314
|
+
function displayImageInfo(image, providerName) {
|
|
315
|
+
const content = [
|
|
316
|
+
`${chalk.bold("\u{1F3A8} Provider:")} ${chalk.cyan(providerName)}`,
|
|
317
|
+
`${chalk.bold("\u{1F4D0} Size:")} ${chalk.yellow(`${image.width}x${image.height}`)}`,
|
|
318
|
+
`${chalk.bold("\u{1F464} Author:")} ${chalk.magenta(image.author)}`,
|
|
319
|
+
image.authorUrl ? `${chalk.bold("\u{1F517} Author URL:")} ${chalk.gray(image.authorUrl)}` : "",
|
|
320
|
+
`${chalk.bold("\u{1F310} Original:")} ${chalk.gray(image.originalUrl)}`,
|
|
321
|
+
`${chalk.bold("\u{1F4F7} Image URL:")} ${chalk.gray(image.url)}`
|
|
322
|
+
].filter(Boolean).join("\n");
|
|
323
|
+
console.log("\n" + boxen(content, {
|
|
324
|
+
padding: 1,
|
|
325
|
+
margin: 1,
|
|
326
|
+
borderStyle: "round",
|
|
327
|
+
borderColor: "green",
|
|
328
|
+
title: "\u{1F5BC}\uFE0F Image Details",
|
|
329
|
+
titleAlignment: "center"
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
function displayDownloadSuccess(filePath) {
|
|
333
|
+
const content = `${chalk.bold("\u{1F4C1} Saved to:")} ${chalk.cyan(filePath)}`;
|
|
334
|
+
console.log("\n" + boxen(content, {
|
|
335
|
+
padding: 1,
|
|
336
|
+
margin: 1,
|
|
337
|
+
borderStyle: "round",
|
|
338
|
+
borderColor: "blue",
|
|
339
|
+
title: "\u{1F4BE} Download Complete",
|
|
340
|
+
titleAlignment: "center"
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
var init_display = __esm({
|
|
344
|
+
"src/cli/display.ts"() {
|
|
345
|
+
"use strict";
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// src/cli/provider-manager.ts
|
|
350
|
+
function selectRandomProvider(providers) {
|
|
351
|
+
const availableProviders = Object.entries(providers).filter(([_, key]) => key).map(([name]) => name);
|
|
352
|
+
if (availableProviders.length === 0) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
return availableProviders[Math.floor(Math.random() * availableProviders.length)];
|
|
356
|
+
}
|
|
357
|
+
function createProvider(providerName, apiKey) {
|
|
358
|
+
switch (providerName) {
|
|
359
|
+
case "unsplash":
|
|
360
|
+
return new UnsplashProvider(apiKey);
|
|
361
|
+
case "pexels":
|
|
362
|
+
return new PexelsProvider(apiKey);
|
|
363
|
+
case "pixabay":
|
|
364
|
+
return new PixabayProvider(apiKey);
|
|
365
|
+
default:
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function getProviderInstance(providerName, providers) {
|
|
370
|
+
const name = providerName.toLowerCase();
|
|
371
|
+
const apiKey = providers[name];
|
|
372
|
+
if (!apiKey) {
|
|
373
|
+
displayError(`API key for ${providerName} not found.`);
|
|
374
|
+
console.log(`Please set ${providerName.toUpperCase()}_KEY environment variable.
|
|
375
|
+
`);
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
const provider = createProvider(name, apiKey);
|
|
379
|
+
if (!provider) {
|
|
380
|
+
displayError(`Unknown provider "${providerName}"`);
|
|
381
|
+
console.log("Available providers: unsplash, pexels, pixabay, random\n");
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
return { provider, name };
|
|
385
|
+
}
|
|
386
|
+
var init_provider_manager = __esm({
|
|
387
|
+
"src/cli/provider-manager.ts"() {
|
|
388
|
+
"use strict";
|
|
389
|
+
init_index();
|
|
390
|
+
init_display();
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// src/cli/options-builder.ts
|
|
395
|
+
function buildFetchOptions(options) {
|
|
396
|
+
const fetchOptions = {};
|
|
397
|
+
if (options.query) fetchOptions.query = options.query;
|
|
398
|
+
if (options.width) fetchOptions.width = options.width;
|
|
399
|
+
if (options.height) fetchOptions.height = options.height;
|
|
400
|
+
if (options.quality) fetchOptions.quality = options.quality;
|
|
401
|
+
if (options.orientation) fetchOptions.orientation = options.orientation;
|
|
402
|
+
return fetchOptions;
|
|
403
|
+
}
|
|
404
|
+
function buildDownloadOptions(options) {
|
|
405
|
+
const downloadOptions = {
|
|
406
|
+
overwrite: options.overwrite,
|
|
407
|
+
keepOriginalName: options.keepOriginalName
|
|
408
|
+
};
|
|
409
|
+
if (options.filename) {
|
|
410
|
+
downloadOptions.filename = options.filename;
|
|
411
|
+
}
|
|
412
|
+
return downloadOptions;
|
|
413
|
+
}
|
|
414
|
+
function getDownloadPath(options) {
|
|
415
|
+
return typeof options.download === "string" ? options.download : "./downloads";
|
|
416
|
+
}
|
|
417
|
+
var init_options_builder = __esm({
|
|
418
|
+
"src/cli/options-builder.ts"() {
|
|
419
|
+
"use strict";
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// src/cli/actions.ts
|
|
424
|
+
import ora from "ora";
|
|
425
|
+
import chalk2 from "chalk";
|
|
426
|
+
import * as path3 from "path";
|
|
427
|
+
async function fetchImage(fetcher, providerName, fetchOptions) {
|
|
428
|
+
const spinner = ora({
|
|
429
|
+
text: chalk2.cyan("\u{1F50D} Searching for the perfect image..."),
|
|
430
|
+
spinner: "dots"
|
|
431
|
+
}).start();
|
|
432
|
+
try {
|
|
433
|
+
const image = await fetcher.getRandom(fetchOptions);
|
|
434
|
+
spinner.succeed(chalk2.green("\u2728 Image found!"));
|
|
435
|
+
return image;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
spinner.fail(chalk2.red("Failed to fetch image"));
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function downloadImage(fetcher, image, downloadPath, downloadOptions) {
|
|
442
|
+
const resolvedPath = path3.resolve(downloadPath);
|
|
443
|
+
displayInfo(`Downloading to: ${chalk2.cyan(resolvedPath)}`);
|
|
444
|
+
const spinner = ora({
|
|
445
|
+
text: chalk2.cyan("\u{1F4BE} Downloading image..."),
|
|
446
|
+
spinner: "dots"
|
|
447
|
+
}).start();
|
|
448
|
+
try {
|
|
449
|
+
const filePath = await fetcher.download(image, downloadPath, downloadOptions);
|
|
450
|
+
spinner.succeed(chalk2.green("\u{1F4E6} Download complete!"));
|
|
451
|
+
return filePath;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
spinner.fail(chalk2.red("Download failed"));
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
var init_actions = __esm({
|
|
458
|
+
"src/cli/actions.ts"() {
|
|
459
|
+
"use strict";
|
|
460
|
+
init_display();
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// src/cli.ts
|
|
465
|
+
import { Command } from "commander";
|
|
466
|
+
import chalk3 from "chalk";
|
|
467
|
+
var require_cli = __commonJS({
|
|
468
|
+
"src/cli.ts"() {
|
|
469
|
+
init_index();
|
|
470
|
+
init_env_loader();
|
|
471
|
+
init_display();
|
|
472
|
+
init_provider_manager();
|
|
473
|
+
init_options_builder();
|
|
474
|
+
init_actions();
|
|
475
|
+
loadEnvironment();
|
|
476
|
+
var program = new Command();
|
|
477
|
+
program.name("random-image").description("\u{1F3A8} CLI tool to fetch random images from various providers").version("1.2.0");
|
|
478
|
+
program.command("fetch").description("\u{1F5BC}\uFE0F 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) => {
|
|
479
|
+
try {
|
|
480
|
+
const providers = getApiKeys();
|
|
481
|
+
let providerName = options.provider.toLowerCase();
|
|
482
|
+
if (providerName === "random") {
|
|
483
|
+
const selectedProvider = selectRandomProvider(providers);
|
|
484
|
+
if (!selectedProvider) {
|
|
485
|
+
displayError("No API keys found in environment variables.");
|
|
486
|
+
console.log(chalk3.yellow("\n\u{1F4A1} Please set at least one of:"));
|
|
487
|
+
console.log(chalk3.cyan(" \u2022 UNSPLASH_KEY"));
|
|
488
|
+
console.log(chalk3.cyan(" \u2022 PEXELS_KEY"));
|
|
489
|
+
console.log(chalk3.cyan(" \u2022 PIXABAY_KEY\n"));
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
providerName = selectedProvider;
|
|
493
|
+
displayInfo(`Using random provider: ${chalk3.bold(providerName)}`);
|
|
494
|
+
}
|
|
495
|
+
const result = getProviderInstance(providerName, providers);
|
|
496
|
+
if (!result) {
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
const { provider, name } = result;
|
|
500
|
+
const fetcher = new RandomImage(provider);
|
|
501
|
+
const fetchOptions = buildFetchOptions(options);
|
|
502
|
+
const image = await fetchImage(fetcher, name, fetchOptions);
|
|
503
|
+
displayImageInfo(image, name);
|
|
504
|
+
if (options.download !== void 0) {
|
|
505
|
+
const downloadPath = getDownloadPath(options);
|
|
506
|
+
const downloadOptions = buildDownloadOptions(options);
|
|
507
|
+
const filePath = await downloadImage(
|
|
508
|
+
fetcher,
|
|
509
|
+
image,
|
|
510
|
+
downloadPath,
|
|
511
|
+
downloadOptions
|
|
512
|
+
);
|
|
513
|
+
displayDownloadSuccess(filePath);
|
|
514
|
+
}
|
|
515
|
+
} catch (error) {
|
|
516
|
+
if (error instanceof Error) {
|
|
517
|
+
displayError(error.message);
|
|
518
|
+
} else {
|
|
519
|
+
displayError("An unexpected error occurred");
|
|
520
|
+
}
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
program.parse(process.argv);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
export default require_cli();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nghiavuive/random-image",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Fetch random images from Unsplash, Pexels, and Pixabay with CLI support",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -32,13 +32,20 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.2.0",
|
|
34
34
|
"@types/uuid": "^10.0.0",
|
|
35
|
-
"dotenv": "^17.2.3",
|
|
36
35
|
"tsup": "^8.5.1",
|
|
37
36
|
"typescript": "^5.9.3",
|
|
38
37
|
"vitest": "^4.0.18"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
40
|
"axios": "^1.13.4",
|
|
41
|
+
"boxen": "^8.0.1",
|
|
42
|
+
"chalk": "^5.6.2",
|
|
43
|
+
"commander": "^12.1.0",
|
|
44
|
+
"dotenv": "^17.2.3",
|
|
45
|
+
"ora": "^9.1.0",
|
|
42
46
|
"uuid": "^13.0.0"
|
|
47
|
+
},
|
|
48
|
+
"bin": {
|
|
49
|
+
"random-image": "./dist/cli.js"
|
|
43
50
|
}
|
|
44
51
|
}
|