Compare commits

...

5 Commits

@ -1,4 +1,5 @@
{ {
"deno.enable": true, "deno.enable": true,
"deno.unstable": true, "deno.unstable": true,
"editor.formatOnSave": true
} }

@ -0,0 +1,77 @@
{
"version": "2",
"remote": {
"https://deno.land/std@0.122.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.122.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac",
"https://deno.land/std@0.122.0/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621",
"https://deno.land/std@0.122.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
"https://deno.land/std@0.122.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
"https://deno.land/std@0.122.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
"https://deno.land/std@0.122.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4",
"https://deno.land/std@0.122.0/path/glob.ts": "7bf2349e818e332a830f3d8874c3f45dd7580b6c742ed50dbf6282d84ab18405",
"https://deno.land/std@0.122.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
"https://deno.land/std@0.122.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2",
"https://deno.land/std@0.122.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
"https://deno.land/std@0.122.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e",
"https://deno.land/std@0.161.0/fmt/colors.ts": "9e36a716611dcd2e4865adea9c4bec916b5c60caad4cdcdc630d4974e6bb8bd4",
"https://deno.land/std@0.161.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c",
"https://deno.land/std@0.161.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832",
"https://deno.land/std@0.161.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8",
"https://deno.land/std@0.97.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.97.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7",
"https://deno.land/std@0.97.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e",
"https://deno.land/std@0.97.0/encoding/hex.ts": "f952e0727bddb3b2fd2e6889d104eacbd62e92091f540ebd6459317a61932d9b",
"https://deno.land/std@0.97.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b",
"https://deno.land/std@0.97.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7",
"https://deno.land/std@0.97.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00",
"https://deno.land/std@0.97.0/hash/_wasm/hash.ts": "cb6ad1ab429f8ac9d6eae48f3286e08236d662e1a2e5cfd681ba1c0f17375895",
"https://deno.land/std@0.97.0/hash/_wasm/wasm.js": "94b1b997ae6fb4e6d2156bcea8f79cfcd1e512a91252b08800a92071e5e84e1a",
"https://deno.land/std@0.97.0/hash/hasher.ts": "57a9ec05dd48a9eceed319ac53463d9873490feea3832d58679df6eec51c176b",
"https://deno.land/std@0.97.0/hash/mod.ts": "5d032bd34186cda2f8d17fc122d621430953a6030d4b3f11172004715e3e2441",
"https://deno.land/std@0.97.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
"https://deno.land/std@0.97.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
"https://deno.land/std@0.97.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
"https://deno.land/std@0.97.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a",
"https://deno.land/std@0.97.0/path/glob.ts": "314ad9ff263b895795208cdd4d5e35a44618ca3c6dd155e226fb15d065008652",
"https://deno.land/std@0.97.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
"https://deno.land/std@0.97.0/path/posix.ts": "f56c3c99feb47f30a40ce9d252ef6f00296fa7c0fcb6dd81211bdb3b8b99ca3b",
"https://deno.land/std@0.97.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
"https://deno.land/std@0.97.0/path/win32.ts": "77f7b3604e0de40f3a7c698e8a79e7f601dc187035a1c21cb1e596666ce112f8",
"https://deno.land/x/axiod@0.26.2/helpers.ts": "467f2ca75608f368c8092e800eab0d6d79b3fa42346372be62a5b93acf50fe7e",
"https://deno.land/x/axiod@0.26.2/interfaces.ts": "1b5a7b70b1e7faecd2f251ad080bd87188fba585e2de667af472c4d72abf636e",
"https://deno.land/x/axiod@0.26.2/mod.ts": "1175ec90a040d764b9940753f8d8e3f37a2328a0536eed48e411a8f1a3e9e5cb",
"https://deno.land/x/axiod@0.26.2/mods/url-join.ts": "c925b2dd0bd490093d084bd4c74a9502260e014d5e760a4951b3e7c8ab3ca43f",
"https://deno.land/x/cache@0.2.13/cache.ts": "4005aad54fb9aac9ff02526ffa798032e57f2d7966905fdeb7949263b1c95f2f",
"https://deno.land/x/cache@0.2.13/deps.ts": "6f14e76a1a09f329e3f3830c6e72bd10b53a89a75769d5ea886e5d8603e503e6",
"https://deno.land/x/cache@0.2.13/directories.ts": "ef48531cab3f827252e248596d15cede0de179a2fb15392ae24cf8034519994f",
"https://deno.land/x/cache@0.2.13/file.ts": "5abe7d80c6ac594c98e66eb4262962139f48cd9c49dbe2a77e9608760508a09a",
"https://deno.land/x/cache@0.2.13/file_fetcher.ts": "5c793cc83a5b9377679ec313b2a2321e51bf7ed15380fa82d387f1cdef3b924f",
"https://deno.land/x/cache@0.2.13/helpers.ts": "d1545d6432277b7a0b5ea254d1c51d572b6452a8eadd9faa7ad9c5586a1725c4",
"https://deno.land/x/cache@0.2.13/mod.ts": "3188250d3a013ef6c9eb060e5284cf729083af7944a29e60bb3d8597dd20ebcd",
"https://deno.land/x/plug@0.5.2/deps.ts": "0f53866f60dc4f89bbc3be9d096f7501f2068a51923db220f43f0ad284b6b892",
"https://deno.land/x/plug@0.5.2/mod.ts": "ca0f207510399c27ed1d38b4c4a2d9389f1d69a0213d836fdd2418779b331251",
"https://deno.land/x/plug@0.5.2/plug.ts": "e495c772bc3b19eb30d820bb83f1b75f6047e3009e19d9a5d81dbe68ca642fd9",
"https://deno.land/x/url_join@1.0.0/mod.ts": "d3c7007e3ab15594e54d5b90dce623b3e274a726e0cdf57858e7ecab77a0166c",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/deno-dom-native.ts": "69e7714229512f47b2049a2c81e39830499ecb105e9741908a935a24f281618b",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/api.ts": "0ff5790f0a3eeecb4e00b7d8fbfa319b165962cf6d0182a65ba90f158d74f7d7",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/constructor-lock.ts": "59714df7e0571ec7bd338903b1f396202771a6d4d7f55a452936bd0de9deb186",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/deserialize.ts": "f4d34514ca00473ca428b69ad437ba345925744b5d791cb9552e2d7a0e7b0439",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/document-fragment.ts": "a40c6e18dd0efcf749a31552c1c9a6f7fa614452245e86ee38fc92ba0235e5ae",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/document.ts": "bcb96378097106d82e0d1a356496baea1b73f92dd7d492e6ed655016025665df",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/dom-parser.ts": "609097b426f8c2358f3e5d2bca55ed026cf26cdf86562e94130dfdb0f2537f92",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/element.ts": "312ae401081e6ce11cf62a854c0f78388e4be46579c1fdd9c1d118bc9c79db38",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/elements/html-template-element.ts": "19ad97c55222115e8daaca2788b9c98cc31a7f9d2547ed5bca0c56a4a12bfec8",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/html-collection.ts": "ae90197f5270c32074926ad6cf30ee07d274d44596c7e413c354880cebce8565",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/node-list.ts": "4c6e4b4585301d4147addaccd90cb5f5a80e8d6290a1ba7058c5e3dfea16e15d",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/node.ts": "3069e6fc93ac4111a136ed68199d76673339842b9751610ba06f111ba7dc10a7",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/selectors/custom-api.ts": "852696bd58e534bc41bd3be9e2250b60b67cd95fd28ed16b1deff1d548531a71",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/selectors/nwsapi-types.ts": "c43b36c36acc5d32caabaa54fda8c9d239b2b0fcbce9a28efb93c84aa1021698",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/selectors/nwsapi.js": "985d7d8fc1eabbb88946b47a1c44c1b2d4aa79ff23c21424219f1528fa27a2ff",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/selectors/selectors.ts": "83eab57be2290fb48e3130533448c93c6c61239f2a2f3b85f1917f80ca0fdc75",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/selectors/sizzle-types.ts": "78149e2502409989ce861ed636b813b059e16bc267bb543e7c2b26ef43e4798b",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/selectors/sizzle.js": "c3aed60c1045a106d8e546ac2f85cc82e65f62d9af2f8f515210b9212286682a",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/utils-types.ts": "96db30e3e4a75b194201bb9fa30988215da7f91b380fca6a5143e51ece2a8436",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/dom/utils.ts": "ecd889ba74f3ce282620d8ca1d4d5e0365e6cc86101d2352f3bbf936ae496e2c",
"https://denopkg.dev/gh/Ruthenic/deno-dom@master/src/parser.ts": "b65eb7e673fa7ca611de871de109655f0aa9fa35ddc1de73df1a5fc2baafc332"
}
}

@ -0,0 +1,12 @@
import AO3 from "./src/classes/AO3.ts";
const ao3 = new AO3();
const res = await ao3.search({
freeform: ["no beta read we die like sammy"],
limit: 1,
});
await res.update(1);
console.log(await res.results[0].chapters[0].text);

@ -3,6 +3,7 @@ import { ID } from "../types.d.ts";
import { import {
DOMParser, DOMParser,
} from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/deno-dom-native.ts"; } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/deno-dom-native.ts";
import Search, { SearchParameters } from "./Search.ts";
export default class AO3 { export default class AO3 {
session: { session: {
@ -23,6 +24,12 @@ export default class AO3 {
get: async (path: string) => { get: async (path: string) => {
const res = await fetch( const res = await fetch(
opts?.url ?? "https://archiveofourown.org/" + path, opts?.url ?? "https://archiveofourown.org/" + path,
{
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; rv:106.0) Gecko/20100101 Firefox/106.0",
},
},
); );
if (res.status > 300) { if (res.status > 300) {
console.log(res); console.log(res);
@ -43,4 +50,8 @@ export default class AO3 {
); );
return new Work(id, await res.text(), this.session, new DOMParser()); return new Work(id, await res.text(), this.session, new DOMParser());
} }
search(opts: SearchParameters) {
return new Search(opts, this.session, new DOMParser());
}
} }

@ -78,7 +78,7 @@ export default class Chapter {
this.populateMetadata(); this.populateMetadata();
this.populateSummary(); this.populateSummary();
this.populateNotes(); this.populateNotes();
this.populateText(); await this.populateText();
} }
populateMetadata() { populateMetadata() {
@ -104,7 +104,7 @@ export default class Chapter {
?.trim(); ?.trim();
} }
populateText() { async populateText() {
/*this.text = this.#document.querySelector("div.userstuff[role='article']")?.innerText.trim().replace(/Chapter Text\s+/, "") as string*/ /*this.text = this.#document.querySelector("div.userstuff[role='article']")?.innerText.trim().replace(/Chapter Text\s+/, "") as string*/
//"div.userstuff[role='article'] > p" //"div.userstuff[role='article'] > p"
Array.from( Array.from(
@ -113,7 +113,27 @@ export default class Chapter {
), ),
).forEach( ).forEach(
(t) => this.#text += (t as Element).innerText + "\n", (t) => this.#text += (t as Element).innerText + "\n",
console.log(this.#text),
); );
this.#text = this.#text.trim(); try {
this.#text = this.#text.trim();
} catch {
//assume single chapter work
const res = await this.#session.get(
`/works/${this.#workID}?view_adult=true`,
);
this.#document = this.#DOMParser.parseFromString(
await res.text(),
"text/html",
) as HTMLDocument;
Array.from(
this.#document.querySelectorAll(
"[role='article'] > div.userstuff > p",
),
).forEach(
(t) => this.#text += (t as Element).innerText + "\n",
console.log(this.#text),
);
}
} }
} }

@ -0,0 +1,118 @@
//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 {
/*
* the amount of works to fetch
*
*/
limit?: number;
any?: string;
title?: string;
author?: string;
single_chapter?: boolean;
word_count?: number;
language?: string;
fandoms?: string[];
freeform?: string[];
ratings?: number[];
warnings?: number[];
}
export default class Search {
#baseApiURL = "/works/search?commit=Search";
#opts: SearchParameters;
#session: {
get: (path: string) => Promise<Response>;
};
#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 ?? ""}
&${
this.#opts.freeform?.map((v) => `work_search[freeform_names]=${v}`)
.join("&")
}`;
return url;
}
constructor(opts: SearchParameters, session: {
get: (path: string) => Promise<Response>;
}, 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;
let i = 0;
const limit = this.#opts.limit ?? 20;
await asyncForEach(
Array.from(this.#document.querySelectorAll("[role='article']")),
async (e: Element) => {
if (i >= limit) {
return;
}
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);
i++;
},
);
}
}

@ -70,7 +70,7 @@ export default class Work {
populateTags() { populateTags() {
Array.from( Array.from(
(this.#document.querySelector("dd.fandom > ul.commas") as Element) (this.#document.querySelector("dd.fandom > ul.commas") as Element)
.children, ?.children ?? [],
).map( ).map(
(t) => this.tags.push(t.children[0].innerText), (t) => this.tags.push(t.children[0].innerText),
); );
@ -78,7 +78,7 @@ export default class Work {
(this.#document.querySelector( (this.#document.querySelector(
"dd.relationship > ul.commas", "dd.relationship > ul.commas",
) as Element) ) as Element)
.children, ?.children ?? [],
).map( ).map(
(t) => this.tags.push(t.children[0].innerText), (t) => this.tags.push(t.children[0].innerText),
); );
@ -86,13 +86,13 @@ export default class Work {
(this.#document.querySelector( (this.#document.querySelector(
"dd.character > ul.commas", "dd.character > ul.commas",
) as Element) ) as Element)
.children, ?.children ?? [],
).map( ).map(
(t) => this.tags.push(t.children[0].innerText), (t) => this.tags.push(t.children[0].innerText),
); );
Array.from( Array.from(
(this.#document.querySelector("dd.freeform > ul.commas") as Element) (this.#document.querySelector("dd.freeform > ul.commas") as Element)
.children, ?.children ?? [], //does that make me insane
).map( ).map(
(t) => this.tags.push(t.children[0].innerText), (t) => this.tags.push(t.children[0].innerText),
); );
@ -109,11 +109,26 @@ export default class Work {
//CW: horrifying jank //CW: horrifying jank
async populateChapters() { async populateChapters() {
const firstChapterUrl = let firstChapterUrl = ""; //satisfy the typescript gods
(this.#document.querySelector("li.chapter > a") as Element) try {
.getAttribute( firstChapterUrl =
"href", (this.#document.querySelector("li.chapter > a") as Element)
) as string + "?view_adult=true"; .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 res = await this.#session.get(firstChapterUrl);
const document = this.#DOMParser.parseFromString( const document = this.#DOMParser.parseFromString(
await res.text(), await res.text(),

@ -3,6 +3,15 @@ import { assert } from "https://deno.land/std@0.161.0/testing/asserts.ts";
const ao3 = new AO3(); const ao3 = new AO3();
/* const res = await ao3.search({
any: "hazbin hotel",
limit: 1,
});
await res.update(1);
console.log(await res.results[0].chapters[0].text); */
let work = await ao3.getWork("37522864"); let work = await ao3.getWork("37522864");
await work.init(); await work.init();
@ -24,21 +33,20 @@ assert(await work.chapters[0].workID == "37522864");
assert(await work.chapters[0].name == "Part I"); assert(await work.chapters[0].name == "Part I");
assert((await work.chapters[0].text).length > 0); assert((await work.chapters[0].text).length > 0);
/*
work = await ao3.getWork("39612636"); work = await ao3.getWork("39612636");
await work.init(); await work.init();
//NOTE: this is not to actually make sure we're at feature parity; this is just to track regressions //NOTE: this is not to actually make sure we're at feature parity; this is just to track regressions
assert( assert(
(await work.chapters[22].startNote) === (await work.chapters[22].startNote) ===
`TW: minor suicidal themes\n\nI'll admit, this chapter is a little... Choppy. I had an idea and I had to make do with the time I had. \n\nSmall note: I changed Diamonds description. Before she had black tattoos and white scars over her skin. Now its just all scarring.`, `TW: minor suicidal themes\n\nI'll admit, this chapter is a little... Choppy. I had an idea and I had to make do with the time I had. \n\nSmall note: I changed Diamonds description. Before she had black tattoos and white scars over her skin. Now its just all scarring.`,
); );
assert( assert(
(await work.chapters[22].endNote) === (await work.chapters[22].endNote) ===
"Sorry, no Ozzie + Fizz reunion yet. I promise the next chapter will be better tho", "Sorry, no Ozzie + Fizz reunion yet. I promise the next chapter will be better tho",
); );
assert( assert(
(await work.chapters[22].summary) === (await work.chapters[22].summary) ===
"Fizz tries to make the best out of his comeback", "Fizz tries to make the best out of his comeback",
);.*/ );

Loading…
Cancel
Save