diff options
| author | Fushihara <1039534+fushihara@users.noreply.github.com> | 2024-09-23 15:19:25 +0900 |
|---|---|---|
| committer | Fushihara <1039534+fushihara@users.noreply.github.com> | 2024-09-23 15:19:25 +0900 |
| commit | ba0c8d73e585d7ff249dd90684254bc7fa025544 (patch) | |
| tree | 3b35be04e7bf11eb046246696d45fb8854f01f14 /src/util | |
| parent | 41eff0687f69539a514cfa3a28a68f89fc8c54fd (diff) | |
commit
Diffstat (limited to 'src/util')
| -rw-r--r-- | src/util/animeLoader.ts | 93 | ||||
| -rw-r--r-- | src/util/articleLoader.ts | 88 | ||||
| -rw-r--r-- | src/util/pagenation.ts | 91 |
3 files changed, 272 insertions, 0 deletions
diff --git a/src/util/animeLoader.ts b/src/util/animeLoader.ts new file mode 100644 index 0000000..120123b --- /dev/null +++ b/src/util/animeLoader.ts @@ -0,0 +1,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();
\ No newline at end of file diff --git a/src/util/articleLoader.ts b/src/util/articleLoader.ts new file mode 100644 index 0000000..787725a --- /dev/null +++ b/src/util/articleLoader.ts @@ -0,0 +1,88 @@ +import { readFile } from "fs/promises"; +import { z } from "zod"; +const zodType = z.array( + z.object({ + articleId: z.number().int().min(0), + title: z.string(), + timestampMs: z.number().int().min(0), + maxPageNumber: z.number().int().min(1), + tags: z.array(z.string()), + breadLinks: z.array(z.string()).min(1), + }) +); +const MAX_ITEM_LIMIT = process.env["AKIBA_SOUKEN_MAX_ITEM_LIMIT"]; +export class ArticleLoader { + constructor() { } + async loadData() { + const articleJsonPath = process.env["AKIBA_SOUKEN_ARTICLE_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); + parsedObj.sort((a, b) => { + return b.timestampMs - a.timestampMs; + }); + if (MAX_ITEM_LIMIT != null) { + parsedObj.length = Math.min(Number(MAX_ITEM_LIMIT), parsedObj.length); + } + return parsedObj; + } + async getCategoryList() { + const loadedData = await this.loadData(); + // key:カテゴリ名 , val:回数 + const categoryCount = new Map<string, number>(); + for (const a of loadedData) { + if (a.breadLinks.length == 0) { + continue; + } + const category = a.breadLinks[0]; + if (categoryCount.has(category)) { + categoryCount.set(category, categoryCount.get(category)! + 1); + } else { + categoryCount.set(category, 1); + } + } + const categoryList: { name: string, count: number }[] = []; + for (const [name, count] of categoryCount) { + categoryList.push({ name: name, count }); + } + categoryList.sort((a, b) => { + return b.count - a.count; + }); + return categoryList; + } + async getTagList() { + const loadedData = await this.loadData(); + // key:タグ名 , val:回数 + const tagCount = new Map<string, number>(); + for (const a of loadedData) { + const tags = new Set<string>(); + a.tags.forEach(t => { + tags.add(t); + }); + // パンくずリストの2件目以降はタグ扱いになっている + a.breadLinks.forEach((b, index) => { + if (index != 0) { + tags.add(b); + } + }) + for (const tag of tags) { + if (tagCount.has(tag)) { + tagCount.set(tag, tagCount.get(tag)! + 1); + } else { + tagCount.set(tag, 1); + } + } + } + const tagList: { name: string, count: number }[] = []; + for (const [name, count] of tagCount) { + tagList.push({ name: name, count }); + } + tagList.sort((a, b) => { + return b.count - a.count; + }); + return tagList; + } +} diff --git a/src/util/pagenation.ts b/src/util/pagenation.ts new file mode 100644 index 0000000..15ee01b --- /dev/null +++ b/src/util/pagenation.ts @@ -0,0 +1,91 @@ +export type PagenationProp = { + now: number, + max: number, + between: number, +} +export function createPagenation(prop: PagenationProp) { + if (prop.between < 0) { + throw new Error(`betweenは0以上にして下さい`); + } + if (prop.max < 0) { + throw new Error(`maxは0以上にして下さい`); + } + if (prop.now < 0) { + throw new Error(`nowは0以上にして下さい`); + } + if (prop.max < prop.now) { + throw new Error(`nowの値はmaxと同じか、より小さな値にして下さい. now:${prop.now} , max:${prop.max}`); + } + if (prop.max == 0) { + return []; + } + const MIN = 1; + // 左・中・右の3ブロックの変数を作成 + const blockLeft = MIN; + let blockMiddle = [ + ...Array.from({ length: prop.between }).map((_, index) => { + const i = prop.now - prop.between + index; + return i; + }), + prop.now, + ...Array.from({ length: prop.between }).map((_, index) => { + const i = prop.now + index + 1; + return i; + }), + ]; + blockMiddle = blockMiddle.filter(i => { + if (i <= blockLeft) { + return false; + } + if (prop.max <= i) { + return false; + } + return true; + }); + const blockRight = prop.max; + type A = { type: "back", key: string, link: number | null }; + type B = { type: "next", key: string, link: number | null }; + type C = { type: "sp", key: string, }; + type D = { type: "num", key: string, link: number | null, num: number } + // 結合を作成 + const pageIdList: (A | B | C | D)[] = []; + { + // 左戻る矢印 + if (prop.now == MIN) { + pageIdList.push({ type: "back", key: "back", link: null }); + } else { + pageIdList.push({ type: "back", key: "back", link: prop.now - 1 }); + } + // 最初のページ + pageIdList.push({ type: "num", key: `p-${blockLeft}`, link: blockLeft, num: blockLeft }); + let lastPageId = blockLeft; + // 左と中の間の…を入れるかどうか + if (0 < blockMiddle.length && lastPageId + 1 != blockMiddle[0]) { + pageIdList.push({ type: "sp", key: "sp-left" }); + } + blockMiddle.forEach(m => { + pageIdList.push({ type: "num", key: `p-${m}`, link: m, num: m }); + lastPageId = m; + }); + if (0 < blockMiddle.length && lastPageId + 1 != blockRight) { + // 最後のページ + pageIdList.push({ type: "sp", key: "sp-right" }); + } + if (lastPageId != blockRight) { + pageIdList.push({ type: "num", key: `p-${blockRight}`, link: blockRight, num: blockRight }); + lastPageId = blockRight; + } + // 右矢印 + if (prop.now == prop.max) { + pageIdList.push({ type: "next", key: "next", link: null }); + } else { + pageIdList.push({ type: "next", key: "next", link: prop.now + 1 }); + } + pageIdList.forEach(p => { + if ("link" in p && p.link == prop.now) { + p.link = null; + } + }) + } + return pageIdList +}
\ No newline at end of file |
