@nghiavuive/random-image 1.1.0 → 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 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,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);
package/dist/cli.mjs ADDED
@@ -0,0 +1,377 @@
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((resolve, reject) => {
218
+ writer.on("finish", () => {
219
+ resolve(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.ts
271
+ import { Command } from "commander";
272
+ import * as path2 from "path";
273
+ import * as dotenv from "dotenv";
274
+ import * as fs2 from "fs";
275
+ var require_cli = __commonJS({
276
+ "src/cli.ts"() {
277
+ init_index();
278
+ var possibleEnvPaths = [
279
+ path2.resolve(process.cwd(), ".env"),
280
+ // Current working directory
281
+ path2.resolve(__dirname, ".env"),
282
+ // Same directory as the CLI script (dist/)
283
+ path2.resolve(__dirname, "..", ".env")
284
+ // Parent directory (root of project)
285
+ ];
286
+ for (const envPath of possibleEnvPaths) {
287
+ if (fs2.existsSync(envPath)) {
288
+ dotenv.config({ path: envPath });
289
+ console.log(`Loaded environment from: ${envPath}`);
290
+ break;
291
+ }
292
+ }
293
+ var program = new Command();
294
+ program.name("random-image").description("CLI tool to fetch random images from various providers").version("1.2.0");
295
+ 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) => {
296
+ try {
297
+ const providers = {
298
+ unsplash: process.env.UNSPLASH_KEY,
299
+ pexels: process.env.PEXELS_KEY,
300
+ pixabay: process.env.PIXABAY_KEY
301
+ };
302
+ let providerName = options.provider.toLowerCase();
303
+ if (providerName === "random") {
304
+ const availableProviders = Object.entries(providers).filter(([_, key]) => key).map(([name]) => name);
305
+ if (availableProviders.length === 0) {
306
+ console.error("Error: No API keys found in environment variables.");
307
+ console.error("Please set at least one of: UNSPLASH_KEY, PEXELS_KEY, PIXABAY_KEY");
308
+ process.exit(1);
309
+ }
310
+ providerName = availableProviders[Math.floor(Math.random() * availableProviders.length)];
311
+ console.log(`Using random provider: ${providerName}`);
312
+ }
313
+ let provider;
314
+ const apiKey = providers[providerName];
315
+ if (!apiKey) {
316
+ console.error(`Error: API key for ${providerName} not found.`);
317
+ console.error(`Please set ${providerName.toUpperCase()}_KEY environment variable.`);
318
+ process.exit(1);
319
+ }
320
+ switch (providerName) {
321
+ case "unsplash":
322
+ provider = new UnsplashProvider(apiKey);
323
+ break;
324
+ case "pexels":
325
+ provider = new PexelsProvider(apiKey);
326
+ break;
327
+ case "pixabay":
328
+ provider = new PixabayProvider(apiKey);
329
+ break;
330
+ default:
331
+ console.error(`Error: Unknown provider "${providerName}"`);
332
+ console.error("Available providers: unsplash, pexels, pixabay, random");
333
+ process.exit(1);
334
+ }
335
+ const fetcher = new RandomImage(provider);
336
+ const fetchOptions = {};
337
+ if (options.query) fetchOptions.query = options.query;
338
+ if (options.width) fetchOptions.width = options.width;
339
+ if (options.height) fetchOptions.height = options.height;
340
+ if (options.quality) fetchOptions.quality = options.quality;
341
+ if (options.orientation) fetchOptions.orientation = options.orientation;
342
+ console.log("Fetching random image...");
343
+ const image = await fetcher.getRandom(fetchOptions);
344
+ console.log("\n\u2713 Image fetched successfully!");
345
+ console.log(` URL: ${image.url}`);
346
+ console.log(` Size: ${image.width}x${image.height}`);
347
+ console.log(` Author: ${image.author}`);
348
+ if (image.authorUrl) console.log(` Author URL: ${image.authorUrl}`);
349
+ console.log(` Original: ${image.originalUrl}`);
350
+ if (options.download !== void 0) {
351
+ const downloadPath = typeof options.download === "string" ? options.download : "./downloads";
352
+ console.log(`
353
+ Downloading to ${path2.resolve(downloadPath)}...`);
354
+ const downloadOptions = {
355
+ overwrite: options.overwrite,
356
+ keepOriginalName: options.keepOriginalName
357
+ };
358
+ if (options.filename) {
359
+ downloadOptions.filename = options.filename;
360
+ }
361
+ const filePath = await fetcher.download(image, downloadPath, downloadOptions);
362
+ console.log(`\u2713 Downloaded: ${filePath}`);
363
+ }
364
+ } catch (error) {
365
+ if (error instanceof Error) {
366
+ console.error(`
367
+ Error: ${error.message}`);
368
+ } else {
369
+ console.error("\nAn unexpected error occurred");
370
+ }
371
+ process.exit(1);
372
+ }
373
+ });
374
+ program.parse(process.argv);
375
+ }
376
+ });
377
+ export default require_cli();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nghiavuive/random-image",
3
- "version": "1.1.0",
4
- "description": "",
3
+ "version": "1.2.0",
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,17 @@
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
+ "commander": "^12.1.0",
42
+ "dotenv": "^17.2.3",
42
43
  "uuid": "^13.0.0"
44
+ },
45
+ "bin": {
46
+ "random-image": "./dist/cli.js"
43
47
  }
44
48
  }