Add the Fediverse Info

This commit is contained in:
Taevas 2025-01-22 19:38:42 +01:00
parent 12e68cb2a7
commit 51091d6d59
7 changed files with 155 additions and 19 deletions

View file

@ -20,10 +20,11 @@ This package uses [`@carbon/icons-react`](https://github.com/carbon-design-syste
netlify env:set IBM_TELEMETRY_DISABLED true
```
This package makes use of several online APIs through Netlify in order to deliver the `Infos` that are available on the right side of the website for large screens, accessing most of these APIs requires a key (or similar), which can be set through the following environment variables:
This package makes use of several online APIs through Netlify in order to deliver the `Infos` that are available on the right side of the website, accessing most of these APIs requires a key (or similar), which can be set through the following environment variables:
- `API_GITHUB`
- `API_GITLAB`
- `API_KITSUCLUB`
- `API_LASTFM`
- `API_OSU`
- `API_WANIKANI`

BIN
bun.lockb

Binary file not shown.

View file

@ -0,0 +1,42 @@
import {type Handler} from "@netlify/functions";
import { KitsuclubInfo } from "../../src/components/Info/Fediverse/KitsuClub.js";
const handler: Handler = async () => {
const kitsuclub = await fetch("https://kitsunes.club/api/users/notes", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.API_KITSUCLUB}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"userId": "a2hgd7delf",
"limit": 1,
"withReplies": false,
"withRepliesToSelf": false,
"withQuotes": true,
"withRenotes": false,
"withBots": true,
"withNonPublic": true,
"withChannelNotes": false,
"withFiles": false,
"allowPartial": false,
})
});
const details = (await kitsuclub.json() as Record<string, any>)[0];
const activity: KitsuclubInfo = {
id: details.user.username,
username: details.user.name,
avatar: details.user.avatarUrl,
emojis: details.user.emojis,
text: details.text,
date: details.createdAt
}
return {
statusCode: 200,
body: JSON.stringify(activity),
};
};
export {handler};

View file

@ -7,34 +7,34 @@
"lint:fix": "bunx eslint . --fix"
},
"dependencies": {
"@carbon/icons-react": "^11.52.0",
"@carbon/icons-react": "^11.53.0",
"@netlify/functions": "^2.8.2",
"@octokit/rest": "^20.1.1",
"mongodb": "^6.10.0",
"mongodb": "^6.12.0",
"osu-api-v2-js": "^1.1.0",
"react": "^19.0.0-rc-fb9a90fa48-20240614",
"react-dom": "^19.0.0-rc-fb9a90fa48-20240614",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"timeago.js": "^4.0.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.13.0",
"@stylistic/eslint-plugin-ts": "^2.9.0",
"@tailwindcss/forms": "^0.5.9",
"@types/node": "^20.17.3",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@stylistic/eslint-plugin-ts": "^2.13.0",
"@tailwindcss/forms": "^0.5.10",
"@types/node": "^20.17.13",
"@types/react": "npm:types-react@beta",
"@types/react-dom": "npm:types-react-dom@beta",
"@vitejs/plugin-react": "^4.3.3",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.13.0",
"eslint": "^9.18.0",
"eslint-config-xo-typescript": "^7.0.0",
"eslint-plugin-react": "^7.37.2",
"postcss": "^8.4.47",
"eslint-plugin-react": "^7.37.4",
"postcss": "^8.5.1",
"react-animate-height": "^3.2.3",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"typescript-eslint": "^8.12.2",
"vite": "^5.4.10"
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0",
"vite": "^5.4.11"
},
"type": "module"
}

View file

@ -0,0 +1,16 @@
import React from "react";
import Info from "../Info.js";
import KitsuClub from "./Fediverse/KitsuClub.js";
export default function Hacking() {
const kitsuclub = <KitsuClub key={"kitsuclub"}/>;
return (
<Info
type="Fediverse"
websites={[
kitsuclub,
]}
/>
);
}

View file

@ -0,0 +1,75 @@
import React, {useState, useEffect} from "react";
import Website from "../../Website.js";
export type KitsuclubInfo = {
id: string
username: string
avatar: string
emojis: Record<string, string>
text: string
date: string
} | undefined;
export default function KitsuClub() {
const [kitsuclub, setKitsuclub]: [KitsuclubInfo, React.Dispatch<React.SetStateAction<KitsuclubInfo>>] = useState();
const [elements, setElements] = useState([] as React.JSX.Element[]);
const [error, setError] = useState(false);
const getKitsuclub = async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
setKitsuclub(await fetch("/.netlify/functions/kitsuclub").then(async r => r.json()));
};
useEffect(() => {
getKitsuclub().catch(() => {
setError(true);
});
}, []);
useEffect(() => {
if (kitsuclub) {
const date = new Date(kitsuclub.date).toISOString()
try {
setElements([
<div key={"kitsuclub-details"} className="text-left mb-2">
<img key={"kitsuclub-avatar"} src={kitsuclub.avatar} alt="avatar" className="float-left rounded-lg w-12 mr-2"/>
<strong key={"kitsuclub-username"} className="inline-flex">{...emojify(kitsuclub.username, kitsuclub.emojis)}</strong>
<br/>
<strong key={"kitsuclub-date"} className="inline-flex text-sm">{date.substring(0, date.indexOf("T"))}</strong>
</div>,
<p key={"kitsuclub-text"} className="text-left">{...emojify(kitsuclub.text, kitsuclub.emojis)}</p>, // emojis that are only in the post aren't in the response yet :(
])
} catch {
setError(true)
}
}
}, [kitsuclub]);
return (
<Website
name="KitsuClub"
link={`https://kitsunes.club/@${kitsuclub?.id ?? "taevas"}`}
elements={elements}
error={error}
/>
);
}
function emojify(text: string, all_emojis: Record<string, string>): Array<string | React.JSX.Element> {
const emoji_list: string[] = Object.keys(all_emojis)
const emoji_imgs: string[] = Object.values(all_emojis)
const to_return: Array<string | React.JSX.Element> = []
for (let i = 0; i < emoji_list.length; i++) {
const emoji_name = emoji_list[i]
while (text.indexOf(emoji_name) !== -1) {
const index = text.indexOf(emoji_name)
to_return.push(text.substring(0, index - 1)) // push whatever text before the emoji
to_return.push(<img src={emoji_imgs[i]} alt={emoji_name} className="h-6 w-6 mx-1"/>) // push the emoji
text = text.substring(index + emoji_name.length + 1) // remove whatever text before the emoji and the emoji name
}
}
to_return.push(text) // push whatever text AFTER the last emoji (so all the text if no emoji)
return to_return
}

View file

@ -6,6 +6,7 @@ import Coding from "../components/Info/Coding.js";
import RhythmGames from "../components/Info/RhythmGames.js";
import Anime from "../components/Info/Anime.js";
// import Japanese from "../components/Info/Japanese.js";
import Fediverse from "../components/Info/Fediverse.js"
export default class Infos extends Component {
private readonly dragbar = React.createRef<HTMLDivElement>();
@ -17,10 +18,11 @@ export default class Infos extends Component {
<div draggable="false" ref={this.dragbar} className="z-[100] h-full w-[25px] fixed right-[7px] lg:right-[340px] cursor-ew-resize select-none hover:bg-gradient-to-r from-white/80 to-white/1 active:to-white/20"></div>
<div className="z-[90] p-2.5 flex flex-wrap text-white">
<Music/>
<Fediverse/>
<Coding/>
<Speedrun/>
<Anime/>
{/* <Japanese/> */}
{/*<Japanese/>*/}
<RhythmGames/>
<Hacking/>
</div>