From 8e681392158587c1aefe82908ad88bd75901ac85 Mon Sep 17 00:00:00 2001 From: Ruthenic Date: Fri, 11 Nov 2022 10:05:11 -0500 Subject: [PATCH] single chapter works function now apparently? --- .vscode/settings.json | 2 +- src/classes/AO3.ts | 5 +++ src/classes/Search.ts | 96 +++++++++++++++++++++++++++++++++++++++++++ src/classes/Work.ts | 25 ++++++++--- test.ts | 12 +++++- 5 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 src/classes/Search.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 4010776..be67fe4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { "deno.enable": true, - "deno.unstable": true, + "deno.unstable": true } diff --git a/src/classes/AO3.ts b/src/classes/AO3.ts index ce27177..749e33f 100644 --- a/src/classes/AO3.ts +++ b/src/classes/AO3.ts @@ -3,6 +3,7 @@ import { ID } from "../types.d.ts"; import { DOMParser, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/deno-dom-native.ts"; +import Search, { SearchParameters } from "./Search.ts"; export default class AO3 { session: { @@ -43,4 +44,8 @@ export default class AO3 { ); return new Work(id, await res.text(), this.session, new DOMParser()); } + + async search(opts: SearchParameters) { + return new Search(opts, this.session, new DOMParser()) + } } diff --git a/src/classes/Search.ts b/src/classes/Search.ts new file mode 100644 index 0000000..15b4694 --- /dev/null +++ b/src/classes/Search.ts @@ -0,0 +1,96 @@ +//NOTE: AO3's searching "api" is an absolute clusterfuck. i'm sorry. + +import Work from "./Work.ts"; +import asyncForEach from "../utils/asyncForeach.ts"; +import type { + DOMParser, +} from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/dom-parser.ts"; +import type { + Element, +} from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/element.ts"; +import type { + HTMLDocument, +} from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/document.ts"; + +export const Ratings = { + unrated: 9, + generalAudiences: 10, + teenAudiences: 11, + mature: 12, + explicit: 13, +}; + +export const Warnings = { + creatorChoseNotToUseArchiveWarnings: 14, + noArchiveWarningsApply: 16, + graphicDepictionsOfViolence: 17, + majorCharacterDeath: 18, + noncon: 19, + underage: 20, +}; + +export interface SearchParameters { + any?: string; + title?: string; + author?: string; + single_chapter?: boolean; + word_count?: number; + language?: string; + fandoms?: string[]; + ratings?: number[]; + warnings?: number[]; +} + +export default class Search { + #baseApiURL = "/works/search?commit=Search" + #opts: SearchParameters; + #session: { + get: (path: string) => Promise; + }; + #document!: HTMLDocument; + #DOMParser: DOMParser; + results: Work[] = []; + + #urlConstructor(pageNum = 1) { + let url = this.#baseApiURL + + url += `&page=${pageNum} + &work_search[query]=${this.#opts.any ?? ""} + &work_search[title]=${this.#opts.title ?? ""} + &work_search[creators]=${this.#opts.author ?? ""}` + + return url + } + + constructor(opts: SearchParameters, session: { + get: (path: string) => Promise; + }, DOMParser: DOMParser) { + this.#session = session; + this.#opts = opts; + this.#DOMParser = DOMParser + } + + async update(pageNum: number) { + this.results = [] + const url = this.#urlConstructor(pageNum) + + const res = await this.#session.get(url); + this.#document = this.#DOMParser.parseFromString( + await res.text(), + "text/html", + ) as HTMLDocument; + + await asyncForEach(Array.from(this.#document.querySelectorAll("[role='article']")), + async (e: Element) => { + const workId = e.id.replace("work_", "") + console.log(workId) + const res = await this.#session.get( + `/works/${workId}?view_adult=true&view_full_work=true`, + ); + const work = new Work(workId, await res.text(), this.#session, this.#DOMParser) + await work.init() + this.results.push(work) + } + ) + } +} diff --git a/src/classes/Work.ts b/src/classes/Work.ts index d0ae2fa..dfb3d68 100644 --- a/src/classes/Work.ts +++ b/src/classes/Work.ts @@ -109,11 +109,26 @@ export default class Work { //CW: horrifying jank async populateChapters() { - const firstChapterUrl = - (this.#document.querySelector("li.chapter > a") as Element) - .getAttribute( - "href", - ) as string + "?view_adult=true"; + let firstChapterUrl = "" //satisfy the typescript gods + try { + firstChapterUrl = + (this.#document.querySelector("li.chapter > a") as Element) + .getAttribute( + "href", + ) as string + "?view_adult=true"; + } catch { + //assume single chapter work + const newChapter = new Chapter( + this.id, + this.id, + this.#session, + this.#DOMParser, + ); + this.chapters.push( + newChapter, + ); + return + } const res = await this.#session.get(firstChapterUrl); const document = this.#DOMParser.parseFromString( await res.text(), diff --git a/test.ts b/test.ts index 0698da8..d929698 100644 --- a/test.ts +++ b/test.ts @@ -3,6 +3,15 @@ import { assert } from "https://deno.land/std@0.161.0/testing/asserts.ts"; const ao3 = new AO3(); +const res = await ao3.search({ + any: "hazbin hotel" +}) + +await res.update(1) + +console.log(res.results[0]) + +/* let work = await ao3.getWork("37522864"); await work.init(); @@ -24,7 +33,6 @@ 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(); @@ -41,4 +49,4 @@ assert( assert( (await work.chapters[22].summary) === "Fizz tries to make the best out of his comeback", -);.*/ +);*/