mangadex/scripts/reader/resource/Manga.js
2021-03-19 13:06:32 -07:00

165 lines
5.6 KiB
JavaScript

import { differenceInHours } from 'date-fns'
import Resource from './Resource'
import Chapter from './Chapter'
import Group from './Group'
import Utils from '../utils'
import natsort from 'natsort'
export default class Manga extends Resource {
static get resourceType() { return 'manga' }
constructor(data = {}, responseCode = -1, responseStatus = null) {
super(data, responseCode, responseStatus)
this.id = data.id
this.title = data.title
this.language = data.publication ? data.publication.language : null
this.isHentai = data.isHentai
this.lastChapter = data.lastChapter || ''
this.lastVolume = data.lastVolume || ''
this.mainCover = data.mainCover
this.tags = data.tags || []
this.links = data.links || {}
this._uniqueChapters = []
this._chapters = []
this.chapterListLastLoaded = null
}
// derived data
get isLongStrip() { return this.tags.indexOf(36) !== -1 }
get isDoujinshi() { return this.tags.indexOf(7) !== -1 }
get url() {
const title = this.title.toLowerCase().replace(/&[0-9a-z]+;/gi, '').replace(/[^0-9a-z]/gi, ' ').split(' ').filter(t => t).join('-')
return `/title/${this.id}/${title}`
}
get coverThumb() { return `/images/manga/${this.id}.thumb.jpg` }
get isChapterListOutdated() { return differenceInHours(this.chapterListLastLoaded, new Date()) >= 1 }
// methods
isLastChapter(chapter) {
return this.lastChapter && (chapter.chapter === this.lastChapter) && (chapter.volume == this.lastVolume)
}
getChapter(id) {
return Chapter.getResource(id)
}
get chapterList() {
return this._chapters
}
get uniqueChapterList() {
return this._uniqueChapters
}
updateChapterList(baseChapter, restrictChLang = false) {
let chapters = Chapter.findResources(c => c.mangaId === this.id)
if (restrictChLang) {
chapters = chapters.filter(c => c.language === baseChapter.language)
}
const chapterIds = Manga.createSortedChapterIdList(chapters)
this._chapters = chapterIds.map(id => Chapter.getResource(id))
this._uniqueChapters = Manga.createUniqueChapterList(baseChapter, this._chapters)
}
getNextChapterId(id) {
const index = this._uniqueChapters.map(c => c.id).indexOf(id)
return (index >= 0 && index + 1 < this._uniqueChapters.length) ? this._uniqueChapters[index + 1].id : 0
}
getPrevChapterId(id) {
const index = this._uniqueChapters.map(c => c.id).indexOf(id)
return (index > 0) ? this._uniqueChapters[index - 1].id : -1
}
getAltChapters(id) {
const cur = Chapter.getResource(id)
return cur ? this.chapterList.filter(ch => cur.isAlternativeOf(ch)) : []
}
static createSortedChapterIdList(chapters) {
chapters = chapters.map(ch => {
return !ch ? null : {
id: ch.id,
timestamp: ch.timestamp,
chapter: ch.chapter,
volume: ch.volume,
group: ch.groupIds[0],
}
}).filter(c => c)
const sorter = natsort({ asc: true, insensitive: true })
// sort by timestamp desc
Utils.stableSort(chapters, (a, b) => sorter(b.timestamp, a.timestamp))
// sort by volume desc, so that vol null > vol number where ch are equal
Utils.stableSort(chapters, (a, b) => sorter(b.volume, a.volume))
// sort by group
Utils.stableSort(chapters, (a, b) => sorter(a.group, b.group))
// sort by chapter number
Utils.stableSort(chapters, (a, b) => sorter(a.chapter, b.chapter))
// add "ghost" prev vol numbers
let pv = '0'
for (let c of chapters) {
c.prevVolume = pv
pv = c.volume ? c.volume : pv
}
// sort by vol or prev vol
Utils.stableSort(chapters, (a, b) => sorter(a.volume || a.prevVolume, b.volume || b.prevVolume))
return chapters.map(c => c.id)
}
static createUniqueChapterList(baseChapter, chapters) {
const list = []
if (chapters.length > 0) {
let best = chapters[0]
for (let ch of chapters.slice(1)) {
if (!ch.isAlternativeOf(best)) {
list.push(best)
best = ch
} else if (best !== baseChapter && ch.hasSameGroupsWith(baseChapter)) {
best = ch
}
}
list.push(best)
}
return list
}
static async load(id, params = {}, cache = true) {
const json = await super.load(`manga/${id}`, params)
try {
return this.fromJSON(json.data, json.code, json.status, cache)
} catch (error) {
throw this.fromJSON({ id, status: json.message }, json.code, json.status, false)
}
}
static async loadChapterList(id, params = {}, cache = true) {
const json = await super.load(`manga/${id}/chapters`, params)
try {
const { chapters } = this.fromChapterListJSON(id, json, cache)
return chapters
} catch (error) {
return []
}
}
static async loadWithChapterList(id, params = {}, cache = true) {
const json = await super.load(`manga/${id}`, Object.assign(params, { include: 'chapters' }))
try {
const { manga } = this.fromChapterListJSON(id, json, cache)
return manga
} catch (error) {
throw this.fromJSON({ id, status: json.message }, json.code, json.status, false)
}
}
static fromChapterListJSON(id, json, cache) {
// avoid overwriting group/chapter resources that already exist because these are going to be incomplete
const groups = json.data.groups.map(g => Group.getResource(g.id) || Group.fromJSON(g))
const chapters = json.data.chapters.map(c => Chapter.getResource(c.id) || Chapter.fromJSON(c, json.code, json.status, cache))
const manga = json.data.manga ? this.fromJSON(json.data.manga, json.code, json.status, cache) : this.getResource(id)
manga.chapterListLastLoaded = new Date()
//manga.updateChapterList()
return { groups, chapters, manga }
}
}