UNPKG

fetch-fic

Version:

Package up delicious, delicious fanfic from various sources into epub ebooks ready for reading in your ereader of choice.

268 lines (251 loc) 7.29 kB
'use strict' /* eslint-disable no-return-assign */ const qw = require('qw') const Site = require('./site.js') class Fic { constructor (fetch) { this.id = null this.fetch = fetch this.title = null this.link = null this.updateFrom = null this.author = null this.authorUrl = null this.created = null this.modified = null this.publisher = null this.description = null this.cover = null this.chapterHeadings = null this.externals = null this.words = null this.tags = [] this.fics = [] this.chapters = new ChapterList() this.site = null this.includeTOC = null this.numberTOC = null this.fetchMeta = null this.scrapeMeta = null } updateWith () { return this.updateFrom || this.link } chapterExists (link) { if (link == null) return false if (this.chapters.chapterExists(link, this)) return true if (this.fics.some(fic => fic.chapterExists(link))) return true return false } normalizeLink (link) { try { const site = Site.fromUrl(link) return site.normalizeLink(link) } catch (_) { return link } } getChapter (fetch, link) { const site = Site.fromUrl(link) return site.getChapter(fetch, link) } addChapter (opts) { if (this.chapterExists(opts.link) || this.chapterExists(opts.fetchFrom)) return this.chapters.addChapter(opts) } importFromJSON (raw) { for (let prop of qw` id link title author authorUrl created modified description tags publisher cover chapterHeadings words updateFrom includeTOC numberTOC fetchMeta scrapeMeta `) { this[prop] = raw[prop] } this.chapters.importFromJSON(raw) if (raw.fics) { raw.fics.forEach(fic => this.fics.push(SubFic.fromJSON(this, fic))) } this.site = Site.fromUrl(this.updateWith()) this.externals = raw.externals != null ? raw.externals : true return this } static fromUrl (fetch, link) { const fic = new this(fetch) fic.site = Site.fromUrl(link) fic.link = fic.site.link return fic.site.getFicMetadata(fetch, fic).then(thenMaybeFallback, elseMaybeFallback).thenReturn(fic) function elseMaybeFallback (err) { if (err && (!err.meta || err.meta.status !== 404)) throw err return thenMaybeFallback() } function thenMaybeFallback () { // no chapters in the threadmarks, fallback to fetching if (fic.chapters.length === 0) { fic.scrapeMeta = true return fic.site.scrapeFicMetadata(fetch, fic) } else { fic.fetchMeta = true } } } static fromUrlAndScrape (fetch, link) { const fic = new this(fetch) fic.site = Site.fromUrl(link) fic.link = fic.site.link fic.fetchMeta = true fic.scrapeMeta = true return fic.site.getFicMetadata(fetch, fic).then(() => { return fic.site.scrapeFicMetadata(fetch, fic).thenReturn(fic) }) } static scrapeFromUrl (fetch, link) { const fic = new this() fic.site = Site.fromUrl(link) fic.link = fic.site.link fic.scrapeMeta = true return fic.site.scrapeFicMetadata(fetch, fic).thenReturn(fic) } static fromJSON (raw) { const fic = new this() return fic.importFromJSON(raw) } toJSON () { const result = {} for (let prop of qw` id title link updateFrom author authorUrl created modified publisher cover description tags words fics chapters chapterHeadings includeTOC numberTOC fetchMeta scrapeMeta `) { if (this[prop] != null && (!Array.isArray(this[prop]) || this[prop].length)) result[prop] = this[prop] } if (!this.externals) result.externals = this.externals return result } } class SubFic extends Fic { constructor (parentFic) { super() delete this.fics this.parent = parentFic } chapterExists (link) { return this.chapters.chapterExists(link, this) } static fromJSON (parent, raw) { const fic = new this(parent) fic.importFromJSON(raw) return fic } get author () { return this._author || this.parent.author } set author (value) { return this._author = value } get authorUrl () { return this._authorUrl || this.parent.authorUrl } set authorUrl (value) { return this._authorUrl = value } get publisher () { return this._publisher || this.parent.publisher } set publisher (value) { return this._publisher = value } get link () { return this._link || this.parent.link } set link (value) { return this._link = value } get chapterHeadings () { return this._chapterHeadings || this.parent.chapterHeadings } set chapterHeadings (value) { return this._chapterHeadings = value } get externals () { return this._externals || this.parent.externals } set externals (value) { return this._externals = value } toJSON () { const result = {} for (let prop of qw` title _link _author _authorUrl created modified _publisher description tags chapters _chapterHeadings words includeTOC numberTOC `) { const assignTo = prop[0] === '_' ? prop.slice(1) : prop if (this[prop] && (this[prop].length == null || this[prop].length)) result[assignTo] = this[prop] } return result } } class ChapterList extends Array { chapterExists (link, fic) { if (link == null) { return } else if (fic) { return this.some(chap => fic.normalizeLink(chap.link) === fic.normalizeLink(link)) } else { return this.some(chap => chap.link === link) } } addChapter (opts) { if (this.chapterExists(opts.fetchFrom) || this.chapterExists(opts.link)) return let name = opts.name let ctr = 0 while (this.some(chap => chap.name === name)) { name = opts.name + ' (' + ++ctr + ')' } if (opts.created && (!this.created || opts.created < this.created)) this.created = opts.created this.push(new Chapter(Object.assign({}, opts, {name, order: this.length}))) } importFromJSON (raw) { if (raw.fics && !raw.chapters) return if (!raw.chapters) { throw new Error('Fic "' + raw.title + '" is missing any chapters.') } raw.chapters.forEach(chapter => this.push(Chapter.fromJSON(this.length, chapter))) } } class Chapter { constructor (opts) { this.order = opts.order this.name = opts.name this.link = opts.link this.fetchFrom = opts.fetchFrom this.created = opts.created this.modified = opts.modified this.author = opts.author this.authorUrl = opts.authorUrl this.tags = opts.tags this.externals = opts.externals != null ? opts.externals : true this.headings = opts.headings this.words = opts.words } toJSON () { return { name: this.name, link: this.link, fetchFrom: this.fetchFrom, author: this.author, authorUrl: this.authorUrl, created: this.created, modified: this.modified, tags: this.tags, externals: this.externals !== true ? this.externals : null, headings: this.headings, words: this.words } } static fromJSON (order, opts) { return new Chapter(Object.assign({order}, opts)) } fetchWith () { return this.fetchFrom || this.link } } module.exports = Fic