このコミットが含まれているのは:
+99
-11
@@ -21,6 +21,7 @@ export type GekanatorQuestionKind =
|
||||
| 'source'
|
||||
| 'title'
|
||||
| 'original_date'
|
||||
| 'post_similarity'
|
||||
|
||||
export type GekanatorQuestionSource =
|
||||
| 'default'
|
||||
@@ -36,6 +37,18 @@ export type GekanatorQuestionCondition =
|
||||
| { type: 'original-month-day'; monthDay: string }
|
||||
| { type: 'title-length-greater-than'; length: number }
|
||||
| { type: 'title-has-ascii' }
|
||||
| {
|
||||
type: 'post-similarity'
|
||||
postId: number
|
||||
answer: GekanatorAnswerValue
|
||||
threshold: number
|
||||
}
|
||||
|
||||
export type GekanatorExtraQuestion = {
|
||||
id: number
|
||||
text: string
|
||||
source: GekanatorQuestionSource
|
||||
priorityWeight: number }
|
||||
|
||||
export type StoredGekanatorQuestion = {
|
||||
id: string
|
||||
@@ -43,7 +56,8 @@ export type StoredGekanatorQuestion = {
|
||||
kind: GekanatorQuestionKind
|
||||
condition: GekanatorQuestionCondition
|
||||
source?: GekanatorQuestionSource
|
||||
priorityWeight?: number }
|
||||
priorityWeight?: number
|
||||
exampleAnswers?: Record<`${ number }`, GekanatorAnswerValue> }
|
||||
|
||||
export type GekanatorQuestion = {
|
||||
id: string
|
||||
@@ -52,8 +66,24 @@ export type GekanatorQuestion = {
|
||||
condition: GekanatorQuestionCondition
|
||||
source: GekanatorQuestionSource
|
||||
priorityWeight: number
|
||||
exampleAnswers?: Record<`${ number }`, GekanatorAnswerValue>
|
||||
test: (post: Post) => boolean }
|
||||
|
||||
|
||||
const directExampleAnswerFor = (
|
||||
question: StoredGekanatorQuestion,
|
||||
post: Post,
|
||||
): GekanatorAnswerValue | null => {
|
||||
const direct = question.exampleAnswers?.[String (post.id) as `${ number }`]
|
||||
if (direct)
|
||||
return direct
|
||||
|
||||
if (question.condition.type === 'post-similarity' && question.condition.postId === post.id)
|
||||
return question.condition.answer
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const countBy = <T extends string | number> (values: T[]): Map<T, number> => {
|
||||
const counts = new Map<T, number> ()
|
||||
values.forEach (value => counts.set (value, (counts.get (value) ?? 0) + 1))
|
||||
@@ -172,24 +202,59 @@ const questionableTag = (post: Post, key: string): boolean => {
|
||||
|
||||
const questionMatches = (
|
||||
post: Post,
|
||||
condition: GekanatorQuestionCondition,
|
||||
question: StoredGekanatorQuestion,
|
||||
): boolean => {
|
||||
switch (condition.type)
|
||||
const directAnswer = directExampleAnswerFor (question, post)
|
||||
if (directAnswer)
|
||||
return question.condition.type === 'post-similarity'
|
||||
? directAnswer === question.condition.answer
|
||||
: directAnswer === 'yes'
|
||||
|
||||
switch (question.condition.type)
|
||||
{
|
||||
case 'tag':
|
||||
return questionableTag (post, condition.key)
|
||||
return questionableTag (post, question.condition.key)
|
||||
case 'source':
|
||||
return hostOf (post) === condition.host
|
||||
return hostOf (post) === question.condition.host
|
||||
case 'original-year':
|
||||
return originalYearOf (post) === condition.year
|
||||
return originalYearOf (post) === question.condition.year
|
||||
case 'original-month':
|
||||
return originalMonthOf (post) === condition.month
|
||||
return originalMonthOf (post) === question.condition.month
|
||||
case 'original-month-day':
|
||||
return originalMonthDayOf (post) === condition.monthDay
|
||||
return originalMonthDayOf (post) === question.condition.monthDay
|
||||
case 'title-length-greater-than':
|
||||
return (post.title?.length ?? 0) > condition.length
|
||||
return (post.title?.length ?? 0) > question.condition.length
|
||||
case 'title-has-ascii':
|
||||
return /[A-Za-z0-9]/.test (post.title ?? '')
|
||||
case 'post-similarity':
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const expectedAnswerForQuestion = (
|
||||
question: StoredGekanatorQuestion | GekanatorQuestion | undefined,
|
||||
post: Post | null,
|
||||
): GekanatorAnswerValue | null => {
|
||||
if (!(question) || !(post))
|
||||
return null
|
||||
|
||||
const directAnswer = directExampleAnswerFor (question, post)
|
||||
if (directAnswer)
|
||||
return directAnswer
|
||||
|
||||
switch (question.condition.type)
|
||||
{
|
||||
case 'tag':
|
||||
case 'source':
|
||||
case 'original-year':
|
||||
case 'original-month':
|
||||
case 'original-month-day':
|
||||
case 'title-length-greater-than':
|
||||
case 'title-has-ascii':
|
||||
return questionMatches (post, question) ? 'yes' : 'no'
|
||||
case 'post-similarity':
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +265,7 @@ export const restoreGekanatorQuestion = (
|
||||
...question,
|
||||
source: question.source ?? 'default',
|
||||
priorityWeight: question.priorityWeight ?? 1,
|
||||
test: (post: Post) => questionMatches (post, question.condition) })
|
||||
test: (post: Post) => questionMatches (post, question) })
|
||||
|
||||
|
||||
export const storeGekanatorQuestion = (
|
||||
@@ -211,7 +276,8 @@ export const storeGekanatorQuestion = (
|
||||
kind: question.kind,
|
||||
condition: question.condition,
|
||||
source: question.source,
|
||||
priorityWeight: question.priorityWeight })
|
||||
priorityWeight: question.priorityWeight,
|
||||
exampleAnswers: question.exampleAnswers })
|
||||
|
||||
|
||||
export const fetchGekanatorPosts = async (): Promise<Post[]> => {
|
||||
@@ -226,6 +292,15 @@ export const fetchGekanatorQuestions = async (): Promise<StoredGekanatorQuestion
|
||||
}
|
||||
|
||||
|
||||
export const fetchGekanatorExtraQuestions = async (
|
||||
gameId: number,
|
||||
): Promise<GekanatorExtraQuestion[]> => {
|
||||
const data = await apiGet<{ questions: GekanatorExtraQuestion[] }> (
|
||||
`/gekanator/games/${ gameId }/extra_questions`)
|
||||
return data.questions
|
||||
}
|
||||
|
||||
|
||||
export const buildGekanatorQuestions = (posts: Post[]): GekanatorQuestion[] => {
|
||||
const tagCounts = countBy (posts.flatMap (post =>
|
||||
post.tags
|
||||
@@ -393,3 +468,16 @@ export const saveGekanatorQuestionSuggestion = async ({
|
||||
gekanator_game_id: gekanatorGameId,
|
||||
question_text: questionText,
|
||||
answer })
|
||||
|
||||
|
||||
export const saveGekanatorExtraQuestionAnswers = async ({
|
||||
gameId,
|
||||
answers,
|
||||
}: {
|
||||
gameId: number
|
||||
answers: { questionId: number; answer: GekanatorAnswerValue }[]
|
||||
}) =>
|
||||
await apiPost (`/gekanator/games/${ gameId }/extra_question_answers`, {
|
||||
answers: answers.map (item => ({
|
||||
question_id: item.questionId,
|
||||
answer: item.answer })) })
|
||||
|
||||
新しい課題から参照
ユーザをブロックする