nhentai-api-tiffceet-patch
Version:
nHentai Node.JS API client.
330 lines (292 loc) • 7.46 kB
JavaScript
/**
* @module API
*/
import processOptions from './options';
import http from 'http';
import https from 'https';
import { version, } from '../package.json';
import Search from './search';
import Book from './book';
import Tag from './tag';
import Image from './image';
/**
* API arguments
* @typedef {object} APIArgs
* @property {string} host API host.
* @property {Function} apiPath API endpoint URL path generator.
*/
/**
* Class used for building URL paths to nHentai API endpoints.
* This class is internal and has only static methods.
* @class
*/
class APIPath {
/**
* Search by query endpoint.
* @param {string} query Search query.
* @param {number} [page=1] Page ID.
* @returns {string} URL path.
*/
static search(query, page = 1) {
return `/api/galleries/search?query=${query}&page=${page}`;
}
/**
* Search by tag endpoint.
* @param {number} tagID Tag ID.
* @param {number} [page=1] Page ID.
* @returns {string} URL path.
*/
static searchTagged(tagID, page = 1) {
return `/api/galleries/tagged?tag_id=${tagID}&page=${page}`;
}
/**
* Search alike endpoint.
* @param {number} bookID Book ID.
* @returns {string} URL path.
*/
static searchAlike(bookID) {
return `/api/gallery/${bookID}/related`;
}
/**
* Book content endpoint.
* @param {number} bookID Book ID.
* @returns {string} URL path.
*/
static book(bookID) {
return `/api/gallery/${bookID}`;
}
/**
* Book's cover image endpoint.
* @param {number} mediaID Media ID.
* @param {string} extension Image extension.
* @returns {string} URL path.
*/
static bookCover(mediaID, extension) {
return `/galleries/${mediaID}/cover.${extension}`;
}
/**
* Book's page image endpoint.
* @param {number} mediaID Media ID.
* @param {number} page Page ID.
* @param {string} extension Image extension.
* @returns {string} URL path.
*/
static bookPage(mediaID, page, extension) {
return `/galleries/${mediaID}/${page}.${extension}`;
}
/**
* Book's page's thumbnail image endpoint.
* @param {number} mediaID Media ID.
* @param {number} page Page ID.
* @param {string} extension Image extension.
* @returns {string} URL path.
*/
static bookThumb(mediaID, page, extension) {
return `/galleries/${mediaID}/${page}t.${extension}`;
}
}
/**
* Class used for interaction with nHentai API.
* @class
*/
class API {
/**
* API path class
* @type {APIPath}
* @static
* @private
*/
static APIPath = APIPath;
/**
* Applies provided options on top of defaults.
* @param {nHentaiOptions} options Options to apply.
*/
constructor(options = {}) {
let params = processOptions(options);
Object.assign(this, params);
}
/**
* Get http(s) module depending on `options.ssl`.
* @type {https|http}
*/
get net() {
return this.ssl
? https
: http;
}
/**
* JSON get request.
* @param {object} options HTTP(S) request options.
* @param {string} options.host Host.
* @param {string} options.path Path.
* @returns {Promise<object>} Parsed JSON.
*/
request(options) {
let {
net,
agent,
} = this;
return new Promise((resolve, reject) => {
Object.assign(options, {
agent,
headers: {
'User-Agent': `nhentai-api-client/${version} Node.js/${process.versions.node}`,
},
});
net.get(options, response => {
const
{ statusCode, } = response,
contentType = response.headers['content-type'];
let error;
if (statusCode !== 200)
error = new Error(`Request failed with status code ${statusCode}`);
else if (!(/^application\/json/).test(contentType))
error = new Error(`Invalid content-type - expected application/json but received ${contentType}`);
if (error) {
response.resume();
reject(error);
return;
}
response.setEncoding('utf8');
let rawData = '';
response.on('data', (chunk) => rawData += chunk);
response.on('end', () => {
try {
resolve(JSON.parse(rawData));
} catch (error) {
reject(error);
}
});
}).on('error', error => reject(error));
});
}
/**
* Get API arguments.
* This is internal method.
* @param {string} hostType Host type.
* @param {string} api Endpoint type.
* @returns {APIArgs} API arguments.
* @private
*/
getAPIArgs(hostType, api) {
let {
hosts: {
[hostType]: host,
},
constructor: {
APIPath: {
[api]: apiPath,
},
},
} = this;
return {
host,
apiPath,
};
}
/**
* Search by query.
* @param {string} query Query.
* @param {number} [page=1] Page ID.
* @returns {Promise<Search>} Search instance.
* @async
*/
async search(query, page = 1) {
let { host, apiPath, } = this.getAPIArgs('api', 'search'),
search = Search.parse(
await this.request({
host,
path: apiPath(query, page),
})
);
search.page = page;
return search;
}
/**
* Search related books.
* @param {number|Book} book Book instance or Book ID.
* @returns {Promise<Search>} Search instance.
* @async
*/
async searchAlike(book) {
let { host, apiPath, } = this.getAPIArgs('api', 'searchAlike');
return Search.parse(
await this.request({
host,
path: apiPath(
book instanceof Book
? book.id
: +book
),
})
);
}
/**
* Search by tag id.
* @param {number|Tag} tag Tag or Tag ID.
* @param {number} [page=1] Page ID.
* @returns {Promise<Search>} Search instance.
* @async
*/
async searchTagged(tag, page = 1) {
let { host, apiPath, } = this.getAPIArgs('api', 'searchTagged'),
search = Search.parse(
await this.request({
host,
path: apiPath(
tag instanceof Tag
? tag.id
: +tag,
page
),
})
);
search.page = page;
return search;
}
/**
* Get book by id.
* @param {number} bookID Book ID.
* @returns {Promise<Book>} Book instance.
* @async
*/
async getBook(bookID) {
let { host, apiPath, } = this.getAPIArgs('api', 'book');
return Book.parse(
await this.request({
host,
path: apiPath(bookID),
})
);
}
/**
* Get image URL.
* @param {Image} image Image.
* @returns {string} Image URL.
*/
getImageURL(image) {
if (image instanceof Image) {
let { host, apiPath, } = image.isCover
? this.getAPIArgs('thumbs', 'bookCover')
: this.getAPIArgs('images', 'bookPage');
return `http${this.ssl ? 's' : ''}://${host}` + (image.isCover
? apiPath(image.book.media, image.type.extension)
: apiPath(image.book.media, image.id, image.type.extension));
}
throw new Error('image must be Image instance.');
}
/**
* Get image thumbnail URL.
* @param {Image} image Image.
* @returns {string} Image thumbnail URL.
*/
getThumbURL(image) {
if (image instanceof Image && !image.isCover) {
let { host, apiPath, } = this.getAPIArgs('thumbs', 'bookThumb');
return `http${this.ssl ? 's' : ''}://${host}`
+ apiPath(image.book.media, image.id, image.type.extension);
}
throw new Error('image must be Image instance and not book cover.');
}
}
export default API;