FujoWebDev / AO3.js

NodeJS API for scraping AO3 data
MIT License
15 stars 8 forks source link

Update WorkSummary type #24

Closed riazaia closed 2 years ago

riazaia commented 2 years ago

The changes are as follows:

riazaia commented 2 years ago

Thoughts:

essential-randomness commented 2 years ago

From the discord:

Using Array<type> notation

I saw a point brought up on Twitter that the Array notation is superior for legibility reasons for cases like this and I hate it, but also have to agree.

So we should use:

author: Array<
    | "Anonymous"
    | {
        username: string;
        pseud: string;
    }>;

Special treatment of Orphan account

I think it's worth doing (note that I don't remember exactly how orphan works):

const ORPHAN_ACCOUNT = { username: "orphan", pseud: "orphan" } as const;

// ...
author: Array<
    | "Anonymous"
    | typeof ORPHAN_ACCOUNT
    | {
        username: string;
        pseud: string;
    }>;

whenever we return an orphan account, we should make sure to return the ORPHAN_ACCOUNT object, so people can do stuff like:

if (account.author == ORPHAN_ACCOUNT) {
  // TODO: do something with orphan account
}

Special casing anonymous

something else we could do is make the executive decision of also doing:

const ANONYMOUS_ACCOUNT = { username: "anonymous", pseud: "anonymous" } as const;
const ORPHAN_ACCOUNT = { username: "orphan", pseud: "orphan" } as const;

author: Array<
    | typeof ANONYMOUS_ACCOUNT
    | typeof ORPHAN_ACCOUNT
    | {
        username: string;
        pseud: string;
    }>;

it's not 100% correct because anonymous account doesn't have a pseud, but also if we go the other direction people will have to always check that the account is not anonymous before accessing username or pseud, which is going to get annoying fast.

so, like, everything would have to be:

if (author == "Anonymous") {
  // special case
} else {
  // Without the previous check TS would yell at you here
  const { username, pseud } = author;
  // do stuff for all other users
}

vs

// You can just do this wherever you have an author without worrying about the Anonymous case
const { username, pseud } = author;

which you could argue is going to force people to special case for anonymous so they aren't caught by surprise by it, but also IDK, I kinda hate having to type narrow a lot.


We can discuss all this on discord then summarize our decisions here.

riazaia commented 2 years ago

Anonymous works

Works themselves are anonymized (by being put in an anonymous collection), not the authors. What happens when a work is added to a collection is that all the authors become hidden under a single string, "Anonymous". i.e. an anonymous work with 1 author will say Title by Anonymous, and so will an anonymous work with 3 authors.

The previously suggested typing implies returns like authors: ["Anonymous", "Anonymous", "Anonymous"] or ["Anonymous", {username: "cool_user", pseud: "cool_user"}] are possible when they're not.

There's also an AO3 user with the username of Anonymous, so username: Anonymous for anonymized works would be incorrect. Users can also create pseuds called Anonymous. e.g. Title by Anonymous (Username) would result in the object: {username: Username, pseud: Anonymous}.

In regards to the possibility of returning null or an empty array when a work is anonymous: it might be okay for experienced developers, but less experienced ones might not think to do something with an empty array.

There could probably be an isAnonymousWork utility method added.

Orphaned works

When you orphan your fanworks you have the option to have your pseuds remain or not. This means that while some works do have an author with the shape of {username: orphan_account, pseud: orphan_account}, others will have pseuds we should grab.

The only difference between in practice between a regular user's account and orphan_account is that the latter's profile can't be reached. An isOrphanAuthor or isOrphanAccount method should be added.

const isOrphan = (author: Author) => author.username == "orphan_account";

The latest iteration :)

interface Author {
  username: string;
  pseud: string;
}

interface WorkSummary {
  // ...
  authors: "Anonymous" | Author[]
}