|
|
|
@ -1,4 +1,5 @@
|
|
|
|
|
import { isoDateStr } from "../util";
|
|
|
|
|
import { writeFile } from "node:fs/promises";
|
|
|
|
|
|
|
|
|
|
type LogBase = {
|
|
|
|
|
name: string;
|
|
|
|
@ -34,7 +35,14 @@ const byStatusSorted = (items: LogBase[]) => {
|
|
|
|
|
inProgress.sort((a, b) => +(a.started ?? 0) - +(b.started ?? 0));
|
|
|
|
|
|
|
|
|
|
const finished = items.filter((i) => status(i) == "finished");
|
|
|
|
|
finished.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0));
|
|
|
|
|
finished.sort((a, b) => {
|
|
|
|
|
const r = (b.rating ?? 0) - (a.rating ?? 0);
|
|
|
|
|
if (r == 0) {
|
|
|
|
|
return a.name.localeCompare(b.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const wantTo = items.filter((i) => status(i) == "wantTo");
|
|
|
|
|
wantTo.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
@ -47,6 +55,28 @@ const byStatusSorted = (items: LogBase[]) => {
|
|
|
|
|
|
|
|
|
|
type Show = LogBase & { season?: number };
|
|
|
|
|
const SHOWS: Show[] = [
|
|
|
|
|
{
|
|
|
|
|
name: "The Bear",
|
|
|
|
|
rating: 8,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "The Witcher",
|
|
|
|
|
rating: 8,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Arcane",
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Over the Garden Wall",
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Castlevania",
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
const ANIME: Show[] = [
|
|
|
|
|
{
|
|
|
|
|
name: "Kakegurui",
|
|
|
|
|
},
|
|
|
|
@ -63,7 +93,7 @@ const SHOWS: Show[] = [
|
|
|
|
|
name: "The Eminence in Shadow",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Bluelock",
|
|
|
|
|
name: "Blue Lock",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Violet Evergarden",
|
|
|
|
@ -88,97 +118,166 @@ const SHOWS: Show[] = [
|
|
|
|
|
{
|
|
|
|
|
name: "Bocchi the Rock!",
|
|
|
|
|
started: new Date("2023-01-12"),
|
|
|
|
|
season: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Blue Period",
|
|
|
|
|
started: new Date("2023-01-02"),
|
|
|
|
|
finished: new Date("2023-01-12"),
|
|
|
|
|
season: 1,
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Chainsaw Man",
|
|
|
|
|
started: new Date("2022-12-01"),
|
|
|
|
|
finished: new Date("2023-01-04"),
|
|
|
|
|
season: 1,
|
|
|
|
|
rating: 7.5,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Do it Yourself!!",
|
|
|
|
|
started: new Date("2022-12-01"),
|
|
|
|
|
season: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Cyberpunk: Edgerunners",
|
|
|
|
|
season: 1,
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "The Bear",
|
|
|
|
|
rating: 8,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "The Witcher",
|
|
|
|
|
rating: 8,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Arcane",
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Castlevania",
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Over the Garden Wall",
|
|
|
|
|
rating: 10,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
const renderAnime = () => {
|
|
|
|
|
const renderSection = (title: string, items: Show[]) => {
|
|
|
|
|
let out = `<section><h3>${title}</h3>`;
|
|
|
|
|
for (const a of items) {
|
|
|
|
|
out += `<div class="log anime"><h4>${a.name}`;
|
|
|
|
|
if (a.season) {
|
|
|
|
|
out += `, Season ${a.season}`;
|
|
|
|
|
|
|
|
|
|
const cacheAnimeArt = async (a: Pick<Show, "name">) => {
|
|
|
|
|
const query = `
|
|
|
|
|
query($name: String) {
|
|
|
|
|
Media(
|
|
|
|
|
search: $name,
|
|
|
|
|
) {
|
|
|
|
|
coverImage {
|
|
|
|
|
extraLarge
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const variables = {
|
|
|
|
|
name: a.name,
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const req = {
|
|
|
|
|
query,
|
|
|
|
|
variables,
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const f = await fetch("https://graphql.anilist.co/", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
Accept: "application/json",
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(req),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!f.ok) {
|
|
|
|
|
console.warn(`Failed to query anilist for ${a.name}`);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resp = (await f.json()) as
|
|
|
|
|
| {
|
|
|
|
|
data: {
|
|
|
|
|
Media: {
|
|
|
|
|
coverImage: {
|
|
|
|
|
extraLarge: string;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
errors: unknown;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if ("errors" in resp) {
|
|
|
|
|
console.warn(`Invalid anilist query for ${a.name}`);
|
|
|
|
|
console.warn(resp);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const url = resp.data.Media.coverImage.extraLarge;
|
|
|
|
|
const f_ = await fetch(url);
|
|
|
|
|
if (!f.ok) {
|
|
|
|
|
console.warn(`Failed to fetch image for ${a.name}`);
|
|
|
|
|
console.warn(resp);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resUrl = `/static/images/anime/${a.name
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
.replaceAll(" ", "_")}.png`;
|
|
|
|
|
|
|
|
|
|
// IMPROVEMENT: can probably finagle writing directly in to a writable
|
|
|
|
|
// file stream somehow
|
|
|
|
|
await writeFile(`build${resUrl}`, await f_.arrayBuffer());
|
|
|
|
|
|
|
|
|
|
console.log(`Successfully fetched image for ${a.name}`);
|
|
|
|
|
|
|
|
|
|
return resUrl;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// IMPROVEMENT: separate out the async here
|
|
|
|
|
// IMPROVEMENT: keep anime and TV shows separate?
|
|
|
|
|
const renderShows = async () => {
|
|
|
|
|
const shows = [...SHOWS, ...ANIME];
|
|
|
|
|
const animeTitles = new Set(ANIME.map((a) => a.name));
|
|
|
|
|
const { wantTo, dropped, finished, inProgress } = byStatusSorted(shows);
|
|
|
|
|
|
|
|
|
|
const renderSection = async (title: string, items: Show[]) => {
|
|
|
|
|
let out = `<section>
|
|
|
|
|
<h3>${title}</h3>
|
|
|
|
|
<div class="full-bleed">
|
|
|
|
|
<div class="logs">`;
|
|
|
|
|
const sections = items.map(async (a) => {
|
|
|
|
|
let out_ = "";
|
|
|
|
|
|
|
|
|
|
out_ += `<div class="log anime">`;
|
|
|
|
|
if (animeTitles.has(a.name)) {
|
|
|
|
|
const picUrl = await cacheAnimeArt(a);
|
|
|
|
|
if (picUrl) {
|
|
|
|
|
out_ += `<img src="${picUrl}">`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out += "</h4>";
|
|
|
|
|
|
|
|
|
|
out_ += `<div class="name">${a.name}</div>`;
|
|
|
|
|
|
|
|
|
|
if (a.started) {
|
|
|
|
|
out += `<div class="started">Started: ${isoDateStr(a.started)}</div>`;
|
|
|
|
|
out_ += `<div class="started">Started: ${isoDateStr(a.started)}</div>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (a.finished) {
|
|
|
|
|
out += `<div class="finished">Finished: ${isoDateStr(
|
|
|
|
|
out_ += `<div class="finished">Finished: ${isoDateStr(
|
|
|
|
|
a.finished,
|
|
|
|
|
)}</div>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (a.rating) {
|
|
|
|
|
out += `<div class="rating">Rating: ${a.rating} / 10</div>`;
|
|
|
|
|
out_ += `<div class="rating">Rating: ${a.rating} / 10</div>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out += "</div>";
|
|
|
|
|
}
|
|
|
|
|
out += "</section>";
|
|
|
|
|
out_ += "</div>";
|
|
|
|
|
|
|
|
|
|
return out_;
|
|
|
|
|
});
|
|
|
|
|
out += (await Promise.all(sections)).join("");
|
|
|
|
|
out += "</div></div></section>";
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const { wantTo, dropped, finished, inProgress } = byStatusSorted(SHOWS);
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
<section>
|
|
|
|
|
<h2>Anime / TV</h2>
|
|
|
|
|
${renderSection("Currently Watching", inProgress)}
|
|
|
|
|
${renderSection("Finished", finished)}
|
|
|
|
|
${renderSection("Dropped", dropped)}
|
|
|
|
|
${renderSection("Want to Watch", wantTo)}
|
|
|
|
|
${await renderSection("Currently Watching", inProgress)}
|
|
|
|
|
${await renderSection("Finished", finished)}
|
|
|
|
|
${await renderSection("Dropped", dropped)}
|
|
|
|
|
${await renderSection("Want to Watch", wantTo)}
|
|
|
|
|
</section>
|
|
|
|
|
`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const render = () => {
|
|
|
|
|
return renderAnime();
|
|
|
|
|
export const render = async () => {
|
|
|
|
|
return renderShows();
|
|
|
|
|
};
|
|
|
|
|