booru
Version:
Search (and do other things) on a bunch of different boorus!
305 lines • 10.2 kB
JavaScript
"use strict";
/**
* @packageDocumentation
* @module Structures
*/
Object.defineProperty(exports, "__esModule", { value: true });
const node_util_1 = require("node:util");
const common = (0, node_util_1.deprecate)(() => { }, 'Common is now deprecated, just access the properties directly');
/**
* Tries to figure out what the image url should be
*
* @param {string} url why
* @param {*} data boorus
* @param {Booru} booru so hard
*/
function parseImageUrl(url, data, booru) {
let outUrl = url;
// If the image's file_url is *still* undefined or the source is empty or it's deleted
// Thanks danbooru *grumble grumble*
if (!outUrl || outUrl.trim() === '' || data.is_deleted) {
return null;
}
if (outUrl.startsWith('/data')) {
outUrl = `https://danbooru.donmai.us${outUrl}`;
}
if (outUrl.startsWith('/cached')) {
outUrl = `https://danbooru.donmai.us${outUrl}`;
}
if (outUrl.startsWith('/_images')) {
outUrl = `https://dollbooru.org${outUrl}`;
}
if (outUrl.startsWith('//derpicdn.net')) {
outUrl = `https:${data.image}`;
}
if (outUrl.startsWith('/_thumbs')) {
outUrl = `https://rule34.paheal.net${outUrl}`;
}
// Why???
if (!data.file_url && data.directory !== undefined) {
// Danbooru-based boorus sometimes sort their files into directories
// There's 2 directories, one named after the first 2 characters of the hash
// and one named after the next 2 characters of the hash
// Sometimes we get it in the API response as `data.directory`, sometimes it's null
// for some ungodly reason
// I despise the danbooru api honestly
const directory = data.directory ?? `${data.hash.substr(0, 2)}/${data.hash.substr(2, 2)}`;
outUrl = `//${booru.domain}/images/${directory}/${data.image}`;
}
if (!outUrl.startsWith('http')) {
outUrl = `https:${outUrl}`;
}
if (booru.domain === 'xbooru.com' &&
outUrl.startsWith('https://api-cdn.rule34.xxx/')) {
outUrl = outUrl.replace('https://api-cdn.rule34.xxx/', 'https://xbooru.com/');
}
return encodeURI(outUrl);
}
/**
* Takes and transforms tags from the booru's api into a common format
* (which is an array of strings)
* @param {any} data The data from the booru
* @returns {string[]} The tags as a string array, and not just a string or an object
*/
function getTags(data) {
let tags = [];
if (Array.isArray(data.tags)) {
tags = data.tags;
}
else if (data.tags?.general) {
tags = Object.values(data.tags).flat();
}
else if (typeof data.tags === 'string') {
tags = fromTagString(data.tags);
}
else if (typeof data.tag_string === 'string') {
tags = fromTagString(data.tag_string);
}
return tags.filter((v) => v !== '');
}
/**
* Parses a string of tags into an array of tags, doing some sanitization
*
* @example
* fromTagString('tag1 tag2 tag3') => ['tag1', 'tag2', 'tag3']
* fromTagString('tag1 tag,2 tag3 ') => ['tag1', 'tag2', 'tag3']
* @param tags The tags as a string
* @returns The string, parsed into an array of tags
*/
function fromTagString(tags) {
return tags.split(' ').map((v) => v.replace(/,/g, ''));
}
/**
* An image from a booru with a few common props
*
* @example
* ```
* Post {
* fileUrl: 'https://aaaa.com/image.jpg',
* id: '124125',
* tags: ['cat', 'cute'],
* score: 5,
* source: 'https://giraffeduck.com/aaaa.png',
* rating: 's'
* }
* ```
*/
class Post {
/** The {@link Booru} it came from */
booru;
/** The direct link to the file */
fileUrl;
/** The height of the file */
height;
/** The width of the file */
width;
/** The url to the medium-sized image (if available) */
sampleUrl;
/** The height of the medium-sized image (if available) */
sampleHeight;
/** The width of the medium-sized image (if available) */
sampleWidth;
/** The url to the smallest image (if available) */
previewUrl;
/** The height of the smallest image (if available) */
previewHeight;
/** The width of the smallest image (if available) */
previewWidth;
/** The id of this post */
id;
/** If this post is available (ie. not deleted, not banned, has file url) */
available;
/** The tags of this post */
tags;
/** The score of this post */
score;
/** The source of this post, if available */
source;
/**
* The rating of the image, as just the first letter
* (s/q/e/u) => safe/questionable/explicit/unrated
*/
rating;
/** The Date this post was created at */
createdAt;
/** All the data given by the booru @private */
data;
/**
* Create an image from a booru
*
* @param {Object} data The raw data from the Booru
* @param {Booru} booru The booru that created the image
*/
constructor(data, booru) {
// Damn wild mix of boorus
this.data = data;
this.booru = booru;
// Again, thanks danbooru
const deletedOrBanned = data.is_deleted || data.is_banned;
this.fileUrl = parseImageUrl(data.file_url ??
data.image ??
(deletedOrBanned ? data.source : undefined) ??
data.file?.url ??
data.representations?.full, data, booru);
this.available = !deletedOrBanned && this.fileUrl !== null;
this.height = Number.parseInt(data.height ?? data.image_height ?? data.file?.height, 10);
this.width = Number.parseInt(data.width ?? data.image_width ?? data.file?.width, 10);
this.sampleUrl = parseImageUrl(data.sample_url ??
data.large_file_url ??
data.representations?.large ??
data.sample?.url, data, booru);
this.sampleHeight = Number.parseInt(data.sample_height ?? data.sample?.height, 10);
this.sampleWidth = Number.parseInt(data.sample_width ?? data.sample?.width, 10);
this.previewUrl = parseImageUrl(data.preview_url ??
data.preview_file_url ??
data.representations?.small ??
data.preview?.url, data, booru);
this.previewHeight = Number.parseInt(data.preview_height ?? data.preview?.height, 10);
this.previewWidth = Number.parseInt(data.preview_width ?? data.preview?.width, 10);
this.id = data.id ? data.id.toString() : 'No ID available';
this.tags = getTags(data);
if (data.score && data.score.total !== undefined) {
this.score = data.score.total;
}
else {
this.score = data.score ? Number.parseInt(data.score, 10) : data.score;
}
this.source = data.source ?? data.sources ?? data.source_url;
this.rating =
data.rating ??
/(safe|suggestive|questionable|explicit)/i.exec(data.tags) ??
'u';
if (Array.isArray(this.rating)) {
this.rating = this.rating[0];
}
// Thanks derpibooru
if (this.rating === 'suggestive') {
this.rating = 'q';
}
this.rating = this.rating.charAt(0);
this.createdAt = null;
// eslint-disable-next-line
if (typeof data.created_at === 'object') {
this.createdAt = new Date(data.created_at.s * 1000 + data.created_at.n / 1000000000);
}
else if (typeof data.created_at === 'number') {
this.createdAt = new Date(data.created_at * 1000);
}
else if (typeof data.created_at === 'string') {
this.createdAt = new Date(data.created_at);
}
else if (typeof data.change === 'number') {
this.createdAt = new Date(data.change * 1000);
}
else {
this.createdAt = new Date(data.created_at ?? data.date);
}
}
/**
* The direct link to the file
* <p>It's prefered to use `.fileUrl` instead because camelCase
*/
get file_url() {
return this.fileUrl;
}
/**
* The url to the medium-sized image (if available)
* <p>It's prefered to use `.sampleUrl` instead because camelCase
*/
get sample_url() {
return this.sampleUrl;
}
/**
* The height of the medium-sized image (if available)
* <p>It's prefered to use `.sampleHeight` instead because camelCase
*/
get sample_height() {
return this.sampleHeight;
}
/**
* The width of the medium-sized image (if available)
* <p>It's prefered to use `.sampleWidth` instead because camelCase
*/
get sample_width() {
return this.sampleWidth;
}
/**
* The url to the smallest image (if available)
* <p>It's prefered to use `.previewUrl` instead because camelCase
*/
get preview_url() {
return this.previewUrl;
}
/**
* The height of the smallest image (if available)
* <p>It's prefered to use `.previewHeight` instead because camelCase
*/
get preview_height() {
return this.previewHeight;
}
/**
* The width of the smallest image (if available)
* <p>It's prefered to use `.previewWidth` instead because camelCase
*/
get preview_width() {
return this.previewWidth;
}
/**
* Get the post view (url to the post) of this image
*
* @type {String}
* @example
* ```
* const e9 = Booru('e9')
* const imgs = e9.search(['cat', 'dog'])
*
* // Log the post url of the first image
* console.log(imgs[0].postView)
* ```
*/
get postView() {
return this.booru.postView(this.id);
}
/**
* Get some common props on the image
*
* @deprecated All common props are now attached directly to the image
* @type {Object}
*
* @example
* ```
* image.id
* // deprecated, use this instead
* image.id
*
* // To access the post's raw data from the booru, do
* image._data.id
* ```
*/
get common() {
common();
return this;
}
}
exports.default = Post;
//# sourceMappingURL=Post.js.map