Skip to content

Commit

Permalink
0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sefinek committed Dec 11, 2024
1 parent 284d050 commit 558434f
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 88 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,13 @@ The full list of tags is available [on the Booru site](https://nekosia.cat/booru
const { NekosiaAPI } = require('nekosia.js');

(async () => {
const response = await NekosiaAPI.fetchImages('catgirl');
console.log(response); // Sample response: https://nekosia.cat/documentation?page=api-endpoints#example-response
const response1 = await NekosiaAPI.fetchCategoryImages('catgirl');
console.log(response1);

const response2 = await NekosiaAPI.fetchImages({ session: 'ip', count: 1, tags: ['cute', 'blue-hair'], blacklist: ['yellow-hair'] });
console.log(response2);

// https://nekosia.cat/documentation?page=api-endpoints#example-response
})();
```

Expand All @@ -76,7 +81,7 @@ In this example, we used an IP-based session. What does this mean? Thanks to thi
const { NekosiaAPI } = require('nekosia.js');

(async () => {
const response = await NekosiaAPI.fetchImages('catgirl', {
const response = await NekosiaAPI.fetchCategoryImages('catgirl', {
session: 'ip',
count: 1,
additionalTags: [],
Expand All @@ -94,7 +99,7 @@ You can also use `id`, but this requires providing a user identifier (e.g., from
const { NekosiaAPI } = require('nekosia.js');

(async () => {
const response = await NekosiaAPI.fetchImages('catgirl', {
const response = await NekosiaAPI.fetchCategoryImages('catgirl', {
session: 'id',
id: '561621386765971781',
count: 1,
Expand Down
3 changes: 2 additions & 1 deletion examples/dynamicCategoryFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { NekosiaAPI } = require('../index.js');

const fetchImages = async (category, options = {}) => {
try {
const response = await NekosiaAPI.fetchImages(category, options);
const response = await NekosiaAPI.fetchCategoryImages(category, options);
console.log(`${category.toUpperCase()}:`, response);
} catch (err) {
console.error(`Error fetching ${category} images:`, err);
Expand All @@ -12,4 +12,5 @@ const fetchImages = async (category, options = {}) => {
(async () => {
await fetchImages('catgirl');
await fetchImages('foxgirl', { session: 'id', id: 'user123', count: 2 });
await fetchImages('catgirl', { tags: 'animal-ears' });
})();
2 changes: 1 addition & 1 deletion examples/basicUsage.js → examples/fetchCategoryImages.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { NekosiaAPI } = require('../index.js');

(async () => {
const response = await NekosiaAPI.fetchImages('foxgirl', {
const response = await NekosiaAPI.fetchCategoryImages('foxgirl', {
session: 'ip',
count: 1,
additionalTags: ['cute', 'sakura', 'cherry-blossom'],
Expand Down
6 changes: 6 additions & 0 deletions examples/fetchImages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { NekosiaAPI } = require('../index.js');

(async () => {
const response = await NekosiaAPI.fetchImages({ session: 'ip', count: 1, tags: ['cute', 'blue-hair'], blacklist: ['yellow-hair'] });
console.log(response);
})();
6 changes: 0 additions & 6 deletions examples/fetchShadowImages.js

This file was deleted.

6 changes: 4 additions & 2 deletions examples/version.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { NekosiaVersion } = require('../index.js');

(async () => {
console.log(NekosiaVersion.module);
console.log(await NekosiaVersion.api());
console.log(`Nekosia.js: v${NekosiaVersion.module}`);

const data = await NekosiaVersion.api();
console.log(`API: v${data.version}`);
})();
33 changes: 14 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class NekosiaAPI {
}
}

async fetchImages(category, options = {}) {
async fetchCategoryImages(category, options = {}) {
if (!category) {
throw new Error('The image category is required. For example, use fetchImages(\'catgirl\').');
throw new Error('The image category is required. For example, use fetchCategoryImages(\'catgirl\').');
}

if (options.session && !['id', 'ip'].includes(options.session)) {
Expand All @@ -38,24 +38,21 @@ class NekosiaAPI {
throw new Error('`id` is not required if the session is `null` or `undefined`');
}

const queryString = this.buildQueryParams({
session: null,
id: null,
count: 1,
additionalTags: [],
blacklistedTags: [],
...options,
});

return this.makeHttpRequest(`${API_URL}/images/${category}?${queryString}`);
return this.makeHttpRequest(`${API_URL}/images/${category}?${this.buildQueryParams(options)}`);
}

async fetchShadowImages(options = {}) {
if (!Array.isArray(options.additionalTags) || options.additionalTags.length === 0) {
throw new Error('`additionalTags` must be a non-empty array for the shadow category');
async fetchImages(options = {}) {
if (!Array.isArray(options.tags) || options.tags.length === 0) {
throw new Error('`tags` must be a non-empty array for the nothing category');
}

return this.fetchImages('shadow', options);
return this.fetchCategoryImages('nothing', {
session: options.session,
id: options.id,
count: options.count,
additionalTags: options.tags,
blacklistedTags: options.blacklist,
});
}

async fetchById(id) {
Expand All @@ -67,9 +64,7 @@ class NekosiaAPI {

const NekosiaVersion = {
module: https.version,
api: async () => {
return await https.get(BASE_URL);
},
api: async () => https.get(BASE_URL),
};

module.exports = {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nekosia.js",
"version": "0.1.5",
"version": "0.2.0",
"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!",
"keywords": [
"anime api",
Expand All @@ -15,15 +15,17 @@
"anime",
"anime-api",
"api wrapper",
"api-wrapper",
"cat ears",
"cat",
"catgirl",
"catgirls",
"cute animals",
"cute nekos",
"feline",
"girl",
"girls",
"image api",
"japan",
"javascript",
"js library",
"kawaii",
Expand All @@ -36,7 +38,6 @@
"nekosia",
"otaku",
"random anime",
"typescript",
"waifu"
],
"homepage": "https://nekosia.cat",
Expand Down
52 changes: 28 additions & 24 deletions services/https.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,40 @@ const headers = {
'DNT': '1',
};

const makeRequest = url => new Promise((resolve, reject) => {
const req = https.get(url, { headers, timeout: 9000 }, res => {
let data = '';
const timeout = 25000;

res.on('data', chunk => {
data += chunk;
});
const get = async url => {
if (!url || typeof url !== 'string') throw new Error('Missing url.');

res.on('end', () => {
if ((res.statusCode < 200 || res.statusCode >= 300) && res.statusCode !== 400) {
return reject(new Error(`HTTP Status Code: ${res.statusCode}`));
return new Promise((resolve, reject) => {
const req = https.get(url, { headers, timeout }, res => {
const { statusCode } = res;
if ((statusCode < 200 || statusCode >= 300) && statusCode !== 400) {
req.destroy();
return reject(new Error(`Unexpected HTTP Status Code: ${statusCode || 'unknown'}`));
}

try {
const parsedData = JSON.parse(data);
resolve(parsedData);
} catch (err) {
reject(new Error(`Failed to parse JSON: ${err.message}. Response: ${data}`));
}
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (err) {
reject(err);
}
});
});
});

req.on('error', err => reject(new Error(`Request error: ${err.message}`)));
req.on('error', err => {
req.destroy();
reject(err);
});

req.on('timeout', () => {
req.destroy();
reject(new Error('Timeout error'));
req.on('timeout', () => {
req.destroy();
reject(new Error(`Request timed out after ${timeout} ms.`));
});
});
};

req.end();
});

module.exports = { get: makeRequest, version };
module.exports = { get, version };
24 changes: 12 additions & 12 deletions test/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ const { NekosiaAPI } = require('../index.js');

describe('NekosiaAPI (API Tests)', () => {

describe('fetchImages', () => {
describe('fetchCategoryImages', () => {
it('should fetch images for the given category', async () => {
const res = await NekosiaAPI.fetchImages('catgirl', { count: 1 });
const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 1 });

expect(res).toBeInstanceOf(Object);
expect(res.success).toBe(true);
Expand Down Expand Up @@ -43,14 +43,14 @@ describe('NekosiaAPI (API Tests)', () => {
});

it('should return an error for an invalid category', async () => {
const res = await NekosiaAPI.fetchImages('invalid-category', { count: 1 });
const res = await NekosiaAPI.fetchCategoryImages('invalid-category', { count: 1 });

expect(res.success).toBe(false);
expect(res.status).toBe(400);
});

it('should return a specified number of images', async () => {
const res = await NekosiaAPI.fetchImages('catgirl', { count: 3 });
const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 3 });

expect(res).toBeInstanceOf(Object);
expect(res.success).toBe(true);
Expand All @@ -61,20 +61,20 @@ describe('NekosiaAPI (API Tests)', () => {
});
});

describe('fetchShadowImages', () => {
it('should handle no images found for shadow category with additional tags', async () => {
const res = await NekosiaAPI.fetchShadowImages({ count: 1, additionalTags: ['wTbf8J0TirS6a4fO5uyKcRazZOlO5h6o', 'xX9f9pwDAgsM3Li1LwsJ3tXQfGKW4WA0'] });
describe('fetchImages', () => {
it('should handle no images found for nothing category with additional tags', async () => {
const res = await NekosiaAPI.fetchImages({ count: 1, tags: ['wTbf8J0TirS6a4fO5uyKcRazZOlO5h6o', 'xX9f9pwDAgsM3Li1LwsJ3tXQfGKW4WA0'] });

expect(res.success).toBe(false);
expect(res.status).toBe(400);
});

it('should throw an error if additionalTagsArray is empty', async () => {
await expect(NekosiaAPI.fetchShadowImages([])).rejects.toThrow('`additionalTags` must be a non-empty array for the shadow category');
await expect(NekosiaAPI.fetchImages([])).rejects.toThrow('`tags` must be a non-empty array for the nothing category');
});

it('should return an error response for invalid count parameter', async () => {
const res = await NekosiaAPI.fetchImages('catgirl', { count: 'invalid' });
const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 'invalid' });
expect(res.success).toBe(false);
expect(res.status).toBe(400);
expect(res.message).toBe('Invalid count parameter. Expected a number between 1 and 48 (>48).');
Expand All @@ -83,7 +83,7 @@ describe('NekosiaAPI (API Tests)', () => {

describe('fetchById', () => {
it('should fetch an image by ID if it exists', async () => {
const res = await NekosiaAPI.fetchImages('catgirl', { count: 1 });
const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 1 });

if (res.success && res.id) {
const id = res.id;
Expand All @@ -99,10 +99,10 @@ describe('NekosiaAPI (API Tests)', () => {
});

it('should return an error response for invalid ID format', async () => {
const res = await NekosiaAPI.fetchById('12345');
const res = await NekosiaAPI.fetchCategoryImages('12345');
expect(res.success).toBe(false);
expect(res.status).toBe(400);
expect(res.message).toBe('The image with the provided identifier was not found.');
expect(res.message).toBe('No images matching the specified criteria were found. See https://nekosia.cat/documentation?page=api-endpoints#tags-and-categories');
});
});

Expand Down
12 changes: 6 additions & 6 deletions test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,30 +43,30 @@ describe('NekosiaAPI', () => {
});
});

describe('fetchImages', () => {
describe('fetchCategoryImages', () => {
it('should build correct endpoint and make request for given category', async () => {
const mockResponse = { data: { results: [] } };
https.get.mockResolvedValue(mockResponse);

const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/catgirl?count=2&additionalTags=cute';
const res = await NekosiaAPI.fetchImages('catgirl', { count: 2, additionalTags: 'cute' });
const res = await NekosiaAPI.fetchCategoryImages('catgirl', { count: 2, additionalTags: 'cute' });

expect(res).toEqual(mockResponse);
expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
});
});

describe('fetchShadowImages', () => {
describe('fetchImages', () => {
it('should throw an error if additionalTags is empty', async () => {
await expect(NekosiaAPI.fetchShadowImages({})).rejects.toThrow('`additionalTags` must be a non-empty array for the shadow category');
await expect(NekosiaAPI.fetchImages({})).rejects.toThrow('`tags` must be a non-empty array for the nothing category');
});

it('should correctly call fetchImages with additionalTags', async () => {
const mockResponse = { data: { results: [] } };
https.get.mockResolvedValue(mockResponse);

const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/shadow?count=1&additionalTags=dark,shadow';
const res = await NekosiaAPI.fetchShadowImages({ count: 1, additionalTags: ['dark', 'shadow'] });
const expectedEndpoint = 'https://api.nekosia.cat/api/v1/images/nothing?count=1&additionalTags=dark,shadow';
const res = await NekosiaAPI.fetchImages({ count: 1, tags: ['dark', 'shadow'] });

expect(res).toEqual(mockResponse);
expect(https.get).toHaveBeenCalledWith(expectedEndpoint);
Expand Down
Loading

0 comments on commit 558434f

Please sign in to comment.