このコミットが含まれているのは:
2026-06-09 08:17:16 +09:00
コミット be5359eb84
8個のファイルの変更339行の追加51行の削除
+74 -23
ファイルの表示
@@ -1,5 +1,4 @@
import { apiPost } from '@/lib/api'
import { fetchPosts } from '@/lib/posts'
import { apiGet, apiPost } from '@/lib/api'
import type { Post } from '@/types'
@@ -13,6 +12,7 @@ export type GekanatorAnswerValue =
export type GekanatorAnswerLog = {
questionId: string
questionText: string
questionCondition?: GekanatorQuestionCondition
answer: GekanatorAnswerValue
originalAnswer: GekanatorAnswerValue }
@@ -22,10 +22,26 @@ export type GekanatorQuestionKind =
| 'title'
| 'original_date'
export type GekanatorQuestionCondition =
| { type: 'tag'; key: string }
| { type: 'source'; host: string }
| { type: 'original-year'; year: number }
| { type: 'original-month'; month: number }
| { type: 'original-month-day'; monthDay: string }
| { type: 'title-length-greater-than'; length: number }
| { type: 'title-has-ascii' }
export type StoredGekanatorQuestion = {
id: string
text: string
kind: GekanatorQuestionKind
condition: GekanatorQuestionCondition }
export type GekanatorQuestion = {
id: string
text: string
kind: GekanatorQuestionKind
condition: GekanatorQuestionCondition
test: (post: Post) => boolean }
const countBy = <T extends string | number> (values: T[]): Map<T, number> => {
@@ -144,27 +160,49 @@ const questionableTag = (post: Post, key: string): boolean => {
}
const questionMatches = (
post: Post,
condition: GekanatorQuestionCondition,
): boolean => {
switch (condition.type)
{
case 'tag':
return questionableTag (post, condition.key)
case 'source':
return hostOf (post) === condition.host
case 'original-year':
return originalYearOf (post) === condition.year
case 'original-month':
return originalMonthOf (post) === condition.month
case 'original-month-day':
return originalMonthDayOf (post) === condition.monthDay
case 'title-length-greater-than':
return (post.title?.length ?? 0) > condition.length
case 'title-has-ascii':
return /[A-Za-z0-9]/.test (post.title ?? '')
}
}
export const restoreGekanatorQuestion = (
question: StoredGekanatorQuestion,
): GekanatorQuestion => ({
...question,
test: (post: Post) => questionMatches (post, question.condition) })
export const storeGekanatorQuestion = (
question: GekanatorQuestion,
): StoredGekanatorQuestion => ({
id: question.id,
text: question.text,
kind: question.kind,
condition: question.condition })
export const fetchGekanatorPosts = async (): Promise<Post[]> => {
const limit = 200
const first = await fetchPosts ({
url: '', title: '', tags: '', match: 'all',
originalCreatedFrom: '', originalCreatedTo: '',
createdFrom: '', createdTo: '', updatedFrom: '', updatedTo: '',
page: 1, limit, order: 'original_created_at:desc' })
const posts = [...first.posts]
const totalPages = Math.ceil (first.count / limit)
for (let page = 2; page <= totalPages; page++)
{
const data = await fetchPosts ({
url: '', title: '', tags: '', match: 'all',
originalCreatedFrom: '', originalCreatedTo: '',
createdFrom: '', createdTo: '', updatedFrom: '', updatedTo: '',
page, limit, order: 'original_created_at:desc' })
posts.push (...data.posts)
}
return posts
const data = await apiGet<{ posts: Post[] }> ('/gekanator/posts')
return data.posts
}
@@ -209,6 +247,7 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
id: `tag:${ key }`,
text: tagQuestionText (category, label),
kind: 'tag' as const,
condition: { type: 'tag' as const, key: String (key) },
test: (post: Post) => questionableTag (post, String (key)) }
})
@@ -219,6 +258,7 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
id: `source:${ host }`,
text: `${ host } の投稿を思ひ浮かべてゐる?`,
kind: 'source' as const,
condition: { type: 'source' as const, host },
test: (post: Post) => hostOf (post) === host }))
const originalYearQuestions = usefulEntries (originalYears)
@@ -228,6 +268,7 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
id: `original-year:${ year }`,
text: `オリジナルの投稿年は ${ year } 年?`,
kind: 'original_date' as const,
condition: { type: 'original-year' as const, year },
test: (post: Post) => originalYearOf (post) === year }))
const originalMonthQuestions = usefulEntries (originalMonths)
@@ -237,6 +278,7 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
id: `original-month:${ month }`,
text: `オリジナルの投稿月は ${ month } 月?`,
kind: 'original_date' as const,
condition: { type: 'original-month' as const, month },
test: (post: Post) => originalMonthOf (post) === month }))
const originalMonthDayQuestions = usefulEntries (originalMonthDays)
@@ -249,6 +291,7 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
id: `original-month-day:${ monthDay }`,
text: `オリジナルの投稿日は ${ month }${ day } 日?`,
kind: 'original_date' as const,
condition: { type: 'original-month-day' as const, monthDay: String (monthDay) },
test: (post: Post) => originalMonthDayOf (post) === monthDay }
})
@@ -257,11 +300,15 @@ export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
id: 'title:long',
text: '題名が長めの投稿?',
kind: 'title' as const,
condition: {
type: 'title-length-greater-than' as const,
length: titleLengthMedian },
test: (post: Post) => (post.title?.length ?? 0) > titleLengthMedian },
{
id: 'title:ascii',
text: '題名に英数字が混じってゐる?',
kind: 'title' as const,
condition: { type: 'title-has-ascii' as const },
test: (post: Post) => /[A-Za-z0-9]/.test (post.title ?? '') }]
.filter (question => {
const yes = posts.filter (post => question.test (post)).length
@@ -294,6 +341,7 @@ export const saveGekanatorGame = async ({
answers: answers.map (answer => ({
question_id: answer.questionId,
question_text: answer.questionText,
question_condition: answer.questionCondition ?? null,
answer: answer.answer,
original_answer: answer.originalAnswer })) })
@@ -301,10 +349,13 @@ export const saveGekanatorGame = async ({
export const saveGekanatorQuestionSuggestion = async ({
gekanatorGameId,
questionText,
answer,
}: {
gekanatorGameId: number
questionText: string
answer: GekanatorAnswerValue
}): Promise<{ id: number }> =>
await apiPost ('/gekanator/question_suggestions', {
gekanator_game_id: gekanatorGameId,
question_text: questionText })
question_text: questionText,
answer })