nekosia.js 0.1.5 → 0.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Nekosia
3
+ Copyright (c) 2024-2025 Nekosia
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -47,7 +47,7 @@ Check out the [official documentation](https://nekosia.cat/documentation) to lea
47
47
  To install the Nekosia.js module, use the following command:
48
48
 
49
49
  ```bash
50
- npm install neksosia.js
50
+ npm install nekosia.js
51
51
  ```
52
52
 
53
53
 
@@ -63,12 +63,16 @@ The full list of tags is available [on the Booru site](https://nekosia.cat/booru
63
63
  const { NekosiaAPI } = require('nekosia.js');
64
64
 
65
65
  (async () => {
66
- const response = await NekosiaAPI.fetchImages('catgirl');
67
- console.log(response); // Sample response: https://nekosia.cat/documentation?page=api-endpoints#example-response
66
+ const response1 = await NekosiaAPI.fetchCategoryImages('catgirl');
67
+ console.log(response1);
68
+
69
+ const response2 = await NekosiaAPI.fetchImages({ session: 'ip', count: 1, tags: ['cute', 'blue-hair'], blacklist: ['yellow-hair'] });
70
+ console.log(response2);
71
+
72
+ // https://nekosia.cat/documentation?page=api-endpoints#example-response
68
73
  })();
69
74
  ```
70
75
 
71
-
72
76
  ### IP-based Sessions
73
77
  In this example, we used an IP-based session. What does this mean? Thanks to this solution, a user with a specific IP address will not encounter duplicate images when selecting them randomly.
74
78
 
@@ -76,7 +80,7 @@ In this example, we used an IP-based session. What does this mean? Thanks to thi
76
80
  const { NekosiaAPI } = require('nekosia.js');
77
81
 
78
82
  (async () => {
79
- const response = await NekosiaAPI.fetchImages('catgirl', {
83
+ const response = await NekosiaAPI.fetchCategoryImages('catgirl', {
80
84
  session: 'ip',
81
85
  count: 1,
82
86
  additionalTags: [],
@@ -94,7 +98,7 @@ You can also use `id`, but this requires providing a user identifier (e.g., from
94
98
  const { NekosiaAPI } = require('nekosia.js');
95
99
 
96
100
  (async () => {
97
- const response = await NekosiaAPI.fetchImages('catgirl', {
101
+ const response = await NekosiaAPI.fetchCategoryImages('catgirl', {
98
102
  session: 'id',
99
103
  id: '561621386765971781',
100
104
  count: 1,
@@ -110,6 +114,16 @@ const { NekosiaAPI } = require('nekosia.js');
110
114
  https://github.com/Nekosia-API/nekosia.js/tree/main/examples
111
115
 
112
116
 
117
+ ## Tags
118
+ ```js
119
+ const { NekosiaAPI } = require('nekosia.js');
120
+
121
+ (async () => {
122
+ console.log(await NekosiaAPI.fetchTags()); // Simply returns all available tags, etc.
123
+ })();
124
+ ```
125
+
126
+
113
127
  ## Versions
114
128
  ```js
115
129
  const { NekosiaVersion } = require('nekosia.js');
@@ -122,7 +136,7 @@ const { NekosiaVersion } = require('nekosia.js');
122
136
 
123
137
 
124
138
  ## ⭐ » Thanks
125
- If you find the API or this module useful, consider giving a star to the [repository](https://github.com/sefinek24/nekosia.js).
139
+ If you find the API or this module useful, consider giving a star to the [repository](https://github.com/Nekosia-API/nekosia.js).
126
140
  If you have any questions or issues, create a new [Issue](https://github.com/Nekosia-API/nekosia.js/issues/new) or join the [Discord server](https://discord.gg/pba76vJhcP).
127
141
 
128
142
 
@@ -1,8 +1,8 @@
1
1
  const { NekosiaAPI } = require('../index.js');
2
2
 
3
- const fetchImages = async (category, options = {}) => {
3
+ const fetch = async (category, options = {}) => {
4
4
  try {
5
- const response = await NekosiaAPI.fetchImages(category, options);
5
+ const response = await NekosiaAPI.fetchCategoryImages(category, options);
6
6
  console.log(`${category.toUpperCase()}:`, response);
7
7
  } catch (err) {
8
8
  console.error(`Error fetching ${category} images:`, err);
@@ -10,6 +10,7 @@ const fetchImages = async (category, options = {}) => {
10
10
  };
11
11
 
12
12
  (async () => {
13
- await fetchImages('catgirl');
14
- await fetchImages('foxgirl', { session: 'id', id: 'user123', count: 2 });
13
+ await fetch('catgirl');
14
+ await fetch('foxgirl', { session: 'id', id: 'user123', count: 2 });
15
+ await fetch('catgirl', { tags: 'animal-ears' });
15
16
  })();
@@ -0,0 +1,12 @@
1
+ const { NekosiaAPI } = require('../index.js');
2
+
3
+ (async () => {
4
+ const response = await NekosiaAPI.fetchCategoryImages('foxgirl', {
5
+ session: 'ip',
6
+ count: 1,
7
+ additionalTags: ['cute', 'sakura', 'blue-hair', 'blue-eyes'],
8
+ blacklistedTags: ['beret', 'hat'],
9
+ });
10
+
11
+ console.log(response);
12
+ })();
@@ -0,0 +1,12 @@
1
+ const { NekosiaAPI } = require('../index.js');
2
+
3
+ (async () => {
4
+ const response = await NekosiaAPI.fetchImages({
5
+ session: 'ip',
6
+ count: 1,
7
+ tags: ['cute', 'sakura', 'blue-hair', 'blue-eyes'],
8
+ blacklist: ['beret', 'hat'],
9
+ });
10
+
11
+ console.log(response);
12
+ })();
@@ -0,0 +1,6 @@
1
+ const { NekosiaAPI } = require('../index.js');
2
+
3
+ (async () => {
4
+ const json = await NekosiaAPI.fetchTags();
5
+ console.log(json);
6
+ })();
@@ -1,6 +1,8 @@
1
1
  const { NekosiaVersion } = require('../index.js');
2
2
 
3
3
  (async () => {
4
- console.log(NekosiaVersion.module);
5
- console.log(await NekosiaVersion.api());
4
+ console.log(`Nekosia.js: v${NekosiaVersion.module}`);
5
+
6
+ const data = await NekosiaVersion.api();
7
+ console.log(`API: v${data.version}`);
6
8
  })();
package/index.js CHANGED
@@ -25,37 +25,43 @@ class NekosiaAPI {
25
25
  }
26
26
  }
27
27
 
28
- async fetchImages(category, options = {}) {
28
+ async fetchCategoryImages(category, options = {}) {
29
29
  if (!category) {
30
- throw new Error('The image category is required. For example, use fetchImages(\'catgirl\').');
30
+ throw new Error('Image category is required. For example: fetchCategoryImages(\'catgirl\')');
31
31
  }
32
32
 
33
33
  if (options.session && !['id', 'ip'].includes(options.session)) {
34
- throw new Error('The `session` setting can contain only the following values `id` and `ip`, both as strings.');
34
+ throw new Error('The `session` setting can contain only the following values `id` and `ip`, both as strings');
35
35
  }
36
36
 
37
37
  if (!options.session && options.id) {
38
38
  throw new Error('`id` is not required if the session is `null` or `undefined`');
39
39
  }
40
40
 
41
- const queryString = this.buildQueryParams({
42
- session: null,
43
- id: null,
44
- count: 1,
45
- additionalTags: [],
46
- blacklistedTags: [],
47
- ...options
48
- });
49
-
50
- return this.makeHttpRequest(`${API_URL}/images/${category}?${queryString}`);
41
+ const query = this.buildQueryParams(options);
42
+ return this.makeHttpRequest(`${API_URL}/images/${category}${query ? `?${query}` : ''}`);
51
43
  }
52
44
 
53
- async fetchShadowImages(options = {}) {
54
- if (!Array.isArray(options.additionalTags) || options.additionalTags.length === 0) {
55
- throw new Error('`additionalTags` must be a non-empty array for the shadow category');
45
+ async fetchImages(options = {}) {
46
+ if (!Array.isArray(options.tags) || options.tags.length === 0) {
47
+ throw new Error('`tags` must be a non-empty array');
48
+ }
49
+
50
+ if (Array.isArray(options.blacklistedTags)) {
51
+ throw new Error('Unexpected `blacklistedTags` in `fetchImages()`, use `blacklist` instead');
56
52
  }
57
53
 
58
- return this.fetchImages('shadow', options);
54
+ return this.fetchCategoryImages('nothing', {
55
+ session: options.session,
56
+ id: options.id,
57
+ count: options.count,
58
+ additionalTags: options.tags,
59
+ blacklistedTags: options.blacklist,
60
+ });
61
+ }
62
+
63
+ async fetchTags() {
64
+ return this.makeHttpRequest(`${API_URL}/tags`);
59
65
  }
60
66
 
61
67
  async fetchById(id) {
@@ -67,12 +73,10 @@ class NekosiaAPI {
67
73
 
68
74
  const NekosiaVersion = {
69
75
  module: https.version,
70
- api: async () => {
71
- return await https.get(BASE_URL);
72
- }
76
+ api: async () => https.get(BASE_URL),
73
77
  };
74
78
 
75
79
  module.exports = {
76
80
  NekosiaAPI: new NekosiaAPI(),
77
- NekosiaVersion
81
+ NekosiaVersion,
78
82
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nekosia.js",
3
- "version": "0.1.5",
4
- "description": "A simple wrapper for the Nekosia API that offers seamless access to random anime images. Add a touch of anime magic and feline charm to your projects, meow~~! Find out why Nekosia is the purrfect choice!",
3
+ "version": "0.2.1",
4
+ "description": "A simple wrapper for the Nekosia API that offers seamless access to random anime images. Add a touch of anime magic and feline charm to your projects, meow~! Find out why Nekosia is the purrfect choice!",
5
5
  "keywords": [
6
6
  "anime api",
7
7
  "anime art",
@@ -15,6 +15,7 @@
15
15
  "anime",
16
16
  "anime-api",
17
17
  "api wrapper",
18
+ "api-wrapper",
18
19
  "cat ears",
19
20
  "cat",
20
21
  "catgirl",
@@ -22,8 +23,9 @@
22
23
  "cute animals",
23
24
  "cute nekos",
24
25
  "feline",
26
+ "girl",
27
+ "girls",
25
28
  "image api",
26
- "japan",
27
29
  "javascript",
28
30
  "js library",
29
31
  "kawaii",
@@ -36,7 +38,6 @@
36
38
  "nekosia",
37
39
  "otaku",
38
40
  "random anime",
39
- "typescript",
40
41
  "waifu"
41
42
  ],
42
43
  "homepage": "https://nekosia.cat",
@@ -50,14 +51,14 @@
50
51
  "license": "MIT",
51
52
  "author": "Sefinek <contact@sefinek.net> (https://sefinek.net)",
52
53
  "main": "index.js",
53
- "typings": "types/index.d.ts",
54
+ "types": "types/index.d.ts",
54
55
  "scripts": {
55
56
  "test": "jest",
56
57
  "up": "ncu -u && npm install && npm update && npm audit fix"
57
58
  },
58
59
  "devDependencies": {
59
- "@eslint/js": "^9.11.1",
60
- "globals": "^15.10.0",
60
+ "@eslint/js": "^9.25.0",
61
+ "globals": "^16.0.0",
61
62
  "jest": "^29.7.0"
62
63
  }
63
64
  }
package/services/https.js CHANGED
@@ -1,45 +1,49 @@
1
1
  const https = require('https');
2
- const { name, version, homepage, devDependencies } = require('../package.json');
2
+ const { name, version, devDependencies } = require('../package.json');
3
3
 
4
4
  const headers = {
5
- 'User-Agent': `Mozilla/5.0 (compatible; ${name}/${version}; +${homepage})${process.env.JEST_WORKER_ID ? ` jest/${devDependencies.jest.replace(/^[^0-9]*/, '')}` : ''}`,
5
+ 'User-Agent': `${name}/${version} (+https://github.com/Nekosia-API/nekosia.js)${process.env.JEST_WORKER_ID ? ` jest/${devDependencies.jest.replace(/^[^0-9]*/, '')}` : ''}`,
6
6
  'Accept': 'application/json',
7
7
  'Content-Type': 'application/json',
8
8
  'Cache-Control': 'no-cache',
9
9
  'Connection': 'keep-alive',
10
- 'DNT': '1'
10
+ 'DNT': '1',
11
11
  };
12
12
 
13
- const makeRequest = url => new Promise((resolve, reject) => {
14
- const req = https.get(url, { headers, timeout: 9000 }, res => {
15
- let data = '';
13
+ const timeout = 25000;
16
14
 
17
- res.on('data', chunk => {
18
- data += chunk;
19
- });
15
+ const get = async url => {
16
+ if (!url || typeof url !== 'string') throw new Error('Missing URL, expected a string');
20
17
 
21
- res.on('end', () => {
22
- if ((res.statusCode < 200 || res.statusCode >= 300) && res.statusCode !== 400) {
23
- return reject(new Error(`HTTP Status Code: ${res.statusCode}`));
18
+ return new Promise((resolve, reject) => {
19
+ const req = https.get(url, { headers, timeout }, res => {
20
+ const { statusCode } = res;
21
+ if ((statusCode < 200 || statusCode >= 300) && statusCode !== 400) {
22
+ req.destroy();
23
+ return reject(new Error(`Unexpected HTTP Status Code: ${statusCode || 'unknown'}`));
24
24
  }
25
25
 
26
- try {
27
- const parsedData = JSON.parse(data);
28
- resolve(parsedData);
29
- } catch (err) {
30
- reject(new Error(`Failed to parse JSON: ${err.message}. Response: ${data}`));
31
- }
26
+ let data = '';
27
+ res.on('data', chunk => data += chunk);
28
+ res.on('end', () => {
29
+ try {
30
+ resolve(JSON.parse(data));
31
+ } catch (err) {
32
+ reject(err);
33
+ }
34
+ });
32
35
  });
33
- });
34
36
 
35
- req.on('error', err => reject(new Error(`Request error: ${err.message}`)));
37
+ req.on('error', err => {
38
+ req.destroy();
39
+ reject(err);
40
+ });
36
41
 
37
- req.on('timeout', () => {
38
- req.destroy();
39
- reject(new Error('Timeout error'));
42
+ req.on('timeout', () => {
43
+ req.destroy();
44
+ reject(new Error(`Request timed out after ${timeout} ms`));
45
+ });
40
46
  });
47
+ };
41
48
 
42
- req.end();
43
- });
44
-
45
- module.exports = { get: makeRequest, version };
49
+ module.exports = { get, version };
package/test/api.test.js CHANGED
@@ -1,110 +1,134 @@
1
1
  const { NekosiaAPI } = require('../index.js');
2
2
 
3
- describe('NekosiaAPI (API Tests)', () => {
3
+ const expectValidImageObject = res => {
4
+ expect(res.success).toBe(true);
5
+ expect(res.status).toBe(200);
6
+ expect(res).toHaveProperty('image');
7
+ expect(res.image.original.url).toEqual(expect.any(String));
8
+ expect(res.tags.length).toBeGreaterThan(0);
9
+ };
4
10
 
5
- describe('fetchImages', () => {
6
- it('should fetch images for the given category', async () => {
7
- const res = await NekosiaAPI.fetchImages('catgirl', { count: 1 });
11
+ describe('nekosia.js tests', () => {
12
+
13
+ describe('fetchCategoryImages', () => {
14
+
15
+ it('should fetch one image when count is 1', async () => {
16
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 1 });
17
+ expectValidImageObject(res);
18
+ });
19
+
20
+ it('should fetch multiple images when count is between 2 and 24', async () => {
21
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 3 });
8
22
 
9
- expect(res).toBeInstanceOf(Object);
10
23
  expect(res.success).toBe(true);
11
24
  expect(res.status).toBe(200);
25
+ expect(Array.isArray(res.images)).toBe(true);
26
+ expect(res.images.length).toBe(3);
27
+
28
+ for (const img of res.images) {
29
+ expect(img).toHaveProperty('image');
30
+ expect(img.image.original.url).toEqual(expect.any(String));
31
+ }
32
+ });
33
+
34
+ it('should fetch maximum allowed number of images (24)', async () => {
35
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 24 });
12
36
 
13
- expect(res).toHaveProperty('category');
14
- expect(res).toHaveProperty('id');
15
- expect(res).toHaveProperty('image');
16
-
17
- expect(res.image).toHaveProperty('original');
18
- expect(res.image).toHaveProperty('compressed');
19
- expect(res.image.original).toHaveProperty('url');
20
- expect(res.image.original).toHaveProperty('extension');
21
- expect(res.image.compressed).toHaveProperty('url');
22
- expect(res.image.compressed).toHaveProperty('extension');
23
-
24
- expect(res.metadata).toHaveProperty('original');
25
- expect(res.metadata.original).toHaveProperty('width');
26
- expect(res.metadata.original).toHaveProperty('height');
27
- expect(res.metadata.original).toHaveProperty('size');
28
- expect(res.metadata.original).toHaveProperty('extension');
29
-
30
- expect(res.metadata).toHaveProperty('compressed');
31
- expect(res.metadata.compressed).toHaveProperty('width');
32
- expect(res.metadata.compressed).toHaveProperty('height');
33
- expect(res.metadata.compressed).toHaveProperty('size');
34
- expect(res.metadata.compressed).toHaveProperty('extension');
35
-
36
- expect(res).toHaveProperty('tags');
37
- expect(res.tags).toBeInstanceOf(Array);
38
- expect(res.tags.length).toBeGreaterThan(0);
39
-
40
- expect(res).toHaveProperty('rating');
41
- expect(res).toHaveProperty('source');
42
- expect(res.source).toHaveProperty('url');
37
+ expect(res.success).toBe(true);
38
+ expect(res.status).toBe(200);
39
+ expect(Array.isArray(res.images)).toBe(true);
40
+ expect(res.images.length).toBe(24);
43
41
  });
44
42
 
45
- it('should return an error for an invalid category', async () => {
46
- const res = await NekosiaAPI.fetchImages('invalid-category', { count: 1 });
43
+ it('should return error if count exceeds the maximum (25)', async () => {
44
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 25 });
47
45
 
48
46
  expect(res.success).toBe(false);
49
47
  expect(res.status).toBe(400);
48
+ expect(res.message).toMatch(/Count must be between 1 and 24/i);
50
49
  });
51
50
 
52
- it('should return a specified number of images', async () => {
53
- const res = await NekosiaAPI.fetchImages('catgirl', { count: 3 });
54
-
55
- expect(res).toBeInstanceOf(Object);
56
- expect(res.success).toBe(true);
57
- expect(res.status).toBe(200);
58
- expect(res.count).toBe(3);
59
- expect(res).toHaveProperty('images');
60
- expect(res.images).toBeInstanceOf(Array);
61
- expect(res.images.length).toBe(3);
51
+ it('should return error for invalid count value', async () => {
52
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 'invalid' });
53
+ expect(res.success).toBe(false);
54
+ expect(res.status).toBe(400);
62
55
  });
63
- });
64
56
 
65
- describe('fetchShadowImages', () => {
66
- it('should handle no images found for shadow category with additional tags', async () => {
67
- const res = await NekosiaAPI.fetchShadowImages({ count: 1, additionalTags: ['wTbf8J0TirS6a4fO5uyKcRazZOlO5h6o', 'xX9f9pwDAgsM3Li1LwsJ3tXQfGKW4WA0'] });
57
+ it('should return error for negative count', async () => {
58
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: -5 });
59
+ expect(res.success).toBe(false);
60
+ expect(res.status).toBe(400);
61
+ });
68
62
 
63
+ it('should return error for count = 0', async () => {
64
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 0 });
69
65
  expect(res.success).toBe(false);
70
66
  expect(res.status).toBe(400);
71
67
  });
72
68
 
73
- it('should throw an error if additionalTagsArray is empty', async () => {
74
- await expect(NekosiaAPI.fetchShadowImages([])).rejects.toThrow('`additionalTags` must be a non-empty array for the shadow category');
69
+ it('should handle random category', async () => {
70
+ const res = await NekosiaAPI.fetchCategoryImages('random');
71
+
72
+ expectValidImageObject(res);
73
+ });
74
+
75
+ it('should return error for invalid category', async () => {
76
+ const res = await NekosiaAPI.fetchCategoryImages('invalid-category');
77
+ expect(res.success).toBe(false);
78
+ expect(res.status).toBe(400);
75
79
  });
80
+ });
81
+
82
+ describe('fetchImages', () => {
83
+ it('should return error when no image matches tags', async () => {
84
+ const res = await NekosiaAPI.fetchImages({
85
+ count: 1,
86
+ tags: ['invalid-tag1', 'invalid-tag2'],
87
+ });
76
88
 
77
- it('should return an error response for invalid count parameter', async () => {
78
- const res = await NekosiaAPI.fetchImages('catgirl', { count: 'invalid' });
79
89
  expect(res.success).toBe(false);
80
90
  expect(res.status).toBe(400);
81
- expect(res.message).toBe('Invalid count parameter. Expected a number between 1 and 48.');
91
+ });
92
+
93
+ it('should throw if tags array is empty', async () => {
94
+ await expect(() => NekosiaAPI.fetchImages({ tags: [] }))
95
+ .rejects
96
+ .toThrow('`tags` must be a non-empty array');
97
+ });
98
+
99
+ it('should return a valid image for known tag', async () => {
100
+ const res = await NekosiaAPI.fetchImages({
101
+ count: 1,
102
+ tags: ['catgirl'],
103
+ });
104
+
105
+ expectValidImageObject(res);
82
106
  });
83
107
  });
84
108
 
85
109
  describe('fetchById', () => {
86
- it('should fetch an image by ID if it exists', async () => {
87
- const res = await NekosiaAPI.fetchImages('catgirl', { count: 1 });
88
-
89
- if (res.success && res.id) {
90
- const id = res.id;
91
-
92
- const res2 = await NekosiaAPI.fetchById(id);
93
- expect(res2.success).toBe(true);
94
- expect(res2.status).toBe(200);
95
- expect(res2).toHaveProperty('id', id);
96
- expect(res2).toHaveProperty('image');
97
- } else {
98
- throw new Error('No images available to fetch by ID');
99
- }
110
+ it('should fetch image by ID', async () => {
111
+ const preview = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 1 });
112
+ if (!preview.success || !preview.id) throw new Error('No valid image to fetch by ID');
113
+
114
+ const fetched = await NekosiaAPI.fetchById(preview.id);
115
+ expect(fetched.success).toBe(true);
116
+ expect(fetched.status).toBe(200);
117
+ expect(fetched.id).toBe(preview.id);
118
+ expect(fetched).toHaveProperty('image');
100
119
  });
101
120
 
102
- it('should return an error response for invalid ID format', async () => {
121
+ it('should return error for malformed ID', async () => {
103
122
  const res = await NekosiaAPI.fetchById('12345');
104
123
  expect(res.success).toBe(false);
105
124
  expect(res.status).toBe(400);
106
- expect(res.message).toBe('The image with the provided identifier was not found.');
125
+ expect(res.message).toMatch(/The image with the provided identifier was not found/);
107
126
  });
108
- });
109
127
 
128
+ it('should return error for valid format but non-existent ID', async () => {
129
+ const res = await NekosiaAPI.fetchById('keyboardcat');
130
+ expect(res.success).toBe(false);
131
+ expect(400).toBe(res.status);
132
+ });
133
+ });
110
134
  });
@@ -14,6 +14,11 @@ describe('NekosiaAPI', () => {
14
14
  const result = NekosiaAPI.buildQueryParams(options);
15
15
  expect(result).toBe('count=3&additionalTags=tag1,tag2,tag3,tag4');
16
16
  });
17
+
18
+ it('should return empty string for empty options', () => {
19
+ const result = NekosiaAPI.buildQueryParams({});
20
+ expect(result).toBe('');
21
+ });
17
22
  });
18
23
 
19
24
  describe('makeHttpRequest', () => {
@@ -43,30 +48,59 @@ describe('NekosiaAPI', () => {
43
48
  });
44
49
  });
45
50
 
46
- describe('fetchImages', () => {
51
+ describe('fetchCategoryImages', () => {
47
52
  it('should build correct endpoint and make request for given category', async () => {
48
53
  const mockResponse = { data: { results: [] } };
49
54
  https.get.mockResolvedValue(mockResponse);
50
55
 
51
56
  const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/catgirl?count=2&additionalTags=cute';
52
- const res = await NekosiaAPI.fetchImages('catgirl', { count: 2, additionalTags: 'cute' });
57
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 2, additionalTags: 'cute' });
58
+
59
+ expect(res).toEqual(mockResponse);
60
+ expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
61
+ });
62
+
63
+ it('should build correct endpoint with no query params if options are empty', async () => {
64
+ const mockResponse = { data: { results: [] } };
65
+ https.get.mockResolvedValue(mockResponse);
66
+
67
+ const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/catgirl';
68
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl');
53
69
 
54
70
  expect(res).toEqual(mockResponse);
55
71
  expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
56
72
  });
73
+
74
+ it('should include count=1 if explicitly set', async () => {
75
+ const mockResponse = { data: { results: [] } };
76
+ https.get.mockResolvedValue(mockResponse);
77
+
78
+ const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/catgirl?count=1';
79
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 1 });
80
+
81
+ expect(res).toEqual(mockResponse);
82
+ expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
83
+ });
84
+
57
85
  });
58
86
 
59
- describe('fetchShadowImages', () => {
60
- it('should throw an error if additionalTags is empty', async () => {
61
- await expect(NekosiaAPI.fetchShadowImages({})).rejects.toThrow('`additionalTags` must be a non-empty array for the shadow category');
87
+ describe('fetchImages', () => {
88
+ it('should throw an error if tags is missing', async () => {
89
+ await expect(NekosiaAPI.fetchImages({})).rejects.toThrow('`tags` must be a non-empty array');
90
+ });
91
+
92
+ it('should throw an error if tags is not an array', async () => {
93
+ await expect(NekosiaAPI.fetchImages({ tags: 'not-an-array' }))
94
+ .rejects
95
+ .toThrow('`tags` must be a non-empty array');
62
96
  });
63
97
 
64
98
  it('should correctly call fetchImages with additionalTags', async () => {
65
99
  const mockResponse = { data: { results: [] } };
66
100
  https.get.mockResolvedValue(mockResponse);
67
101
 
68
- const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/shadow?count=1&additionalTags=dark,shadow';
69
- const res = await NekosiaAPI.fetchShadowImages({ count: 1, additionalTags: ['dark', 'shadow'] });
102
+ const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/nothing?count=1&additionalTags=dark,shadow';
103
+ const res = await NekosiaAPI.fetchImages({ count: 1, tags: ['dark', 'shadow'] });
70
104
 
71
105
  expect(res).toEqual(mockResponse);
72
106
  expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
@@ -74,8 +108,10 @@ describe('NekosiaAPI', () => {
74
108
  });
75
109
 
76
110
  describe('fetchById', () => {
77
- it('should throw an error if id is not provided', async () => {
78
- await expect(NekosiaAPI.fetchById()).rejects.toThrow('`id` parameter is required');
111
+ it('should throw an error if id is missing or empty', async () => {
112
+ for (const id of [undefined, '']) {
113
+ await expect(NekosiaAPI.fetchById(id)).rejects.toThrow('`id` parameter is required');
114
+ }
79
115
  });
80
116
 
81
117
  it('should correctly fetch image by ID', async () => {
package/types/index.d.ts CHANGED
@@ -4,6 +4,71 @@ declare module 'nekosia.js' {
4
4
  type HexColor = `#${string}`;
5
5
  type AllTagsList = typeof TAGS[number];
6
6
 
7
+ /**
8
+ * Configuration options for the `fetchCategoryImages` function.
9
+ */
10
+ interface FetchImagesCategoryOptions {
11
+ /**
12
+ * Session type:
13
+ * - `id` - Session identified by its `id` value (requires the `id` field to be set).
14
+ * - `ip` - Session identified by the user's IP address.
15
+ * @type String
16
+ * @default undefined
17
+ */
18
+ session?: 'id' | 'ip';
19
+
20
+ /**
21
+ * Identifier of the fetched image.
22
+ * @type String
23
+ * @example "66ae26a07886f165901e8a3f"
24
+ */
25
+ id?: string;
26
+
27
+ /**
28
+ * The number of images to fetch. WARNING! The higher the number, the more data the server will need to process, leading to longer response times.
29
+ *
30
+ * - Minimum - 1
31
+ * - Maximum - 24
32
+ * @type Number
33
+ * @default 1
34
+ */
35
+ count?: number;
36
+
37
+ /**
38
+ * Additional tags to include in the image search.
39
+ * This can be a single string representing one tag or an array of strings for multiple tags.
40
+ * @type Array
41
+ * @example ["cute", "sakura", "blue-eyes"]
42
+ * @default []
43
+ */
44
+ additionalTags?: AllTagsList | AllTagsList[];
45
+
46
+ /**
47
+ * Tags to exclude from the image search.
48
+ * This can be a single string representing one tag or an array of strings for multiple tags.
49
+ * @type Array
50
+ * @example ["beret", "hat", "short-hair"]
51
+ * @default []
52
+ */
53
+ blacklistedTags?: AllTagsList | AllTagsList[];
54
+
55
+ /**
56
+ * Defines the content rating of an image.
57
+ * The rating indicates the appropriateness of the content, specifying whether the image is suitable for all audiences or contains content that may be sensitive or inappropriate for certain viewers.
58
+ *
59
+ * Possible values:
60
+ * - `safe`: Suitable for all audiences, contains no explicit or questionable content.
61
+ * - `questionable`: May contain content sensitive or inappropriate for younger audiences, but not explicit.
62
+ * - `nsfw`: Contains explicit content, not safe for work (NSFW).
63
+ *
64
+ * The default value is ALWAYS `safe`.
65
+ * @type String
66
+ * @example safe
67
+ * @default safe
68
+ */
69
+ rating?: 'safe' | 'suggestive';
70
+ }
71
+
7
72
  /**
8
73
  * Configuration options for the `fetchImages` function.
9
74
  */
@@ -41,7 +106,7 @@ declare module 'nekosia.js' {
41
106
  * @example ["cute", "sakura", "cherry-blossom"]
42
107
  * @default []
43
108
  */
44
- additionalTags?: AllTagsList | AllTagsList[];
109
+ tags?: AllTagsList | AllTagsList[];
45
110
 
46
111
  /**
47
112
  * Tags to exclude from the image search.
@@ -50,7 +115,7 @@ declare module 'nekosia.js' {
50
115
  * @example ["beret", "hat", "short-hair"]
51
116
  * @default []
52
117
  */
53
- blacklistedTags?: AllTagsList | AllTagsList[];
118
+ blacklist?: AllTagsList | AllTagsList[];
54
119
 
55
120
  /**
56
121
  * Defines the content rating of an image.
@@ -306,6 +371,17 @@ declare module 'nekosia.js' {
306
371
  attribution: ImageAttribution;
307
372
  }
308
373
 
374
+ /**
375
+ * GET https://api.nekosia.cat/api/v1/tags
376
+ */
377
+ interface TagsResponse {
378
+ status: number,
379
+ success: boolean,
380
+ tags: string[],
381
+ anime: string[],
382
+ characters: string[]
383
+ }
384
+
309
385
  /**
310
386
  * Nekosia API class, containing methods for fetching images.
311
387
  * All methods are asynchronous and return a Promise resolving to an `ImageResponse`.
@@ -317,7 +393,7 @@ declare module 'nekosia.js' {
317
393
  * @param options - Configuration options for the request (optional).
318
394
  * @example
319
395
  * const { NekosiaAPI } = require('nekosia.js');
320
- * await NekosiaAPI.fetchImages('catgirl', {
396
+ * await NekosiaAPI.fetchCategoryImages('catgirl', {
321
397
  * count: 1,
322
398
  * additionalTags: ['cute', 'sakura', 'cherry-blossom'],
323
399
  * blacklistedTags: ['beret']
@@ -325,22 +401,28 @@ declare module 'nekosia.js' {
325
401
  * @type Object
326
402
  * @returns A Promise resolving to an `ImageResponse`.
327
403
  */
328
- static fetchImages(category: AllTagsList, options?: FetchImagesOptions): Promise<ImageResponse>;
404
+ static fetchCategoryImages(category: AllTagsList | 'random', options?: FetchImagesCategoryOptions): Promise<ImageResponse>;
329
405
 
330
406
  /**
331
407
  * Fetches images based solely on the tags provided by the user. The main category does not affect the image selection as it is not provided here.
332
408
  * @param options - Configuration options for the request (optional).
333
409
  * @example
334
410
  * const { NekosiaAPI } = require('nekosia.js');
335
- * await NekosiaAPI.fetchShadowImages({
411
+ * await NekosiaAPI.fetchImages({
336
412
  * count: 1,
337
- * additionalTags: ['catgirl', 'foxgirl'],
338
- * blacklistedTags: ['dog-girl']
413
+ * tags: ['catgirl', 'foxgirl'],
414
+ * blacklist: ['dog-girl']
339
415
  * });
340
416
  * @type Object
341
417
  * @returns A Promise resolving to an `ImageResponse`.
342
418
  */
343
- static fetchShadowImages(options?: FetchImagesOptions): Promise<ImageResponse>;
419
+ static fetchImages(options?: FetchImagesOptions): Promise<ImageResponse>;
420
+
421
+ /**
422
+ * Fetches the latest array with tags, anime titles, and characters
423
+ * @returns A Promise resolving to an `ImageResponse`.
424
+ */
425
+ static fetchTags(id: string): Promise<TagsResponse>;
344
426
 
345
427
  /**
346
428
  * Fetches an image by its identifier.
package/types/tags.ts CHANGED
@@ -1,2 +1,4 @@
1
- // https://api.nekosia.cat/api/v1/tags
2
- export const TAGS = ['absolute-territory','after-the-rain','afternoon-tea','ahoge','ajitani-hifumi','akazukin','aksktrpg','akura','alice-in-wonderland','alice-margatroid','alice-tendou','alllisso','alluring-face','alone','amamine','amamineko-cafe','amashiro-natsuki','ame-nochi-yuki','ameto-yuki','angel','angry-face','animal-ears','apex','apple','apple-caramel','apple-pie','apron-dress','aqua','aquarium','areola-slip','aria-tsukushi','arknights','arknights-battle-illustration-contest','armed-girls','armpits','ashen-hair','assault-rifle','autumn','autumn-leaves','axillary-costume','azki','azkiart','azur-lane','babydoll','back-alley','background','balloon','balloons','bandages','bandaid','bangtail','bare-feet','bare-soles','barefoot','basketball','basketball-uniform','bath-time','bath-towel','beach','beach-ball','beast-girl','beautiful','beautiful-reimu','bed','bed-hair','bedroom','belly','belly-glimpse','bellybutton','bench','beret','bewitching-thighs','big-breasts','bikini','bikini-top','birthday','black-and-white','black-bikini','black-dress','black-hair','black-hair-with-red-eyes','black-knee-high-socks','black-ribbon','black-sailor-uniform','black-shoes','black-skirt','black-stockings','black-thigh-high-socks','black-thigh-highs','black-tights','blazer','blonde','bloomers','blue-archive','blue-eues','blue-eyes','blue-hair','blue-ribbon','blue-shoes','blue-skirt','blue-sky','bluearchive','blushing','bocchi-chan','bondage','botamochi','bottomless','boy-cut','bra','braid','breast-hold','breasts','bride','brown-hair','bubble-tea','bunny-ears','bunny-girl','butterfly','by-the-window','cable-knitting','cake','cameltoe','camisole','camisole-dress','canal','candy','candy-apple','car','cardigan','cardigan-uniform','casual-wear','cat','cat-and-girl','cat-ears','cat-ears-hat','cat-ears-headphones','cat-ears-hoodie','cat-ears-maid','cat-mouth','cat-pose','cat-tail','catgirl','celia-claire','celica','cheeks','cheerleader','chericot-rozel','cherry','cherry-blossom','cherry-petals','chest-outline','chibi','chihuahua','chin-resting','chinese-lolita','chinese-style','chino','chino-kafuu','chitosezaka-suzu','chiyu','chocolate','chocolate-mint','choker','christmas','chuuni','classroom','cloak','closed-eyes','clouds','coffee','collar','comic-treasure','confused','cookie','corset','cosmos','costume-change','crossed-legs','crying','cuite','cutaway-shoulder','cute','cute-cat','cuteness-is-justice','dakimakura-cover','dango','detexted','dog-and-girl','dog-ears','dog-girl','doggirl','donut','doujin','drawers','drawmei','dress','dress-shirt','dvdart','elementary-school-student','elevator','enticing-brassiere','epic-seven','epic-seven-fan-art-illustration-contest','exposed-back','eye-mask','eyepatch','fairy-eye','fairy-tale','fallenshadow','fantasy','fantasy-girl','feather-ears','feet','felicia-lulufleur','festival-of-white','firefly','fishnets','flare-skirt','flared-bikini','flat-chest','flower','flowers-and-girls','forest','fox-ears','fox-mask','fox-shrine-maiden','foxgirl','frills','fubuki-shiragami','fubuki-shiragami-fanart','fuwamoco','fuwawa','fuwawa-abyssgard','fuyoyo','fuzzy','fwmcpix','game','game-pad','gamer','gamer-girl','ganbare-ganbare','garter','garter-ring','garter-stockings','garterbelt','gawr-gura','genshin-impact','gensokyo-in-spring','getting-dressed','gijinka','girl','girls','girls-frontline','glasses','gloves','gothic-lolita','greek-foot','green-eyes','green-hair','green-ribbon','guildcq','guitar','gun','gym-uniform','hairdressing','hairpin','hairstyle-change','hakui-koyori','half-pants','half-pigtails','half-up-hairstyle','halloween','halo','hamico','hammann','hammock','han-costume','hanami','hands-forming-a-heart','harness','haruhara-shun','hayate-hisakawa','hazakura-chikori','headband','headboard-angle','headdress','headphone','headphones','headset','heart','heart-eyes','heartbreak','hearts','hedgehog','heels','herta','heterochromia','hibiki','hibiscus','high-angle','high-school-girl','high-waisted-skirt','hiiro','hikaru','himura-moritaka','hirakata-kana','hizuki-miu','hizuki-rurufu','holding-hands-with-fingers-interlocked','holding-ice-in-mouth','holoadvent','hololive','hololive-en','hololive-english','hololiveen','holox','hong','honkaistarrail','hood','hoodie','hoshi','hoshino','hoshitsuki-neiro','hoshizora-no-memoria','hot-pants','hot-spring','houzuki-michiru','hu-tao','hutao','hyerang','ice-cream','ichigori-ena','idol','illumination','in-the-train','incredibly-cute','indie-virtual-youtuber','indie-vtuber','infamous-left-foot-defense','inflatable-pool','inner-color','inugami-korone','itigori-ena','itou-yuna','izayoi-nonomi','jagged-sclera','japanese-clothes','japanese-maid','japanese-style','japanese-style-maid','japanese-umbrella','jersey','jk','jumper-skirt','jumpy','just-woke-up','kabashima-hana','kamiko-kana','kamioka-chiroru','kamisato-ayaka','kamiyama-shiki','kancolle','kantai-collection','kanzaki-ririka','karin','karyl','katana','katcolle','kazari-rua','kazueru','kazusa-kyoyama','kigurumi','kimono','kimono-dress','kindergarten-pupil','kingdom','kirakira-monstars','kiryuu-kikyou','kitchen','klee','knee-high-socks','kneeling','knockout-knockers','koahri','kohaku-nene','koharu','koiko-irori','koito-amuno','kokoro-amamiya','korie-riko','korone','koyori-hakui','koyori-sketch','kuda-izuna','kuro-namako','kuromi-serika','kuroneko','kyaru','kyouyama-kazusa','lancat','lanmewko','lap-pillow','laplus-darkness','large-breasts','lattice-tower','leaning-forward','left-foot-shield','legato','leggings','legs','lemon','leo','lily-of-the-valley','lime','lingerie','little-devil','lolicon-bait','lolipop-complete','lolita','lolita-fashion','lollipop','long-black-hair','long-blond-hair','long-gloves','long-hair','long-pink-hair','long-red-hair','long-white-gloves','long-white-hair','loungewear','lying-down','lying-on-one-side','macaron','machikaze-fuuna','mad','magical-girl','maid','maid-bikini','maid-day','maid-uniform','makeup','mari-iochi','marie','marine-day','marine-look','maruro','mascot','matsumiya-kiseri','mayoi-shigure','mea-kagura','medium-hair','mf-bunko-j','middle-school-student','midori-matsukaze','midriff','mihoshi-mei','miike-chan','mika','mika-misono','mikeneko','mikeneko-fanart','miko','miku-maekawa','minami-kotone','mini-hakama','miniskirt','miniskirt-miko','mint','mirror','miu-channel','miu-hizuki','mizukiart','mococo','mococo-abyssgard','mole-under-the-eye','moon','murasaki-shion','muryo','myoya','nachoneko','nagi-hisakawa','nahida','nail-polish','naked-navel','nanashi-mumei','nap','natori-sana','necktie','necomi','needs-to-be-appreciated-more','negligee','nekoha-shizuku','nekomata-okayu','new-year','newsboy-cap','night','night-sky','nijisanji','nikke','nine-tailed','nintendo-switch','nintendoswitch','no-game-no-life','noire','nolan','nonaka-yuu','nurse','off-shoulder','off-shoulder-dress','okomeillust','okusora-ayane','old-fashioned-school-swimsuit','older-girl','omochi-box','omochi-monaka','one-eye-covered','one-piece','one-piece-swimsuit','ookami-mio','open-shirt','orange-ribbon','otokonoko','overalls','overly-long-sleeves','pajamas','panchira','panties','pantsu','pantyhose','parasol','park','patting','paw-pads','peace-sign','pekora-usada-fanart','personification-of-drinks','photo-editing','photoshop','pigtails','pillow','pinching-garments','pink','pink-eyes','pink-hair','pink-ribbon','pink-shoes','pink-skirt','pink-sweater','pink-twintails','plaid-pattern','plaid-skirt','plastic-bottle','platform','playing-cards','plump-thighs','plushie','pocky','pointy-ears','ponytail','pool','pool-at-night','porch','present','princess-connect','prone-position','psycho','pu-ht','purple-ears','purple-eyes','purple-hair','purple-ribbon','purple-skirt','queen-of-hearts','rabbit-girl','ragna-doll','railroad-crossing','railway','rain','rainy-season','ramune','randoseru','rawr','re-oekaki','red-and-blue','red-eyes','red-hair','red-pigtails','red-ribbon','red-ribbons','red-skirt','reflected-glare','reimu-hakurei','reisa-uzawa','remilia-scarlet','rena-yorumi','reverse-sitting','rhodes','ribbed-fabric','ribbon','ribbonos','ribbons','ringo-chan','roar','rocket-launcher','roxanne','rribbon','rubbib','rucaco','rukako','ruki-roki','rumi','runa-shinomiya','rurudo','rurudo-lion','rushia-uruha','sad','saekiya-sabou','saiba-midori','saiba-momoi','sailor','sailor-dress','sailor-one-piece','sailor-swimsuit','sailor-uniform','sakamata-chloe','sakura','sakura-miko','sakuraba-moeka','sakurai-hana','sand','santa','santa-cosplay','sassy-loli','scared','scarf','scenery','school-girl','school-swimsuit','school-uniform','school-uniform-cardigan','school-uniform-hoodie','schoolgirl','sea','see-through','selfie','setokane','shano-hiyori','shanoa-asmr','shigure','shiori','shirai-tanuki','shirakami-fubuki','shirakawa-yumea','shirasu-azusa','shiroko','shiroko-terror','shiromichan','shooting-star','short-boots','short-haircut','short-pants','short-white-hair','shouu-kun','showgirl','side-string-bikini','side-tie-panty','silver-hair','silver-long-hair','singer','sisters','sitting','sitting-on-feet','sitting-with-hands-around-knees','skirt','skirt-lift','sleep','sleep-wear','sleeping-face','sleepy','sleepy-face','sleeveless','sleeveless-dress','sleeveless-parka','small-fangs','smile','snack-nili','sneakers','snow','snow-leopard','snowman','socks','sofa','sole','somna','sorasaki-hina','spiral-eyes','splashes','spring','squirrel','stairs','standing-picture','starlight-stage','starry-sky','starry-sky-dress','steamed-bun-with-meat-filling','stockings','straight-fringe','strap-shoes','straw-hat','strawberries','strawberry','string-ribbon','striped-bikini','striped-thigh-high-socks','sukumizu','summer','summer-dress','summer-kimono','summer-pockets','sunaookami-shiroko','sunflowers','sunglasses','sunohara-kokona','sunset','supine','suspender-skirt','suzuho-hotaru','sweat','sweater','swim-ring','swimsuit','swimsuit-parka','swimwear','swing','sword','t-shirt','tail','tail-from-under-skirt','tail-holding','tail-with-ribbon','taiwan','takanashi','takanashi-hoshino','tattoo','tea','tea-time','tears','teary-eyed','teary-eyes','telephone','tenshinoikenie','the-person-behind','the-soles-of-feet-in-socks','theriantrope','thick-eyebrows','thigh-high-socks','thigh-highs','thighhighs','thighs','tie-side-bikini','tiger','tight','tired','tobu-railway','toes','tokoshibyra','tongue-out','touhou','touhou-project','towel','transparency','tropical-fish','tsugiri-noa','tsurara','twins','twintails','uise-iu','umbrella','underwater','uniform','upwards-gaze','uruha-rushia-fanart','usada-pekora','usagigo','valentine','vampire','vdonburi','vrchat','vtuber','w-sitting','wa-lolita','waitress','wallpaper','water','water-surface','watermelon','weapon','wearing-only-a-shirt','wedding-dress','wet','wet-see-through','white-bikini','white-cat','white-day','white-dress','white-hair','white-hairpin','white-high-socks','white-knee-high-socks','white-ribbon','white-sailor-uniform','white-school-swimsuit','white-skirt','white-socks','white-stockings','white-swimsuit','white-thigh-high-socks','white-tiger','white-tights','wide-brim-hat','wings','wink','winter','witch','wolf-ears','wolf-girl','yandere','yawn','yellow-bikini','yellow-eyes','yellow-hair','yellow-ribbon','yoko','young-girl','yuika-shiina','yukata','yukihana-lamy','yukikaze','yukinoshita-peo','yutori-natsu'] as const;
1
+ export const TAGS =
2
+ ['absolute-territory','after-the-rain','afternoon-tea','akazukin','alice-in-wonderland','alice-mana','alllisso','alluring-face','alona','alone','amamine','amamineko-cafe','amashiro-natsuki','angel','angry-face','animal-ears','apex','apple','apple-caramel','apple-pie','apron-dress','aqua','aquarium','aria-tsukushi','arknights','arknights-battle-illustration-contest','armed-girls','armpits','ashen-hair','assault-rifle','autumn','autumn-leaves','axillary-costume','azki','azkiart','azur-lane','babydoll','back-alley','background','balloon','balloons','bandages','bandaid','bare-feet','barefoot','basketball','basketball-uniform','beach','beast-girl','beautiful','beautiful-reimu','bed','bed-hair','bedroom','belly-glimpse','bellybutton','bench','beret','bewitching-thighs','birthday','black-and-white','black-dress','black-hair','black-hair-with-red-eyes','black-knee-high-socks','black-ribbon','black-sailor-uniform','black-shoes','black-skirt','black-stockings','black-thigh-high-socks','black-thigh-highs','black-tights','blazer','blonde','bloomers','blue-archive','blue-eyes','blue-hair','blue-ribbon','blue-shoes','blue-skirt','blue-sky','blushing','bocchi-chan','botamochi','bottomless','boy-cut','braid','breasts','brown-hair','bubble-tea','bunny-ears','bunny-girl','butterfly','by-the-window','cable-knitting','cafe','cake','camisole','camisole-dress','canal','candy-apple','car','cardigan','cardigan-uniform','casual-wear','cat','cat-and-girl','cat-ears','cat-ears-hat','cat-ears-headband','cat-ears-headphones','cat-ears-hoodie','cat-ears-maid','cat-mouth','cat-paws','cat-pose','cat-tail','catboy','catgirl','cats','cheeks','cherry','cherry-blossom','cherry-petals','chess','chibi','chihuahua','chin-resting','chinese-lolita','chinese-style','chino','chino-kafuu','chitosezaka-suzu','chocolate','chocolate-mint','choker','christmas','chuuni','classroom','cloak','closed-eyes','clouds','cocoa-hoto','coffee','coffee-shop','collar','comic-treasure','confused','cookie','corset','cosmos','crossed-legs','crying','cutaway-shoulder','cute','cute-cat','cuteness-is-justice','cyte','detexted','dog','dog-and-girl','dog-ears','dog-girl','doll','donut','doujin','drawers','drawmei','dress','dress-shirt','dvdart','elaina','elementary-school-student','elevator','epic-seven','eye-mask','eyepatch','fairy-eye','fairy-tale','fantasy','fantasy-girl','feather-ears','feet','felicia-lulufleur','festival-of-white','firefly','fireworks','fishnets','flare-skirt','flat-chest','floral-playing-cards','flower','flowers','flowers-and-girls','food','footwear','forest','fox','fox-ears','fox-mask','fox-shrine-maiden','foxgirl','frills','fubuki-shiragami','fubuki-shiragami-fanart','fuwamoco','fuyoyo','fuzzy','game','game-pad','gamer','gamer-girl','garter','garterbelt','gawr-gura','genshin-impact','gensokyo-in-spring','getting-dressed','ghost','gijinka','girl','girls','glasses','gloves','gothic-lolita','green-eyes','green-hair','green-ribbon','guildcq','guitar','gun','gym-uniform','hair-tie-in-mouth','hairdressing','hairpin','hairstyle-change','hajime-todoroki','hakui-koyori','half-pants','half-pigtails','half-up-hairstyle','halloween','halo','hamico','han-costume','hanami','hands-forming-a-heart','harness','hayate-hisakawa','hazakura-chikori','headband','headboard-angle','headdress','headphone','headphones','headset','heart','heart-eyes','heartbreak','hearts','hedgehog','heels','herta','heterochromia','hibiki','hibiscus','high-angle','high-school-girl','high-waisted-skirt','hiiro','hikaru','hizamaru','holding-hands-with-fingers-interlocked','holding-ice-in-mouth','hololive','hololive-en','hololive-english','hololiveen','holox','hong','honkaistarrail','hood','hoodie','hoshi','hoshino','hoshizora-no-memoria','hot-spring','hu-tao','hutao','hydrangea','ice-cream','ichigo','ichigori-ena','idol','illumination','in-the-train','incredibly-cute','indie-vtuber','indoor-shoes','inner-color','inugami-korone','itigori-ena','jagged-sclera','japanese-clothes','japanese-maid','japanese-style','japanese-style-maid','japanese-umbrella','jk','jumper-skirt','jumpy','just-woke-up','kabashima-hana','kamiko-kana','kamioka-chiroru','kamisato-ayaka','kamiyama-shiki','kanade-otonose','kancolle','kanon-mashiro','kantai-collection','karin','katana','katcolle','kazari-rua','kazueru','kazusa-kyoyama','kimono','kimono-dress','kindergarten-pupil','kingdom','kirakira-monstars','kiryuu-kikyou','kitchen','klee','knee-high-socks','knitted-vest','koharu','koiko-irori','koito-amuno','korie-riko','koyori-hakui','koyori-sketch','kuda-izuna','kuro-namako','kuroneko','kyaru','kyouyama-kazusa','lancat','lanmewko','laplus-darkness','large-breasts','lattice-tower','left-foot-shield','legato','leggings','legs','lemon','leo','lily-of-the-valley','lime','lingerie','little-devil','lolipop-complete','lolita','lolita-fashion','lollipop','long-black-hair','long-blond-hair','long-hair','long-pink-hair','long-red-hair','long-white-gloves','long-white-hair','loungewear','lying-down','lying-on-one-side','macaron','mad','magical-girl','maid','maid-uniform','makeup','mari-iochi','marie','marine-look','maruro','mascot','mayoi-shigure','medium-hair','middle-school-student','midori-matsukaze','mihoshi-mei','miike-chan','mika','mika-misono','mikeneko','miko','minato-aqua','mini-hakama','miniskirt','miniskirt-miko','mint','mirror','mizukiart','mococo','mococo-abyssgard','moon','murasaki-shion','muryo','myoya','nachoneko','nagi-hisakawa','nahida','nail-polish','nana-kagura','nanashi-mumei','nap','natori-sana','necktie','negligee','nekoha-shizuku','nekomata-okayu','new-year','newsboy-cap','night','night-sky','nijisanji','nikke','nine-tailed','nintendo-switch','nintendoswitch','noire','nolan','nonaka-yuu','nurse','off-shoulder','off-shoulder-dress','okomeillust','omochi-box','omochi-monaka','one-eye-covered','one-piece','ookami-mio','orange-hair','otokonoko','overalls','overly-long-sleeves','painting','pajamas','panda-ears','parasol','park','patting','peace-sign','pekora-usada-fanart','personification-of-drinks','photo-editing','pigtails','pillow','pinching-garments','pink','pink-eyes','pink-hair','pink-ribbon','pink-shoes','pink-skirt','pink-twintails','plaid-pattern','plaid-skirt','platform','playing-cards','plump-thighs','plushie','pocky','pointy-ears','ponytail','porch','present','princess-connect','project-sekai','prone-position','pu-ht','purple-ears','purple-eyes','purple-hair','purple-ribbon','purple-skirt','queen-of-hearts','railroad-crossing','railway','rain','rainy-season','ramune','randoseru','rawr','red-and-blue','red-eyes','red-hair','red-pigtails','red-ribbon','red-skirt','reflected-glare','regloss','reimu-hakurei','reisa-uzawa','remilia-scarlet','reverse-sitting','rhodes','ribbed-fabric','ribbon','ringo-chan','roar','rocket-launcher','rucaco','rukako','ruki-roki','rumi','runa-shinomiya','rurudo','rurudo-lion','rushia-uruha','sad','saiba-midori','saiba-momoi','sailor-dress','sailor-one-piece','sailor-uniform','sakamata-chloe','sakura','sakura-miko','sakuraba-moeka','sakurada-hane','sakuradahane','sakurai-hana','santa','sassy-loli','scared','scarf','scenery','school-sweater','school-uniform','school-uniform-cardigan','school-uniform-hoodie','schoolgirl','scornful-eyes','sea','see-through','selfie','setokane','shano-hiyori','shanoa-asmr','shiba-inu','shigure','shiori','shirakawa-yumea','shirasu-azusa','shiroko','shiroko-terror','shiromichan','shooting-star','short-boots','short-haircut','short-pants','short-white-hair','shouu-kun','showgirl','shy','silver-hair','silver-long-hair','singer','sisters','sitting','skirt','skirt-lift','skirtm','sleep','sleeping-face','sleepwear','sleepy','sleepy-face','sleeveless','sleeveless-dress','sleeveless-parka','small-fangs','smartphone','smile','smug-face','sneakers','snow','snowman','snowy-landscape','socks','sofa','sole','somna','sorasaki-hina','sousei','spiral-eyes','splashes','spring','squirrel','standing-picture','starlight-stage','starry-sky','starry-sky-dress','steamed-bun-with-meat-filling','stockings','straight-fringe','strap-shoes','straw-hat','strawberries','strawberry','streaked-hair','striped-thigh-high-socks','summer','summer-dress','summer-kimono','summer-pockets','sunaookami-shiroko','sunflowers','sunohara-kokona','sunset','supine','suspender-skirt','suzuho-hotaru','sweat','sweater','sweets','swimwear','swing','sword','tail','tail-from-under-skirt','tail-holding','tail-with-ribbon','taiwan','takanashi','takanashi-hoshino','tea','tea-time','tears','teary-eyed','teddy-bear','telephone','the-person-behind','the-soles-of-feet-in-socks','thick-eyebrows','thigh-high-socks','thigh-highs','thighhighs','thighs','tiered-dress','tiger','tights','tired','tobu-railway','toes','tongue-out','touhou','touhou-project','tropical-fish','twins','twintails','uise-iu','umbrella','underwater','uniform','upwards-gaze','usada-pekora','usagigo','valentine','vampire','vdonburi','vrchat','vtuber','w-sitting','wa-lolita','waitress','wallpaper','wallpapers','water','water-surface','watermelon','weapon','wedding-dress','wet','what-is-this-cuteness','white-cat','white-day','white-dress','white-hair','white-hait','white-high-socks','white-knee-high-socks','white-ribbon','white-sailor-uniform','white-skirt','white-socks','white-stockings','white-thigh-high-socks','white-tiger','white-tights','white-twintails','wings','wink','winter','witch','wolf','wolf-ears','wolf-girl','yandere','yaoyao','yawn','yellow-eyes','yellow-hair','yellow-ribbon','yoko','young-boy','young-girl','yuika-shiina','yuimisu','yukata','yukihana-lamy','yukinoshita-peo','yutori-natsu','yuuki-sakuna'] as const;
3
+
4
+ // GET https://api.nekosia.cat/api/v1/tags
@@ -1,12 +0,0 @@
1
- const { NekosiaAPI } = require('../index.js');
2
-
3
- (async () => {
4
- const response = await NekosiaAPI.fetchImages('foxgirl', {
5
- session: 'ip',
6
- count: 1,
7
- additionalTags: ['cute', 'sakura', 'cherry-blossom'],
8
- blacklistedTags: ['beret']
9
- });
10
-
11
- console.log(response);
12
- })();
@@ -1,6 +0,0 @@
1
- const { NekosiaAPI } = require('../index.js');
2
-
3
- (async () => {
4
- const response = await NekosiaAPI.fetchShadowImages({ session: 'ip', count: 1, additionalTags: ['cute', 'blue-hair'] });
5
- console.log(response);
6
- })();