Skip to content

Commit

Permalink
feat: pronouns
Browse files Browse the repository at this point in the history
this implementation is very crude, and should be rewritten
  • Loading branch information
6lr61 committed Sep 13, 2024
1 parent e1d685d commit 61e4234
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/components/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BadgeList from "./BadgeList";
import ElapsedTime from "./ElapsedTime";
import MentionSegment from "./MentionSegment";
import ProfilePicture from "./ProfilePicture";
import Pronoun from "./Pronoun";
import Reply from "./Reply";
import TextSegment from "./TextSegment";
import TwitchEmote from "./TwitchEmote";
Expand Down Expand Up @@ -54,7 +55,8 @@ export default function Message({ message }: Props): React.ReactElement {
style={{ backgroundColor: colorToRgba(message.color) }}
>
<BadgeList badges={message.badges} />
<UserName message={message} />
<UserName message={message} />
<Pronoun login={message.chatter_user_login} />
<ElapsedTime startingDate={message.timestamp} />
</header>
{message.reply && <Reply message={message.reply} />}
Expand Down
12 changes: 12 additions & 0 deletions src/components/Pronoun.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { ReactElement } from "react";
import { usePronouns } from "../hooks/usePronouns";

export default function Pronoun({
login,
}: {
login: string;
}): ReactElement | undefined {
const pronoun = usePronouns(login);

return <p className="flex-1 font-normal text-sm">{pronoun}</p>;
}
2 changes: 1 addition & 1 deletion src/components/UserName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function UserName({
message: ChatMessage;
}): React.ReactElement {
return (
<p className="flex-1 overflow-hidden text-ellipsis">
<p className="overflow-hidden text-ellipsis">
<span className="font-bold">{message.chatter_user_name}</span>
{hasLocalizedName(message) && (
<span className="font-normal">({message.chatter_user_login})</span>
Expand Down
103 changes: 103 additions & 0 deletions src/hooks/usePronouns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { useEffect, useMemo, useState } from "react";

const PRONOUNS_URL = "https://api.pronouns.alejo.io/v1";

interface PronounDescription {
name: string;
subject: string;
object: string;
singular: boolean;
}

interface UserEntry {
channel_id: string;
channel_login: string;
pronoun_id: string;
alt_pronoun_id: null; // unused?
}

type PronounResponse = Record<PronounDescription["name"], PronounDescription>;

async function getPronouns(): Promise<PronounResponse> {
const response = await fetch(`${PRONOUNS_URL}/pronouns`);

if (!response.ok) {
throw new Error(
`getUser: Bad HTTP response: ${response.status.toString()} ${
response.statusText
}`
);
}

return (await response.json()) as PronounResponse;
}

async function getUser(login: string): Promise<UserEntry | null> {
const response = await fetch(`${PRONOUNS_URL}/users/${login}`);

if (response.status === 404) {
return null;
}

if (!response.ok) {
throw new Error(
`getUser: Bad HTTP response: ${response.status.toString()} ${
response.statusText
}`
);
}

return (await response.json()) as UserEntry;
}

function toString(description: PronounDescription): string {
return description.singular
? description.subject
: `${description.subject}/${description.object}`;
}

const descriptors = new Map<string, PronounDescription>();
const users = new Map<string, UserEntry | null>();

// FIXME DON'T DO THIS!
(function init() {
if (descriptors.size > 0) {
return;
}

getPronouns()
.then((pronouns) => {
Object.entries(pronouns).forEach(([pronoun, description]) =>
descriptors.set(pronoun, description)
);
})
.catch((error: unknown) => {
console.error("PronounProvider:", error);
});
})();

export function usePronouns(login: string): string | undefined {
const [pronoun, setPronoun] = useState<PronounDescription>();
const user = useMemo(() => users.get(login), [login]);

if (user) {
setPronoun(() => descriptors.get(user.pronoun_id));
}

useEffect(() => {
if (user || user === null) {
return;
}

void getUser(login).then((entry) => {
if (entry === null) {
users.set(login, null);
return;
}

setPronoun(() => descriptors.get(entry.pronoun_id));
});
}, [login, user]);

return pronoun && toString(pronoun);
}

0 comments on commit 61e4234

Please sign in to comment.