1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
import { readFile } from "fs/promises";
import { z } from "zod";
const zodType = z.array(
z.strictObject({
animeId: z.number().int().min(0),
title: z.string(),
primaryCategory: z.string(),
startSeason: z.string(),
titleReviewScore: z.object({
story: z.number().min(0).nullable(),
sakuga: z.number().min(0).nullable(),
character: z.number().min(0).nullable(),
music: z.number().min(0).nullable(),
originality: z.number().min(0).nullable(),
storyboard: z.number().min(0).nullable(),
voice: z.number().min(0).nullable(),
song: z.number().min(0).nullable(),
manzokudo: z.number().min(0).nullable(),
}),
titleReviewList: z.array(
z.object({
reviewId: z.number().int(),
userIconUrl: z.string().url(),
userName: z.string(),
score: z.number(),
isSpoiler: z.boolean(),
timestampSec: z.number().int().min(0),
commentCount: z.number().int().min(0),
iineCount: z.number().int().min(0)
}),
),
titleHitokotoList: z.array(
z.object({
hitokotoId: z.number().int(),
userIcon: z.string().url(),
userName: z.string(),
reviewHtml: z.string(),
timestampSec: z.number().int(),
})
),
episodeList: z.array(z.object({
episodeId: z.number().int().min(0),
subTitle: z.string(),
reviewCount: z.number().int().min(0),
score: z.number().nullable(),
})),
episodeHitokotoList: z.array(z.object({
episodeId: z.number().int().min(0),
hitokotoList: z.array(z.object({
hitokotoId: z.number().int().min(0),
userIcon: z.string(),
userName: z.string().min(1),
reviewHtml: z.string().min(1),
timestampSec: z.number().int().min(0),
}))
})),
})
);
const MAX_ITEM_LIMIT = process.env["AKIBA_SOUKEN_MAX_ITEM_LIMIT"];
export type AnimeLoaderData = z.infer<typeof zodType>[number]
class AnimeLoader {
constructor() { }
private dataCache: Awaited<ReturnType<AnimeLoader["loadData_"]>> | null = null;
async loadData() {
if (this.dataCache != null) {
return this.dataCache;
}
const loadedData = await this.loadData_();
this.dataCache = loadedData;
return loadedData;
}
private async loadData_() {
const articleJsonPath = process.env["AKIBA_SOUKEN_ANIME_JSON"]!;
//console.log(`[${articleJsonPath}]`);
const jsonStr = await readFile(articleJsonPath, { encoding: "utf-8" }).then(text => {
const jsonObj = JSON.parse(text);
return jsonObj;
});
const parsedObj = zodType.parse(jsonStr).filter(a => {
if (a.titleHitokotoList.length != 0) { return true; }
if (a.titleReviewList.length != 0) { return true; }
if (a.titleReviewScore.manzokudo != null) { return true; }
if (a.episodeList.find(e => e.score != null || e.reviewCount != 0)) { return true; }
if (a.episodeHitokotoList.length != 0) { return true; }
return false;
});
if (MAX_ITEM_LIMIT != null) {
parsedObj.length = Math.min(Number(MAX_ITEM_LIMIT), parsedObj.length);
}
return parsedObj;
}
}
export const animeLoader = new AnimeLoader();
|