nekosia.js 0.2.0 → 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
@@ -1,6 +1,6 @@
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
5
  const response = await NekosiaAPI.fetchCategoryImages(category, options);
6
6
  console.log(`${category.toUpperCase()}:`, response);
@@ -10,7 +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 });
15
- await fetchImages('catgirl', { tags: 'animal-ears' });
13
+ await fetch('catgirl');
14
+ await fetch('foxgirl', { session: 'id', id: 'user123', count: 2 });
15
+ await fetch('catgirl', { tags: 'animal-ears' });
16
16
  })();
@@ -4,8 +4,8 @@ const { NekosiaAPI } = require('../index.js');
4
4
  const response = await NekosiaAPI.fetchCategoryImages('foxgirl', {
5
5
  session: 'ip',
6
6
  count: 1,
7
- additionalTags: ['cute', 'sakura', 'cherry-blossom'],
8
- blacklistedTags: ['beret'],
7
+ additionalTags: ['cute', 'sakura', 'blue-hair', 'blue-eyes'],
8
+ blacklistedTags: ['beret', 'hat'],
9
9
  });
10
10
 
11
11
  console.log(response);
@@ -1,6 +1,12 @@
1
1
  const { NekosiaAPI } = require('../index.js');
2
2
 
3
3
  (async () => {
4
- const response = await NekosiaAPI.fetchImages({ session: 'ip', count: 1, tags: ['cute', 'blue-hair'], blacklist: ['yellow-hair'] });
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
+
5
11
  console.log(response);
6
12
  })();
package/index.js CHANGED
@@ -27,18 +27,19 @@ class NekosiaAPI {
27
27
 
28
28
  async fetchCategoryImages(category, options = {}) {
29
29
  if (!category) {
30
- throw new Error('The image category is required. For example, use fetchCategoryImages(\'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
- return this.makeHttpRequest(`${API_URL}/images/${category}?${this.buildQueryParams(options)}`);
41
+ const query = this.buildQueryParams(options);
42
+ return this.makeHttpRequest(`${API_URL}/images/${category}${query ? `?${query}` : ''}`);
42
43
  }
43
44
 
44
45
  async fetchImages(options = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nekosia.js",
3
- "version": "0.2.0",
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",
@@ -57,8 +57,8 @@
57
57
  "up": "ncu -u && npm install && npm update && npm audit fix"
58
58
  },
59
59
  "devDependencies": {
60
- "@eslint/js": "^9.17.0",
61
- "globals": "^15.13.0",
60
+ "@eslint/js": "^9.25.0",
61
+ "globals": "^16.0.0",
62
62
  "jest": "^29.7.0"
63
63
  }
64
64
  }
package/services/https.js CHANGED
@@ -1,8 +1,8 @@
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',
@@ -13,7 +13,7 @@ const headers = {
13
13
  const timeout = 25000;
14
14
 
15
15
  const get = async url => {
16
- if (!url || typeof url !== 'string') throw new Error('Missing URL, expected a string.');
16
+ if (!url || typeof url !== 'string') throw new Error('Missing URL, expected a string');
17
17
 
18
18
  return new Promise((resolve, reject) => {
19
19
  const req = https.get(url, { headers, timeout }, res => {
@@ -41,7 +41,7 @@ const get = async url => {
41
41
 
42
42
  req.on('timeout', () => {
43
43
  req.destroy();
44
- reject(new Error(`Request timed out after ${timeout} ms.`));
44
+ reject(new Error(`Request timed out after ${timeout} ms`));
45
45
  });
46
46
  });
47
47
  };
package/test/api.test.js CHANGED
@@ -1,109 +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
+ };
10
+
11
+ describe('nekosia.js tests', () => {
4
12
 
5
13
  describe('fetchCategoryImages', () => {
6
- it('should fetch images for the given category', async () => {
14
+
15
+ it('should fetch one image when count is 1', async () => {
7
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);
12
27
 
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');
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 });
36
+
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.fetchCategoryImages('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.fetchCategoryImages('catgirl', { count: 3 });
54
-
55
- expect(res).toBeInstanceOf(Object);
56
- expect(res.success).toBe(true);
57
- expect(res.status).toBe(200);
58
- expect(res).toHaveProperty('images');
59
- expect(res.images).toBeInstanceOf(Array);
60
- 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);
61
55
  });
62
- });
63
56
 
64
- describe('fetchImages', () => {
65
- it('should handle no images found for nothing category with additional tags', async () => {
66
- const res = await NekosiaAPI.fetchImages({ count: 1, tags: ['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
+ });
67
62
 
63
+ it('should return error for count = 0', async () => {
64
+ const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 0 });
68
65
  expect(res.success).toBe(false);
69
66
  expect(res.status).toBe(400);
70
67
  });
71
68
 
72
- it('should throw an error if additionalTagsArray is empty', async () => {
73
- await expect(NekosiaAPI.fetchImages([])).rejects.toThrow('`tags` must be a non-empty array');
69
+ it('should handle random category', async () => {
70
+ const res = await NekosiaAPI.fetchCategoryImages('random');
71
+
72
+ expectValidImageObject(res);
74
73
  });
75
74
 
76
- it('should return an error response for invalid count parameter', async () => {
77
- const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 'invalid' });
75
+ it('should return error for invalid category', async () => {
76
+ const res = await NekosiaAPI.fetchCategoryImages('invalid-category');
78
77
  expect(res.success).toBe(false);
79
78
  expect(res.status).toBe(400);
80
- expect(res.message).toBe('Invalid count parameter. Expected a number between 1 and 48 (>48).');
81
79
  });
82
80
  });
83
81
 
84
- describe('fetchById', () => {
85
- it('should fetch an image by ID if it exists', async () => {
86
- const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 1 });
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
+ });
87
88
 
88
- if (res.success && res.id) {
89
- const id = res.id;
89
+ expect(res.success).toBe(false);
90
+ expect(res.status).toBe(400);
91
+ });
90
92
 
91
- const res2 = await NekosiaAPI.fetchById(id);
92
- expect(res2.success).toBe(true);
93
- expect(res2.status).toBe(200);
94
- expect(res2).toHaveProperty('id', id);
95
- expect(res2).toHaveProperty('image');
96
- } else {
97
- throw new Error('No images available to fetch by ID');
98
- }
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');
99
97
  });
100
98
 
101
- it('should return an error response for invalid ID format', async () => {
102
- const res = await NekosiaAPI.fetchCategoryImages('12345');
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);
106
+ });
107
+ });
108
+
109
+ describe('fetchById', () => {
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');
119
+ });
120
+
121
+ it('should return error for malformed ID', async () => {
122
+ const res = await NekosiaAPI.fetchById('12345');
103
123
  expect(res.success).toBe(false);
104
124
  expect(res.status).toBe(400);
105
- expect(res.message).toBe('No images matching the specified criteria were found. See https://nekosia.cat/documentation?page=api-endpoints#tags-and-categories');
125
+ expect(res.message).toMatch(/The image with the provided identifier was not found/);
106
126
  });
107
- });
108
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
+ });
109
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', () => {
@@ -54,13 +59,42 @@ describe('NekosiaAPI', () => {
54
59
  expect(res).toEqual(mockResponse);
55
60
  expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
56
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');
69
+
70
+ expect(res).toEqual(mockResponse);
71
+ expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
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
87
  describe('fetchImages', () => {
60
- it('should throw an error if additionalTags is empty', async () => {
88
+ it('should throw an error if tags is missing', async () => {
61
89
  await expect(NekosiaAPI.fetchImages({})).rejects.toThrow('`tags` must be a non-empty array');
62
90
  });
63
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');
96
+ });
97
+
64
98
  it('should correctly call fetchImages with additionalTags', async () => {
65
99
  const mockResponse = { data: { results: [] } };
66
100
  https.get.mockResolvedValue(mockResponse);
@@ -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
@@ -28,7 +28,7 @@ declare module 'nekosia.js' {
28
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
29
  *
30
30
  * - Minimum - 1
31
- * - Maximum - 48
31
+ * - Maximum - 24
32
32
  * @type Number
33
33
  * @default 1
34
34
  */
@@ -38,7 +38,7 @@ declare module 'nekosia.js' {
38
38
  * Additional tags to include in the image search.
39
39
  * This can be a single string representing one tag or an array of strings for multiple tags.
40
40
  * @type Array
41
- * @example ["cute", "sakura", "cherry-blossom"]
41
+ * @example ["cute", "sakura", "blue-eyes"]
42
42
  * @default []
43
43
  */
44
44
  additionalTags?: AllTagsList | AllTagsList[];
@@ -66,7 +66,7 @@ declare module 'nekosia.js' {
66
66
  * @example safe
67
67
  * @default safe
68
68
  */
69
- rating?: 'safe' | 'questionable' | 'nsfw';
69
+ rating?: 'safe' | 'suggestive';
70
70
  }
71
71
 
72
72
  /**
@@ -401,7 +401,7 @@ declare module 'nekosia.js' {
401
401
  * @type Object
402
402
  * @returns A Promise resolving to an `ImageResponse`.
403
403
  */
404
- static fetchCategoryImages(category: AllTagsList, options?: FetchImagesCategoryOptions): Promise<ImageResponse>;
404
+ static fetchCategoryImages(category: AllTagsList | 'random', options?: FetchImagesCategoryOptions): Promise<ImageResponse>;
405
405
 
406
406
  /**
407
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.
package/types/tags.ts CHANGED
@@ -1,3 +1,4 @@
1
- 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','alona','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','bear-ears','beargirl','beast-girl','beautiful','beautiful-reimu','bed','bed-hair','bedroom','belly','belly-glimpse','bellybutton','bench','beret','bewitching-thighs','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','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-paws','cat-pose','cat-tail','catboy','catgirl','cats','celia-claire','celica','cheeks','cheerleader','chericot-rozel','cherry','cherry-blossom','cherry-petals','chess','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','cocoa-hoto','coffee','collar','comic-treasure','confused','cookie','corset','cosmos','costume-change','cross-halter-bikini','crossed-legs','crying','cuite','cutaway-shoulder','cute','cute-cat','cuteness-is-justice','dakimakura-cover','dango','detexted','dog-and-girl','dog-ears','dog-girl','doll','donut','doujin','drawers','drawmei','dress','dress-shirt','dvdart','elementary-school-student','elevator','enticing-brassiere','epic-seven','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','floral-playing-cards','flower','flowers-and-girls','forest','fox','fox-ears','fox-mask','fox-shrine-maiden','foxgirl','frills','fubuki-shiragami','fubuki-shiragami-fanart','fuwamoco','fuwawa-abyssgard','fuyoyo','fuzzy','fwmcpix','game','game-pad','gamer','gamer-girl','ganbare-ganbare','garter','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','hajime-todoroki','hakui-koyori','half-pants','half-pigtails','half-up-hairstyle','halloween','halo','hamico','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','hizuki-miu','hizuki-rurufu','holding-hands-with-fingers-interlocked','holding-ice-in-mouth','hololive','hololive-en','hololive-english','hololiveen','holox','hong','honkaistarrail','hood','hoodie','hoshi','hoshino','hoshitsuki-neiro','hoshizora-no-memoria','hot-spring','houzuki-michiru','hu-tao','hutao','hydrangea','ice-cream','ichigo','ichigori-ena','idol','illumination','in-the-train','incredibly-cute','indie-virtual-youtuber','indie-vtuber','indoor-shoes','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','kanade-otonose','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','kohaku-nene','koharu','koiko-irori','koito-amuno','kokoro-amamiya','korie-riko','koyori-hakui','koyori-sketch','kuda-izuna','kuro-namako','kuromi-serika','kuroneko','kyaru','kyouyama-kazusa','lancat','lanmewko','lap-pillow','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-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-uniform','makeup','mari-iochi','marie','marine','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','miko','miku-maekawa','minami-kotone','minato-aqua','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','nanashi-mumei','nap','natori-sana','necktie','necomi','needs-to-be-appreciated-more','negligee','nekoha-shizuku','nekomata-okayu','nene-kusanagi','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-hair','orange-ribbon','otokonoko','overalls','overly-long-sleeves','pajamas','panchira','panda-ears','panties','pants','parasol','park','patting','paw-pads','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-sweater','pink-twintails','plaid-pattern','plaid-skirt','plastic-bottle','platform','playing-cards','plump-thighs','plushie','pocky','pointy-ears','polar-bear','ponytail','pool','pool-at-night','porch','present','princess-connect','project-sekai','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','regloss','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','sakurada-hane','sakuradahane','sakurai-hana','sand','santa','santa-cosplay','sassy-loli','scared','scarf','scenery','school-swimsuit','school-uniform','school-uniform-cardigan','school-uniform-hoodie','schoolgirl','scornful-eyes','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','shy','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','skirtm','sleep','sleep-wear','sleeping-face','sleepy','sleepy-face','sleeveless','sleeveless-dress','sleeveless-parka','small-breasts','small-fangs','smile','snack-nili','sneakers','snow','snow-leopard','snowman','snowy-landscape','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','streaked-hair','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','sweets','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','test','the-person-behind','the-soles-of-feet-in-socks','theriantrope','thick-eyebrows','thigh-high-socks','thigh-highs','thighhighs','thighs','tie-side-bikini','tiered-dress','tiger','tights','tired','tobu-railway','toes','tokoshibyra','tongue-out','touhou','touhou-project','towel','transparency','trick-or-treat','tropical-fish','tsugiri-noa','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-hait','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','white-twintails','wings','wink','winter','witch','wolf-ears','wolf-girl','yandere','yaoyao','yawn','yellow-eyes','yellow-hair','yellow-ribbon','yoko','young-boy','young-girl','yuika-shiina','yukata','yukihana-lamy','yukinoshita-peo','yutori-natsu','yuuki-sakuna'] 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;
2
3
 
3
- // https://api.nekosia.cat/api/v1/tags
4
+ // GET https://api.nekosia.cat/api/v1/tags