From 1210e75ca17c1438b4a29858d2a90ff5a0379eb0 Mon Sep 17 00:00:00 2001 From: Ruthenic Date: Wed, 1 Feb 2023 21:47:00 -0500 Subject: [PATCH] Add `User` class + other things I forgor --- .vscode/settings.json | 6 ++++- README.md | 6 ++++- mod.ts | 3 ++- src/classes/Story.ts | 5 ++++ src/classes/User.ts | 61 ++++++++++++++++++++++++++++++++++++++++++ src/classes/Wattpad.ts | 9 +++++++ src/types.d.ts | 45 ++++++++++++++++++++++++++++++- tests/index.ts | 6 +++-- tests/search.ts | 8 +++--- tests/story.ts | 23 +++++++++++----- tests/user.ts | 33 +++++++++++++++++++++++ 11 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 src/classes/User.ts create mode 100644 tests/user.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 0eb830f..c8bd270 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { "deno.enable": true, "deno.unstable": true, - "editor.formatOnSave": true + "editor.formatOnSave": true, + "[typescript]": { + "editor.tabSize": 4, + "editor.defaultFormatter": "denoland.vscode-deno" + } } diff --git a/README.md b/README.md index 43ca3f4..35596c4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # wattpad-deno + early library for accessing Wattpad using Deno ## Q&A ### Why? + Why not ### Docs? -The API *should* be pretty self-explanitory, if anything through deno.land's built in docs generator \ No newline at end of file + +The API _should_ be pretty self-explanitory, if anything through deno.land's +built in docs generator diff --git a/mod.ts b/mod.ts index 8ce9275..f648624 100644 --- a/mod.ts +++ b/mod.ts @@ -4,5 +4,6 @@ export default Wattpad; import Chapter from "./src/classes/Chapter.ts"; import Search from "./src/classes/Search.ts"; import Story from "./src/classes/Story.ts"; +import User from "./src/classes/User.ts"; const VERSION = "v0.1.1"; -export { Chapter, Search, Story, VERSION, Wattpad }; +export { Chapter, Search, Story, User, VERSION, Wattpad }; diff --git a/src/classes/Story.ts b/src/classes/Story.ts index 92c1ef2..e081536 100644 --- a/src/classes/Story.ts +++ b/src/classes/Story.ts @@ -1,4 +1,5 @@ import Chapter from "./Chapter.ts"; +import User from "./User.ts"; import { Session, StoryJSON } from "../types.d.ts"; export default class Story { @@ -69,4 +70,8 @@ export default class Story { new Chapter(this.storyJSON.id, part, this.#session) ); } + + getAuthor() { + return new User(this.#session, this.storyJSON.user.name); + } } diff --git a/src/classes/User.ts b/src/classes/User.ts new file mode 100644 index 0000000..ca7c783 --- /dev/null +++ b/src/classes/User.ts @@ -0,0 +1,61 @@ +import Story from "./Story.ts"; +import { SearchResults, Session, UserJSON } from "../types.d.ts"; + +export default class User { + #session: Session; + #searchLimit: number; + username: string; + displayName?: string; + description?: string; + createDate?: Date; + userJSON!: UserJSON; + stories: Story[] = []; + + constructor(session: Session, username: string, limit: number = 20) { + this.#session = session; + this.username = username; + this.#searchLimit = limit; + } + + async init() { + this.userJSON = await (await this.#session.get( + `/api/v3/users/${this.username}`, + false, + { + params: new URLSearchParams({ + fields: + "username,description,avatar,name,email,genderCode,language,birthdate,verified,isPrivate,ambassador,is_staff,follower,following,backgroundUrl,votesReceived,numFollowing,numFollowers,createDate,followerRequest,website,facebook,twitter,followingRequest,numStoriesPublished,numLists,location,externalId,programs,showSocialNetwork,verified_email,has_accepted_latest_tos,email_reverification_status,highlight_colour,safety(isMuted,isBlocked),has_writer_subscription", + }), + }, + )) + .json(); + + this.displayName = this.userJSON.name; + this.description = this.userJSON.description; + this.createDate = new Date(this.userJSON.createDate); + } + + async updateStories(pageNum: number) { + const res: SearchResults = await (await this.#session.get( + `/v4/users/${this.username}/stories/published`, + false, + { + params: new URLSearchParams({ + fields: "stories(id)", + limit: this.#searchLimit.toString(), + offset: (pageNum * this.#searchLimit).toString(), + mature: "1", + }), + }, + )).json(); + + for (let i = 0; i < res.stories.length; i++) { + const story = new Story( + res.stories[i].id, + this.#session, + ); + + this.stories.push(story); + } + } +} diff --git a/src/classes/Wattpad.ts b/src/classes/Wattpad.ts index da360bf..ab51b61 100644 --- a/src/classes/Wattpad.ts +++ b/src/classes/Wattpad.ts @@ -1,5 +1,6 @@ import Story from "./Story.ts"; import Search, { SearchParameters as QuerySearchParams } from "./Search.ts"; +import User from "./User.ts"; import { newSession, Options } from "../utils/http.ts"; import { Session } from "../types.d.ts"; @@ -45,6 +46,14 @@ export default class Wattpad { return new Story(id, this.session); } + /** + * gets a User from a username + * @returns {Promise} a User class for the user + */ + getUser(username: string): User { + return new User(this.session, username); + } + search(opts: QuerySearchParams) { return new Search(opts, this.session); } diff --git a/src/types.d.ts b/src/types.d.ts index 210e0bf..1f37424 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -28,7 +28,6 @@ export interface StoryJSON { tagRankings: TagRanking[]; highlight_colour: string; promoted: boolean; - sponsor: any[]; isAdExempt: boolean; story_text_url: TextURL; isPaywalled: boolean; @@ -110,3 +109,47 @@ export interface SearchTag { id: string; name: string; } + +export interface UserJSON { + username: string; + avatar: string; + isPrivate: boolean; + backgroundUrl: string; + follower: boolean; + following: boolean; + followerRequest: string; + followingRequest: string; + safety: Safety; + name: string; + description: string; + genderCode: string; + language: number; + createDate: string; + location: string; + verified: boolean; + ambassador: boolean; + facebook: string; + twitter: string; + website: string; + votesReceived: number; + numStoriesPublished: number; + numFollowing: number; + numFollowers: number; + numLists: number; + verified_email: boolean; + is_staff: boolean; + highlight_colour: string; + programs: Programs; + externalId: string; + showSocialNetwork: boolean; +} + +export interface Programs { + wattpad_stars: boolean; + wattpad_circle: boolean; +} + +export interface Safety { + isMuted: boolean; + isBlocked: boolean; +} diff --git a/tests/index.ts b/tests/index.ts index c81f585..da81f4a 100644 --- a/tests/index.ts +++ b/tests/index.ts @@ -1,10 +1,12 @@ import Wattpad from "../src/classes/Wattpad.ts"; -import workTest from "./story.ts"; +import storyTest from "./story.ts"; import chaptersTest from "./chapter.ts"; import searchTest from "./search.ts"; +import userTest from "./user.ts"; const wattpad = new Wattpad(); -workTest(wattpad); +storyTest(wattpad); chaptersTest(wattpad); searchTest(wattpad); +userTest(wattpad); diff --git a/tests/search.ts b/tests/search.ts index 1bc051f..bc211d4 100644 --- a/tests/search.ts +++ b/tests/search.ts @@ -26,11 +26,11 @@ export default function test(watt: Wattpad) { await search.update(0); - assert(search.results.length == 30, "not enough search results"); + assert(search.results.length === 30, "not enough search results"); await search.update(1); assert( - search.results.length == 30, + search.results.length === 30, "could not find second page of results", ); }); @@ -59,11 +59,11 @@ export default function test(watt: Wattpad) { }); await search.update(0); - assert(search.results.length == 20, "not enough search results"); + assert(search.results.length === 20, "not enough search results"); await search.update(1); assert( - search.results.length == 20, + search.results.length === 20, "could not find second page of results", ); }); diff --git a/tests/story.ts b/tests/story.ts index 873ad91..5d1d85f 100644 --- a/tests/story.ts +++ b/tests/story.ts @@ -4,24 +4,33 @@ import { assert } from "https://deno.land/std@0.167.0/testing/asserts.ts"; export default function test(watt: Wattpad) { Deno.test("stories", async (test) => { - let work: Story; + let story: Story; await test.step("initialization", async () => { - work = watt.getStory("327425279"); - await work.init(); + story = watt.getStory("327425279"); + await story.init(); }); await test.step("tags", () => { - assert(work.tags.length > 0, "Failed to parse any tags"); + assert(story.tags.length > 0, "Failed to parse any tags"); assert( - work.tags.includes("bendyxreader"), + story.tags.includes("bendyxreader"), "Failed to get correct tags", ); }); await test.step("other metadata", () => { assert( - work.name === "Inky Desires [Bendy X Reader]", - "incorrect work name", + story.name === "Inky Desires [Bendy X Reader]", + "incorrect story name", + ); + }); + + await test.step("getting author displayname", async () => { + const author = story.getAuthor(); + await author.init(); + assert( + author.displayName === "DustyAngel47", + "incorrect author displayname", ); }); }); diff --git a/tests/user.ts b/tests/user.ts new file mode 100644 index 0000000..a6ef3d8 --- /dev/null +++ b/tests/user.ts @@ -0,0 +1,33 @@ +import Wattpad from "../mod.ts"; +import type { User } from "../mod.ts"; +import { assert } from "https://deno.land/std@0.167.0/testing/asserts.ts"; + +export default function test(watt: Wattpad) { + Deno.test("users", async (test) => { + let user: User; + await test.step("initialization", async () => { + user = watt.getUser("DustyAngel47"); + await user.init(); + }); + + await test.step("metadata", () => { + assert( + user.username === "DustyAngel47", + "incorrect username (somehow)", + ); + assert( + user.displayName === "DustyAngel47", + "incorrect display name", + ); + }); + + await test.step("stories", async () => { + await user.updateStories(0); + // FIXME: should be resistant to me publishing more than one fic + assert( + user.stories.find((story) => story.id === "327425279"), + "incorrect stories found", + ); + }); + }); +}