From 37f137881d06904b8fa007fb822cdbabe26cdc1f Mon Sep 17 00:00:00 2001 From: Ruthenic Date: Sat, 24 Dec 2022 01:52:51 -0500 Subject: [PATCH] ADD ACCOUNT LOGIN!!!!!!!!! note: literally no error handling, slightly jank, BUT IT WORKS!!! right now this mostly just allows us to access restricted works, but in the future this lets us do WAY more things that normally require an account (reading/modifying bookmarks, maybe even posting works?) --- account-test.ts | 19 ++++++++++++ deno.lock | 56 ++++++++++++++++++++++++++++++++++++ src/classes/AO3.ts | 72 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 account-test.ts diff --git a/account-test.ts b/account-test.ts new file mode 100644 index 0000000..0496994 --- /dev/null +++ b/account-test.ts @@ -0,0 +1,19 @@ +import AO3 from "./src/classes/AO3.ts"; + +const ao3 = new AO3(); + +await ao3.authenticate( + Deno.env.get("AO3USERNAME") as string, + Deno.env.get("AO3PASSWORD") as string, +); + +const res = await ao3.search({ + fandoms: ["Murder Drones (Web Series)"], + limit: 10, +}); + +await res.update(1); + +res.results.forEach( + (v) => console.log(v.name), +); diff --git a/deno.lock b/deno.lock index cdd38fe..28362b2 100644 --- a/deno.lock +++ b/deno.lock @@ -41,6 +41,10 @@ "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/another_cookiejar@v5.0.1/cookie.ts": "2be7548d01a3a9df97deb187761a843a77fd824057478919abf1e1e89ae1eb2e", + "https://deno.land/x/another_cookiejar@v5.0.1/cookie_jar.ts": "e47d7b2c608bcd9600fd26825b600946f16ae167216cea71935049188d2fc6d1", + "https://deno.land/x/another_cookiejar@v5.0.1/fetch_wrapper.ts": "4fbca1e77383cf7da4703798e06a1129b21120f4d01c3a4c0801674dd7b6d53b", + "https://deno.land/x/another_cookiejar@v5.0.1/mod.ts": "eff949014965771f2cd447fe78625a1ad28b59333afa40640f02c0922534d89a", "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", @@ -79,5 +83,57 @@ "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" + }, + "npm": { + "specifiers": { "fetch-cookie": "fetch-cookie@2.1.0" }, + "packages": { + "fetch-cookie@2.1.0": { + "integrity": "sha512-39+cZRbWfbibmj22R2Jy6dmTbAWC+oqun1f1FzQaNurkPDUP4C38jpeZbiXCR88RKRVDp8UcDrbFXkNhN+NjYg==", + "dependencies": { + "set-cookie-parser": "set-cookie-parser@2.5.1", + "tough-cookie": "tough-cookie@4.1.2" + } + }, + "psl@1.9.0": { + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dependencies": {} + }, + "punycode@2.1.1": { + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dependencies": {} + }, + "querystringify@2.2.0": { + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dependencies": {} + }, + "requires-port@1.0.0": { + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dependencies": {} + }, + "set-cookie-parser@2.5.1": { + "integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==", + "dependencies": {} + }, + "tough-cookie@4.1.2": { + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dependencies": { + "psl": "psl@1.9.0", + "punycode": "punycode@2.1.1", + "universalify": "universalify@0.2.0", + "url-parse": "url-parse@1.5.10" + } + }, + "universalify@0.2.0": { + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dependencies": {} + }, + "url-parse@1.5.10": { + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "querystringify@2.2.0", + "requires-port": "requires-port@1.0.0" + } + } + } } } diff --git a/src/classes/AO3.ts b/src/classes/AO3.ts index b00017d..b09ccd4 100644 --- a/src/classes/AO3.ts +++ b/src/classes/AO3.ts @@ -3,13 +3,25 @@ import { ID } from "../types.d.ts"; import { DOMParser, } from "https://denopkg.dev/gh/Ruthenic/deno-dom@master/deno-dom-wasm.ts"; +import { HTMLDocument } from "../types.d.ts"; import Search, { SearchParameters } from "./Search.ts"; +import { + CookieJar, + wrapFetch, +} from "https://deno.land/x/another_cookiejar@v5.0.1/mod.ts"; export default class AO3 { session: { get: (path: string) => Promise; + post: ( + path: string, + payload: Record, + ) => Promise; }; DOMParser = new DOMParser(); + fetch: typeof fetch; + cookieJar: CookieJar; + #headers: Record; /** * a representation of AO3 in class form @@ -17,15 +29,40 @@ export default class AO3 { constructor(opts?: { url?: string; }) { + this.cookieJar = new CookieJar(); + this.fetch = wrapFetch({ cookieJar: this.cookieJar }); + this.#headers = { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; rv:108.0) Gecko/20100101 Firefox/108.0", + }; this.session = { get: async (path: string) => { - const res = await fetch( + const res = await this.fetch( opts?.url ?? "https://archiveofourown.org/" + path, { - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; rv:106.0) Gecko/20100101 Firefox/106.0", - }, + headers: this.#headers, + }, + ); + if (res.status > 300) { + console.log(res); + throw new Error("Failed request, probably rate-limited"); + } + return res; + }, + + post: async ( + path: string, + // deno-lint-ignore no-explicit-any + payload: any, + headers?: string, + ) => { + const res = await this.fetch( + opts?.url ?? "https://archiveofourown.org/" + path, + { + "credentials": "include", + headers: Object.assign(headers ?? {}, this.#headers), + method: "POST", + body: payload, }, ); if (res.status > 300) { @@ -48,6 +85,31 @@ export default class AO3 { return new Work(id, await res.text(), this.session, new DOMParser()); } + async authenticate(username_or_email: string, password: string) { + const loginPage = await this.session.get("/users/login"); + const document = this.DOMParser.parseFromString( + await loginPage.text(), + "text/html", + ) as HTMLDocument; + const authenticity_token = document.querySelector( + "input[name='authenticity_token']", + )?.getAttribute("value"); + if (authenticity_token) { + await this.session.post( + "/users/login", + new URLSearchParams({ + "user[login]": username_or_email, + "user[password]": password, + authenticity_token, + utf8: "✓", + commit: "Log In", + }), + ); + } else { + throw new Error("Failed to get authenticity token"); + } + } + search(opts: SearchParameters) { return new Search(opts, this.session, new DOMParser()); }