diff --git a/.vscode/settings.json b/.vscode/settings.json index aa1c94e..4010776 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "deno.enable": true, - "deno.unstable": true + "deno.enable": true, + "deno.unstable": true, } diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..541a5c4 --- /dev/null +++ b/deno.json @@ -0,0 +1,7 @@ +{ + "fmt": { + "options": { + "indentWidth": 4 + } + } +} diff --git a/src/classes/AO3.ts b/src/classes/AO3.ts index 21f5a16..ce27177 100644 --- a/src/classes/AO3.ts +++ b/src/classes/AO3.ts @@ -1,46 +1,46 @@ import Work from "./Work.ts"; import { ID } from "../types.d.ts"; import { - DOMParser, -} from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/deno-dom-wasm.ts"; + DOMParser, +} from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/deno-dom-native.ts"; export default class AO3 { - session: { - get: (path: string) => Promise; - }; - DOMParser = new DOMParser(); + session: { + get: (path: string) => Promise; + }; + DOMParser = new DOMParser(); - /** - * a representation of AO3 in class form - */ - constructor(opts?: { - url?: string; - }) { - /*this.session = axiod.create({ + /** + * a representation of AO3 in class form + */ + constructor(opts?: { + url?: string; + }) { + /*this.session = axiod.create({ baseURL: opts?.url ?? "https://archiveofourown.org/", });*/ - this.session = { - get: async (path: string) => { - const res = await fetch( - opts?.url ?? "https://archiveofourown.org/" + path, - ); - if (res.status > 300) { - console.log(res); - throw new Error("Failed request, probably rate-limited"); - } - return res; - }, - }; - } + this.session = { + get: async (path: string) => { + const res = await fetch( + opts?.url ?? "https://archiveofourown.org/" + path, + ); + if (res.status > 300) { + console.log(res); + throw new Error("Failed request, probably rate-limited"); + } + return res; + }, + }; + } - /** - * gets a Work from an ID - * @returns {Promise} a Work class for the work - */ - async getWork(id: ID): Promise { - const res = await this.session.get( - `/works/${id}?view_adult=true&view_full_work=true`, - ); - return new Work(id, await res.text(), this.session, new DOMParser()); - } + /** + * gets a Work from an ID + * @returns {Promise} a Work class for the work + */ + async getWork(id: ID): Promise { + const res = await this.session.get( + `/works/${id}?view_adult=true&view_full_work=true`, + ); + return new Work(id, await res.text(), this.session, new DOMParser()); + } } diff --git a/src/classes/Chapter.ts b/src/classes/Chapter.ts index f38c381..a9a400e 100644 --- a/src/classes/Chapter.ts +++ b/src/classes/Chapter.ts @@ -1,116 +1,119 @@ import axiod from "https://deno.land/x/axiod@0.26.2/mod.ts"; import type { - DOMParser, + DOMParser, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/dom-parser.ts"; import type { - Element, + Element, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/element.ts"; import type { - HTMLDocument, + HTMLDocument, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/document.ts"; import { ID } from "../types.d.ts"; export default class Chapter { - #session: { - get: (path: string) => Promise; - }; - isInited = false; - #document!: HTMLDocument; - #DOMParser: DOMParser; - #id: ID; - #workID: ID; - #name!: string; - #text!: string; - #summary!: string; - #startNote!: string; - #endNote!: string; - id!: Promise; - workID!: Promise; - name!: Promise; - text!: Promise; - summary!: Promise; - startNote!: Promise; - endNote!: Promise; + #session: { + get: (path: string) => Promise; + }; + isInited = false; + #document!: HTMLDocument; + #DOMParser: DOMParser; + #id: ID; + #workID: ID; + #name!: string; + #text!: string; + #summary!: string; + #startNote!: string; + #endNote!: string; + id!: Promise; + workID!: Promise; + name!: Promise; + text!: Promise; + summary!: Promise; + startNote!: Promise; + endNote!: Promise; - constructor(workId: ID, id: ID, session: { - get: (path: string) => Promise; - }, DOMParser: DOMParser) { - this.#session = session; - this.#workID = workId; - this.#id = id; - this.#DOMParser = DOMParser; + constructor(workId: ID, id: ID, session: { + get: (path: string) => Promise; + }, DOMParser: DOMParser) { + this.#session = session; + this.#workID = workId; + this.#id = id; + this.#DOMParser = DOMParser; - return new Proxy(this, { - get: async (target, prop) => { - if (!this.isInited) { - await target.init(); - target.isInited = true; - } - switch (prop) { - case "id": - return target.#id; - case "workID": - return target.#workID; - case "name": - return target.#name; - case "text": - return target.#text; - case "summary": - return target.#summary; - case "startNote": - return target.#startNote; - case "endNote": - return target.#endNote; - } - }, - }); - } + return new Proxy(this, { + get: async (target, prop) => { + if (!this.isInited) { + await target.init(); + target.isInited = true; + } + switch (prop) { + case "id": + return target.#id; + case "workID": + return target.#workID; + case "name": + return target.#name; + case "text": + return target.#text; + case "summary": + return target.#summary; + case "startNote": + return target.#startNote; + case "endNote": + return target.#endNote; + } + }, + }); + } - async init() { - console.log("initing chapter"); - const res = await this.#session.get( - `/works/${this.#workID}/chapters/${this.#id}?view_adult=true`, - ); - this.#document = this.#DOMParser.parseFromString( - await res.text(), - "text/html", - ) as HTMLDocument; - this.populateMetadata(); - this.populateSummary(); - this.populateNotes(); - this.populateText(); - } + async init() { + console.log("initing chapter"); + const res = await this.#session.get( + `/works/${this.#workID}/chapters/${this.#id}?view_adult=true`, + ); + this.#document = this.#DOMParser.parseFromString( + await res.text(), + "text/html", + ) as HTMLDocument; + this.populateMetadata(); + this.populateSummary(); + this.populateNotes(); + this.populateText(); + } - populateMetadata() { - this.#name = this.#document.querySelector("h3.title")?.innerText.replace( - /Chapter \d+: /, - "", - ).trim() as string; - } + populateMetadata() { + this.#name = this.#document.querySelector("h3.title")?.innerText + .replace( + /Chapter \d+: /, + "", + ).trim() as string; + } - populateSummary() { - this.#summary = this.#document.querySelector("#summary > .userstuff") - ?.innerText.trim() as string; - } + populateSummary() { + this.#summary = this.#document.querySelector("#summary > .userstuff") + ?.innerText.trim() as string; + } - populateNotes() { - const notesList = Array.from( - this.#document.querySelectorAll(".notes > .userstuff"), - ).map((n) => - (n as Element).innerHTML - ); - this.#startNote = notesList[0]?.trim()?.replace(/<\/{0,1}p>/g, "\n")?.trim(); - this.#endNote = notesList[1]?.trim()?.replace(/<\/{0,1}p>/g, "\n")?.trim(); - } + populateNotes() { + const notesList = Array.from( + this.#document.querySelectorAll(".notes > .userstuff"), + ).map((n) => (n as Element).innerHTML); + this.#startNote = notesList[0]?.trim()?.replace(/<\/{0,1}p>/g, "\n") + ?.trim(); + this.#endNote = notesList[1]?.trim()?.replace(/<\/{0,1}p>/g, "\n") + ?.trim(); + } - populateText() { - /*this.text = this.#document.querySelector("div.userstuff[role='article']")?.innerText.trim().replace(/Chapter Text\s+/, "") as string*/ - //"div.userstuff[role='article'] > p" - Array.from( - this.#document.querySelectorAll("div.userstuff[role='article'] > p"), - ).forEach( - (t) => this.#text += (t as Element).innerText + "\n", - ); - this.#text = this.#text.trim(); - } + populateText() { + /*this.text = this.#document.querySelector("div.userstuff[role='article']")?.innerText.trim().replace(/Chapter Text\s+/, "") as string*/ + //"div.userstuff[role='article'] > p" + Array.from( + this.#document.querySelectorAll( + "div.userstuff[role='article'] > p", + ), + ).forEach( + (t) => this.#text += (t as Element).innerText + "\n", + ); + this.#text = this.#text.trim(); + } } diff --git a/src/classes/Work.ts b/src/classes/Work.ts index d9762d7..d0ae2fa 100644 --- a/src/classes/Work.ts +++ b/src/classes/Work.ts @@ -1,144 +1,141 @@ import Chapter from "./Chapter.ts"; import type { - DOMParser, + DOMParser, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/dom-parser.ts"; import type { - Element, + Element, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/element.ts"; import type { - HTMLDocument, + HTMLDocument, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/document.ts"; import { ID } from "../types.d.ts"; -import asyncForEach from "../utils/asyncForeach.ts"; export default class Work { - #session: { - get: (path: string) => Promise; - }; - /** - * ID of the work - */ - id: ID; - #document: HTMLDocument; - #DOMParser: DOMParser; - /** - * a list of Chapters in the work - */ - chapters: Chapter[] = []; - /** - * a list of tags in the work - */ - tags: string[] = []; - /** - * the approximate date the work was published - */ - published?: Date; - /** - * the approximate date the work was last updated - */ - updated?: Date; + #session: { + get: (path: string) => Promise; + }; + /** + * ID of the work + */ + id: ID; + #document: HTMLDocument; + #DOMParser: DOMParser; + /** + * a list of Chapters in the work + */ + chapters: Chapter[] = []; + /** + * a list of tags in the work + */ + tags: string[] = []; + /** + * the approximate date the work was published + */ + published?: Date; + /** + * the approximate date the work was last updated + */ + updated?: Date; - /** - * represents a work on AO3 - * @param id the ID of the work - * @param body the HTML body of the work (in text) - * @param session an axiod session (used for fetching additional details) - */ - constructor( - id: ID, - body: string, - session: { - get: (path: string) => Promise; - }, - DOMParser: DOMParser, - ) { - this.#session = session; - this.id = id; - this.#document = DOMParser.parseFromString( - body, - "text/html", - ) as HTMLDocument; - this.#DOMParser = DOMParser; - } - - //jank incarnate - async init() { - this.populateTags(); - this.populateDates(); - await this.populateChapters(); - } - - populateTags() { - Array.from( - (this.#document.querySelector("dd.fandom > ul.commas") as Element) - .children, - ).map( - (t) => this.tags.push(t.children[0].innerText), - ); - Array.from( - (this.#document.querySelector("dd.relationship > ul.commas") as Element) - .children, - ).map( - (t) => this.tags.push(t.children[0].innerText), - ); - Array.from( - (this.#document.querySelector("dd.character > ul.commas") as Element) - .children, - ).map( - (t) => this.tags.push(t.children[0].innerText), - ); - Array.from( - (this.#document.querySelector("dd.freeform > ul.commas") as Element) - .children, - ).map( - (t) => this.tags.push(t.children[0].innerText), - ); - } + /** + * represents a work on AO3 + * @param id the ID of the work + * @param body the HTML body of the work (in text) + * @param session an axiod session (used for fetching additional details) + */ + constructor( + id: ID, + body: string, + session: { + get: (path: string) => Promise; + }, + DOMParser: DOMParser, + ) { + this.#session = session; + this.id = id; + this.#document = DOMParser.parseFromString( + body, + "text/html", + ) as HTMLDocument; + this.#DOMParser = DOMParser; + } - populateDates() { - this.published = new Date( - this.#document.querySelector("dd.published")?.innerText as string, - ); - this.updated = new Date( - this.#document.querySelector("dd.status")?.innerText as string, - ); - } + //jank incarnate + async init() { + this.populateTags(); + this.populateDates(); + await this.populateChapters(); + } - //CW: horrifying jank - async populateChapters() { - const firstChapterUrl = - (this.#document.querySelector("li.chapter > a") as Element).getAttribute( - "href", - ) as string + "?view_adult=true"; - const res = await this.#session.get(firstChapterUrl); - const document = this.#DOMParser.parseFromString( - await res.text(), - "text/html", - ) as HTMLDocument; + populateTags() { + Array.from( + (this.#document.querySelector("dd.fandom > ul.commas") as Element) + .children, + ).map( + (t) => this.tags.push(t.children[0].innerText), + ); + Array.from( + (this.#document.querySelector( + "dd.relationship > ul.commas", + ) as Element) + .children, + ).map( + (t) => this.tags.push(t.children[0].innerText), + ); + Array.from( + (this.#document.querySelector( + "dd.character > ul.commas", + ) as Element) + .children, + ).map( + (t) => this.tags.push(t.children[0].innerText), + ); + Array.from( + (this.#document.querySelector("dd.freeform > ul.commas") as Element) + .children, + ).map( + (t) => this.tags.push(t.children[0].innerText), + ); + } - const promises: Promise[] = []; + populateDates() { + this.published = new Date( + this.#document.querySelector("dd.published")?.innerText as string, + ); + this.updated = new Date( + this.#document.querySelector("dd.status")?.innerText as string, + ); + } - Array.from((document.getElementById("selected_id") as Element).children) - .sort( - (a, b) => { - return Number(a.getAttribute("value")) - - Number(b.getAttribute("value")); - }, - ).forEach((c) => { - promises.push((async (c: Element) => { - const newChapter = new Chapter( - this.id, - c.getAttribute("value") as string, - this.#session, - this.#DOMParser, - ); - await 1; //shut up - this.chapters.push( - newChapter, - ); - })(c)); - }); + //CW: horrifying jank + async populateChapters() { + const firstChapterUrl = + (this.#document.querySelector("li.chapter > a") as Element) + .getAttribute( + "href", + ) as string + "?view_adult=true"; + const res = await this.#session.get(firstChapterUrl); + const document = this.#DOMParser.parseFromString( + await res.text(), + "text/html", + ) as HTMLDocument; - await Promise.all(promises); - } + Array.from((document.getElementById("selected_id") as Element).children) + .sort( + (a, b) => { + return Number(a.getAttribute("value")) - + Number(b.getAttribute("value")); + }, + ).forEach((c) => { + const newChapter = new Chapter( + this.id, + c.getAttribute("value") as string, + this.#session, + this.#DOMParser, + ); + this.chapters.push( + newChapter, + ); + }); + } } diff --git a/src/utils/asyncForeach.ts b/src/utils/asyncForeach.ts index 2a133b2..f5cf3e2 100644 --- a/src/utils/asyncForeach.ts +++ b/src/utils/asyncForeach.ts @@ -1,5 +1,5 @@ export default async function asyncForEach(array: any[], callback: Function) { - for (let index = 0; index < array.length; index++) { - await callback(array[index], index, array); - } + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } } diff --git a/test.ts b/test.ts index 47fa73c..0698da8 100644 --- a/test.ts +++ b/test.ts @@ -24,6 +24,7 @@ assert(await work.chapters[0].workID == "37522864"); assert(await work.chapters[0].name == "Part I"); assert((await work.chapters[0].text).length > 0); +/* work = await ao3.getWork("39612636"); await work.init(); @@ -40,4 +41,4 @@ assert( assert( (await work.chapters[22].summary) === "Fizz tries to make the best out of his comeback", -); +);.*/