UNPKG

node-hitomi

Version:
237 lines (234 loc) 8.22 kB
import { HitomiError, ErrorCode } from '../structures/error.mjs'; import { TranslatedGallery, GalleryReference, Gallery, Title } from '../structures/gallery.mjs'; import { RESOURCE_DOMAIN } from '../internal/constants.mjs'; import { IndexProvider } from '../internal/providers.mjs'; import { Base } from '../internal/base.mjs'; import { Image, Video } from '../structures/media.mjs'; import { Tag, Language } from '../structures/tag.mjs'; const SortType = { DateAdded: 'added', DatePublished: 'published', Random: 'random', PopularityDay: 'today', PopularityWeek: 'week', PopularityMonth: 'month', PopularityYear: 'year' }; class GalleryManager extends Base { constructor(hitomi) { super(hitomi); this.defineProperties({ index: new IndexProvider(hitomi, 'galleries') }); } async retrieve(id) { const rawGallery = JSON.parse((await this.hitomi.request(RESOURCE_DOMAIN, '/galleries/' + id + '.js', 2)).slice(18)); const dedicatedTags = [[], [], [], []]; const tags = []; const files = []; const translations = []; let i = 0; let type; for(; i < GalleryManager.RAW_TYPES.length; i++) { const rawPluralType = GalleryManager.RAW_TYPES[i] + 's'; type = i !== 2 ? GalleryManager.RAW_TYPES[i] : 'series'; if(rawGallery[rawPluralType]) { for(let j = 0; j < rawGallery[rawPluralType].length; j++) dedicatedTags[i].push(new Tag(this.hitomi, type, rawGallery[rawPluralType][j][GalleryManager.RAW_TYPES[i]])); } } for(i = 0; i < rawGallery.tags.length; i++) { if(Boolean(rawGallery.tags[i].male)) { type = 'male'; } else if(Boolean(rawGallery.tags[i].female)) { type = 'female'; } else { type = 'tag'; } tags.push(new Tag(this.hitomi, type, rawGallery.tags[i].tag, false)); } const thumbnailIndex = Math.floor(rawGallery.files.length / 2); for(i = 0; i < rawGallery.files.length; i++) { files.push(new Image(this.hitomi, rawGallery.files[i].width, rawGallery.files[i].height, rawGallery.files[i].hash, rawGallery.files[i].name, Boolean(rawGallery.files[i].hasavif), true, false, !i || i === thumbnailIndex)); } for(i = 0; i < rawGallery.languages.length; i++) { translations.push(new TranslatedGallery(this.hitomi, rawGallery.languages[i].galleryid, new Language(this.hitomi, rawGallery.languages[i].name, rawGallery.languages[i].language_localname), rawGallery.languages[i].url)); } const relations = []; for(i = 0; i < rawGallery.related.length; i++) { relations.push(new GalleryReference(this.hitomi, rawGallery.related[i])); } return new Gallery(this.hitomi, +rawGallery.id, rawGallery.language ? new Language(this.hitomi, rawGallery.language, rawGallery.language_localname) : null, rawGallery.galleryurl, new Title(rawGallery.title, rawGallery.japanese_title), rawGallery.type, dedicatedTags[0], dedicatedTags[1], dedicatedTags[2], dedicatedTags[3], tags, files, translations, relations, Boolean(rawGallery.blocked), new Date(rawGallery.date), rawGallery.datepublished ? new Date(rawGallery.datepublished) : null, rawGallery.videofilename ? new Video(this.hitomi, files[1].width, files[1].height, rawGallery.videofilename, files[1].hash) : null); } async requestIds(path, range, isNegative = false) { const view = await this.hitomi.request(RESOURCE_DOMAIN, path, 1, range); const ids = new Set(); for(let i = 0; i < view.byteLength; i += 4) { ids.add(view.getInt32(i)); } if(isNegative) { ids.add(0); } return ids; } static createNozomiPath(options = {}) { const language = options.language || 'all'; let orderBy = ''; if(options.orderBy) { switch(options.orderBy) { case SortType.DatePublished: { orderBy = 'date/published'; } case SortType.DateAdded: case SortType.Random: { break; } case SortType.PopularityDay: case SortType.PopularityWeek: case SortType.PopularityMonth: case SortType.PopularityYear: { orderBy = 'popular/' + options.orderBy; break; } default: { throw HitomiError.invalidMember('OrderBy', SortType); } } } if(!options.tag || options.tag.type === 'language') { return '/n/' + (orderBy || 'index') + '-' + language + '.nozomi'; } if(orderBy) { orderBy += '/'; } let area; switch(options.tag.type) { case 'male': case 'female': { area = 'tag/'; orderBy += options.tag.type + ':'; break; } default: { area = options.tag.type + '/'; } } return '/n/' + area + orderBy + encodeURIComponent(options.tag.name) + '-' + language + '.nozomi'; } createReferences(ids, shouldShuffle) { const references = []; for(const id of ids) { references.push(new GalleryReference(this.hitomi, id)); } if(shouldShuffle) { let currentIndex = references.length; let targetIndex; while(currentIndex) { targetIndex = Math.floor(Math.random() * currentIndex--); const temporary = references[targetIndex]; references[targetIndex] = references[currentIndex]; references[currentIndex] = temporary; } } return references; } async list(options = {}) { const idSets = []; const isRandom = options.orderBy === SortType.Random; let language; let i = 0; let range; if(options.page) { const size = options.page.size ? options.page.size * 4 : 100; const start = options.page.index ? options.page.index * size : 0; range = start + '-' + (start + size - 1); } if(options.tags && options.tags.length) { const tags = options.tags.slice().sort(Tag.compare); if(!tags[0].isNegative) { for(; i < tags.length && !tags[i].isNegative; i++) { if(tags[i].type === 'language') { language = tags[i].name; break; } } i = 0; } if(range) { if(tags.length > 2 || tags.length === 2 && !language) { throw new HitomiError(ErrorCode.InvalidCombination, 'Page', 'used with multiple tags', false); } if(tags[tags.length - 1].isNegative) { throw new HitomiError(ErrorCode.InvalidCombination, 'Page', 'used with negative tag', false); } return this.createReferences(await this.requestIds(GalleryManager.createNozomiPath({ tag: tags[+(tags[0].type === 'language')], orderBy: options.orderBy, language: language }), range), isRandom); } idSets.push(await this.requestIds(GalleryManager.createNozomiPath({ tag: tags[0].isNegative ? undefined : tags[i++], orderBy: options.orderBy, language: language }))); for(; i < tags.length; i++) { if(tags[i].type !== 'language' || !language && tags[i].isNegative) { idSets.push(await this.requestIds(GalleryManager.createNozomiPath({ tag: tags[i], language: language }), undefined, tags[i].isNegative)); } } } else { const path = GalleryManager.createNozomiPath({ orderBy: options.orderBy }); if(range) { return this.createReferences(await this.requestIds(path, range), isRandom); } if(options.orderBy) { idSets.push(await this.requestIds(path)); } } if(options.title && options.title.length) { const version = await this.index.retrieve(); const title = options.title.toLowerCase() + ' '; const rootNode = await this.index.getNodeAtAddress(0n, version); if(!rootNode) { throw HitomiError.emptyRootNode; } i = 0; let j = title.indexOf(' '); while(j !== -1) { if(j - i) { const data = await this.index.binarySearch(await this.hitomi.hashTerm(title.slice(i, j)), rootNode, version); if(!data) { return []; } idSets.push(await this.requestIds('/galleriesindex/galleries.' + version + '.data', (data[0] + 4n) + '-' + (data[0] + BigInt(data[1]) - 1n))); } i = j + 1; j = title.indexOf(' ', i); } } if(idSets.length) { for(i = 1; i < idSets.length; i++) { if(!idSets[0].size) { return []; } const isNegative = idSets[i].has(0); for(const id of idSets[0]) { if(isNegative === idSets[i].has(id)) { idSets[0].delete(id); } } } } else { idSets.push(await this.requestIds(GalleryManager.createNozomiPath())); } return this.createReferences(idSets[0], isRandom); } } GalleryManager.RAW_TYPES = ['artist', 'group', 'parody', 'character']; export { GalleryManager, SortType };