nhentai.js-api
Version:
nhentai.net API
232 lines (231 loc) • 11 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NHentai = exports.NHSort = exports.NHSearchResults = void 0;
const jsdom_1 = require("jsdom");
const https_1 = __importDefault(require("https"));
const search_results_1 = __importStar(require("./search_results"));
exports.NHSearchResults = search_results_1.default;
Object.defineProperty(exports, "NHSort", { enumerable: true, get: function () { return search_results_1.NHSort; } });
class NHentai {
/**
* @throws if number is negative
* @throws if url is falsey or does not match NHentai.nhentaiRegex
*/
hentai(url) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
if (typeof url === 'number') {
if (isNaN(url) || url < 0) {
throw new Error('Not a valid number');
}
url = `https://nhentai.net/g/${url}`;
}
else if (!url) {
throw new Error('URL should not be falsey');
}
else if (!NHentai.nhentaiRegex.test(url)) {
throw new Error('URL is not a valid nhentai url');
}
const doc = (yield jsdom_1.JSDOM.fromURL(url)).window.document;
url = doc.location.href;
const id = +url.match(/\d+/)[0];
const thumbnails = Array.from(doc.querySelectorAll('div.thumbs img.lazyload')).map((el) => el.getAttribute('data-src'));
const title = doc
.querySelector('meta[itemprop=name]')
.getAttribute('content');
const recommended = search_results_1.default.collectHentai(doc, this);
const comments = () => {
return new Promise((resolve, reject) => {
https_1.default.get(`https://nhentai.net/api/gallery/${id}/comments`, (res) => {
res.setEncoding('utf8');
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('error', (error) => {
reject(error);
});
res.on('end', () => {
const json = JSON.parse(body);
if (res.statusCode === 200) {
resolve(json.map((c) => {
c.poster.avatar_url = `https://i.nhentai.net/${c.poster.avatar_url}`;
c.poster.url = `https://nhentai.net/users/${c.poster.id}/${c.poster.slug}`;
c.post_date *= 1000;
c.user = () => this.user(c.poster.id, c.poster.slug);
c.hentai = () => this.hentai(c.gallery_id);
return c;
}));
}
else {
reject(json);
}
});
});
});
};
return {
title,
cleanTitle: title.replace(/\([^(]+\)|\[[^\[]+\]/g, '').trim() || title,
id,
url: url,
cover: (_a = doc
.querySelector('meta[itemprop=image]')) === null || _a === void 0 ? void 0 : _a.getAttribute('content'),
images: thumbnails.map((src) => src.replace('t.', 'i.').replace('t.', '.')),
thumbnails,
tags: {
tags: this.tags(doc, 'Tags', 'tag'),
parodies: this.tags(doc, 'Parodies', 'parody'),
characters: this.tags(doc, 'Characters', 'category'),
artists: this.tags(doc, 'Artists', 'artist'),
groups: this.tags(doc, 'Groups', 'group'),
languages: this.tags(doc, 'Languages', 'language'),
categories: this.tags(doc, 'Categories', 'category')
},
uploaded: new Date((_b = doc.querySelector('span.tags > time')) === null || _b === void 0 ? void 0 : _b.getAttribute('datetime')).getTime(),
pages: thumbnails.length,
recommended,
comments
};
});
}
tags(doc, type, urlPart) {
return Array.from(Array.from(doc.querySelectorAll('div.tag-container'))
.find((el) => { var _a; return (_a = el.textContent) === null || _a === void 0 ? void 0 : _a.includes(type); })
.querySelectorAll('span a span'))
.map((span, i, arr) => {
var _a, _b;
return i % 2 === 0
? {
name: span.textContent,
amount: this.toNumber((_a = arr[i + 1]) === null || _a === void 0 ? void 0 : _a.textContent),
amountString: ((_b = arr[i + 1]) === null || _b === void 0 ? void 0 : _b.textContent) || '0',
url: `https://nhentai.net/${urlPart}/` +
span
.textContent.toLowerCase()
.replace(/ +/g, '-')
}
: undefined;
})
.filter((tag) => !!tag);
}
toNumber(text) {
if (!text) {
return 0;
}
else if (text.endsWith('K')) {
return Number.parseInt(text.replace('K', '000'));
}
else if (text.endsWith('M')) {
return Number.parseInt(text.replace('M', '000000'));
}
else {
return Number.parseInt(text);
}
}
/**
* Get a random hentai from nhentai.net
*
* @param english true if random hentai should be english
* @returns a random hentai
*/
random(english) {
return __awaiter(this, void 0, void 0, function* () {
if (!english) {
return this.hentai('https://nhentai.net/random/');
}
else {
const results = yield this.search('language:english');
const randomPage = Math.floor(Math.random() * results.pages) + 1;
yield results.goto(randomPage);
const randomHentai = Math.floor(Math.random() * results.hentai.length);
return this.hentai(results.hentai[randomHentai].id);
}
});
}
user(id, slug) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
slug = slug
.toLowerCase()
.replace(/[^a-z0-9_\-]/g, ' ')
.replace(/ +/g, '-');
const doc = (yield jsdom_1.JSDOM.fromURL(`https://nhentai.net/users/${id}/${slug}`)).window.document;
const container = doc.querySelector('#user-container');
const avatarImg = container.querySelector('div.bigavatar img');
const userInfo = container.querySelector('div.user-info');
const pEls = userInfo.querySelectorAll('p');
const pWith = (tag, has) => {
var _a;
for (const p of pEls) {
const el = p.querySelector(tag);
if ((_a = el === null || el === void 0 ? void 0 : el.textContent) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(has.toLowerCase())) {
return p;
}
}
};
return {
avatar: avatarImg.getAttribute('src'),
username: userInfo.querySelector('h1').textContent,
joined: new Date(userInfo.querySelector('p time').getAttribute('datetime')).getTime(),
about: (_a = pWith('b', 'About')) === null || _a === void 0 ? void 0 : _a.textContent,
favoriteTags: (_b = pWith('b', 'Favorite tags')) === null || _b === void 0 ? void 0 : _b.textContent,
recentComments: Array.from(doc.querySelectorAll('div.container div.comment')).map((el) => {
const wuckyJson = el.getAttribute('data-state');
const json = wuckyJson.replace(/"/g, '"');
const comment = JSON.parse(json);
comment.poster.avatar_url = `https://i.nhentai.net/${comment.poster.avatar_url}`;
comment.poster.url = `https://nhentai.net/users/${comment.poster.id}/${comment.poster.slug}`;
comment.post_date *= 1000;
comment.user = () => this.user(comment.poster.id, comment.poster.slug);
comment.hentai = () => this.hentai(comment.gallery_id);
return comment;
}),
recentFavorites: search_results_1.default.collectHentai(doc, this)
};
});
}
/**
* Search for a hentai on nhentai.net
*
* @returns {Promise<NHSearchResults>} search results
*/
search(query, sort) {
return __awaiter(this, void 0, void 0, function* () {
return new search_results_1.default(this, query, sort).lookup();
});
}
}
exports.NHentai = NHentai;
NHentai.nhentaiRegex = /^https?:\/\/nhentai.net\/(g\/\d+|random)\/?$/;